diff --git a/Generator/generator-botbuilder-java/generators/app/templates/app.java b/Generator/generator-botbuilder-java/generators/app/templates/app.java
index 65584b463..c182c1422 100644
--- a/Generator/generator-botbuilder-java/generators/app/templates/app.java
+++ b/Generator/generator-botbuilder-java/generators/app/templates/app.java
@@ -10,7 +10,7 @@
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.implementation.ConnectorClientImpl;
+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;
diff --git a/etc/bot-checkstyle.xml b/etc/bot-checkstyle.xml
new file mode 100644
index 000000000..55fa68e98
--- /dev/null
+++ b/etc/bot-checkstyle.xml
@@ -0,0 +1,179 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 2c850e05b..bbb50bc4e 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
@@ -1,779 +1,780 @@
-package com.microsoft.bot.builder;
-
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-import com.microsoft.bot.connector.ConnectorClient;
-import com.microsoft.bot.connector.Conversations;
-import com.microsoft.bot.connector.authentication.*;
-import com.microsoft.bot.connector.implementation.ConnectorClientImpl;
-import com.microsoft.bot.connector.implementation.ConversationsImpl;
-import com.microsoft.bot.schema.ActivityImpl;
-import com.microsoft.bot.schema.models.*;
-import com.microsoft.rest.retry.RetryStrategy;
-import org.apache.commons.lang3.StringUtils;
-import sun.net.www.http.HttpClient;
-
-import java.net.MalformedURLException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.*;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.function.Consumer;
-import java.util.function.Function;
-
-import static java.util.concurrent.CompletableFuture.completedFuture;
-
-/**
- * A bot adapter that can connect a bot to a service endpoint.
- * The bot adapter encapsulates authentication processes and sends
- * activities to and receives activities from the Bot Connector Service. When your
- * bot receives an activity, the adapter creates a context object, passes it to your
- * bot's application logic, and sends responses back to the user's channel.
- *
Use {@link Use(Middleware)} to add {@link Middleware} objects
- * to your adapter’s middleware collection. The adapter processes and directs
- * incoming activities in through the bot middleware pipeline to your bot’s logic
- * and then back out again. As each activity flows in and out of the bot, each piece
- * of middleware can inspect or act upon the activity, both before and after the bot
- * logic runs.
- *
- * {@linkalso TurnContext}
- * {@linkalso Activity}
- * {@linkalso Bot}
- * {@linkalso Middleware}
- */
-public class BotFrameworkAdapter extends BotAdapter {
- private final CredentialProvider _credentialProvider;
-
- private final RetryStrategy connectorClientRetryStrategy;
- private Map appCredentialMap = new HashMap();
-
- private final String InvokeReponseKey = "BotFrameworkAdapter.InvokeResponse";
- private boolean isEmulatingOAuthCards = false;
-
- /**
- * Initializes a new instance of the {@link BotFrameworkAdapter} class,
- * using a credential provider.
- *
- * @param credentialProvider The credential provider.
- * @param connectorClientRetryStrategy Retry strategy for retrying HTTP operations.
- * @param httpClient The HTTP client.
- * @param middleware The middleware to initially add to the adapter.
- * @throws IllegalArgumentException {@code credentialProvider} is {@code null}.
- * Use a {@link MiddlewareSet} object to add multiple middleware
- * components in the conustructor. Use the {@link Use(Middleware)} method to
- * add additional middleware to the adapter after construction.
- */
- public BotFrameworkAdapter(CredentialProvider credentialProvider) {
- this(credentialProvider, null, null, null);
- }
-
- public BotFrameworkAdapter(CredentialProvider credentialProvider, RetryStrategy connectorClientRetryStrategy) {
- this(credentialProvider, connectorClientRetryStrategy, null, null);
- }
-
- public BotFrameworkAdapter(CredentialProvider credentialProvider, RetryStrategy connectorClientRetryStrategy, HttpClient httpClient) {
- this(credentialProvider, connectorClientRetryStrategy, httpClient, null);
- }
-
- public BotFrameworkAdapter(CredentialProvider credentialProvider, RetryStrategy connectorClientRetryStrategy, HttpClient httpClient, Middleware middleware) {
- if (credentialProvider == null)
- throw new IllegalArgumentException("credentialProvider");
- _credentialProvider = credentialProvider;
- //_httpClient = httpClient ?? new HttpClient();
- this.connectorClientRetryStrategy = connectorClientRetryStrategy;
-
- if (middleware != null) {
- this.Use(middleware);
- }
- }
-
- /**
- * Sends a proactive message from the bot to a conversation.
- *
- * @param botAppId The application ID of the bot. This is the appId returned by Portal registration, and is
- * generally found in the "MicrosoftAppId" parameter in appSettings.json.
- * @param reference A reference to the conversation to continue.
- * @param callback The method to call for the resulting bot turn.
- * @return A task that represents the work queued to execute.
- * @throws IllegalArgumentException {@code botAppId}, {@code reference}, or
- * {@code callback} is {@code null}.
- * Call this method to proactively send a message to a conversation.
- * Most channels require a user to initaiate a conversation with a bot
- * before the bot can send activities to the user.
- * This method registers the following.services().for the turn.
- * - {@link ConnectorClient}, the channel connector client to use this turn.
- *
- *
- * This overload differers from the Node implementation by requiring the BotId to be
- * passed in. The .Net code allows multiple bots to be hosted in a single adapter which
- * isn't something supported by Node.
- *
- *
- * {@linkalso ProcessActivity(String, Activity, Func { TurnContext, Task })}
- * {@linkalso BotAdapter.RunPipeline(TurnContext, Func { TurnContext, Task } }
- */
- @Override
- public void ContinueConversation(String botAppId, ConversationReference reference, Consumer callback) throws Exception {
- if (StringUtils.isEmpty(botAppId))
- throw new IllegalArgumentException("botAppId");
-
- if (reference == null)
- throw new IllegalArgumentException("reference");
-
- if (callback == null)
- throw new IllegalArgumentException("callback");
-
- try (TurnContextImpl context = new TurnContextImpl(this, new ConversationReferenceHelper(reference).GetPostToBotMessage())) {
- // Hand craft Claims Identity.
- HashMap claims = new HashMap();
- claims.put(AuthenticationConstants.AudienceClaim, botAppId);
- claims.put(AuthenticationConstants.AppIdClaim, botAppId);
- ClaimsIdentityImpl claimsIdentity = new ClaimsIdentityImpl("ExternalBearer", claims);
-
- context.getServices().Add("BotIdentity", claimsIdentity);
-
- ConnectorClient connectorClient = this.CreateConnectorClientAsync(reference.serviceUrl(), claimsIdentity).join();
- context.getServices().Add("ConnectorClient", connectorClient);
- RunPipeline(context, callback);
- }
- return;
- }
-
- /**
- * Initializes a new instance of the {@link BotFrameworkAdapter} class,
- * using an application ID and secret.
- *
- * @param appId The application ID of the bot.
- * @param appPassword The application secret for the bot.
- * @param connectorClientRetryStrategy Retry policy for retrying HTTP operations.
- * @param httpClient The HTTP client.
- * @param middleware The middleware to initially add to the adapter.
- * Use a {@link MiddlewareSet} object to add multiple middleware
- * components in the conustructor. Use the {@link Use(Middleware)} method to
- * add additional middleware to the adapter after construction.
- */
- public BotFrameworkAdapter(String appId, String appPassword) {
- this(appId, appPassword, null, null, null);
- }
-
- public BotFrameworkAdapter(String appId, String appPassword, RetryStrategy connectorClientRetryStrategy) {
- this(appId, appPassword, connectorClientRetryStrategy, null, null);
- }
-
- public BotFrameworkAdapter(String appId, String appPassword, RetryStrategy connectorClientRetryStrategy, HttpClient httpClient) {
- this(appId, appPassword, connectorClientRetryStrategy, httpClient, null);
- }
-
- public BotFrameworkAdapter(String appId, String appPassword, RetryStrategy connectorClientRetryStrategy, HttpClient httpClient, Middleware middleware) {
- this(new SimpleCredentialProvider(appId, appPassword), connectorClientRetryStrategy, httpClient, middleware);
- }
-
- /**
- * Adds middleware to the adapter's pipeline.
- *
- * @param middleware The middleware to add.
- * @return The updated adapter object.
- * Middleware is added to the adapter at initialization time.
- * For each turn, the adapter calls middleware in the order in which you added it.
- */
-
- public BotFrameworkAdapter Use(Middleware middleware) {
- super._middlewareSet.Use(middleware);
- return this;
- }
-
- /**
- * Creates a turn context and runs the middleware pipeline for an incoming activity.
- *
- * @param authHeader The HTTP authentication header of the request.
- * @param activity The incoming activity.
- * @param callback The code to run at the end of the adapter's middleware
- * pipeline.
- * @return A task that represents the work queued to execute. If the activity type
- * was 'Invoke' and the corresponding key (channelId + activityId) was found
- * then an InvokeResponse is returned, otherwise null is returned.
- * @throws IllegalArgumentException {@code activity} is {@code null}.
- * @throws UnauthorizedAccessException authentication failed.
- * Call this method to reactively send a message to a conversation.
- * This method registers the following.services().for the turn.
- * - {@link ConnectorClient}, the channel connector client to use this turn.
- *
- *
- * {@linkalso ContinueConversation(String, ConversationReference, Func { TurnContext, Task })}
- * {@linkalso BotAdapter.RunPipeline(TurnContext, Func { TurnContext, Task })}
- */
- public CompletableFuture ProcessActivity(String authHeader, ActivityImpl activity, Function callback) throws Exception {
- BotAssert.ActivityNotNull(activity);
-
- //ClaimsIdentity claimsIdentity = await(JwtTokenValidation.validateAuthHeader(activity, authHeader, _credentialProvider));
-
- //return completedFuture(await(ProcessActivity(claimsIdentity, activity, callback)));
- return completedFuture(null);
- }
-
- public CompletableFuture ProcessActivity(ClaimsIdentity identity, ActivityImpl activity, Consumer callback) throws Exception {
- BotAssert.ActivityNotNull(activity);
-
- try (TurnContextImpl context = new TurnContextImpl(this, activity)) {
- context.getServices().Add("BotIdentity", identity);
-
- ConnectorClient connectorClient = this.CreateConnectorClientAsync(activity.serviceUrl(), identity).join();
- // TODO: Verify key that C# uses
- context.getServices().Add("ConnectorClient", connectorClient);
-
- super.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.
- if (activity.type() == ActivityTypes.INVOKE) {
- Activity invokeResponse = context.getServices().Get(InvokeReponseKey);
- if (invokeResponse == null) {
- // ToDo: Trace Here
- throw new IllegalStateException("Bot failed to return a valid 'invokeResponse' activity.");
- } else {
- return completedFuture((InvokeResponse) invokeResponse.value());
- }
- }
-
- // For all non-invoke scenarios, the HTTP layers above don't have to mess
- // withthe Body and return codes.
- return null;
- }
- }
-
- /**
- * Sends activities to the conversation.
- *
- * @param context The context object for the turn.
- * @param activities The activities to send.
- * @return A task that represents the work queued to execute.
- * If the activities are successfully sent, the task result contains
- * an array of {@link ResourceResponse} objects containing the IDs that
- * the receiving channel assigned to the activities.
- * {@linkalso TurnContext.OnSendActivities(SendActivitiesHandler)}
- */
- public ResourceResponse[] SendActivities(TurnContext context, Activity[] activities) throws InterruptedException {
- if (context == null) {
- throw new IllegalArgumentException("context");
- }
-
- if (activities == null) {
- throw new IllegalArgumentException("activities");
- }
-
- if (activities.length == 0) {
- throw new IllegalArgumentException("Expecting one or more activities, but the array was empty.");
- }
-
- ResourceResponse[] responses = new ResourceResponse[activities.length];
-
- /*
- * NOTE: we're using for here (vs. foreach) because we want to simultaneously index into the
- * activities array to get the activity to process as well as use that index to assign
- * the response to the responses array and this is the most cost effective way to do that.
- */
- for (int index = 0; index < activities.length; index++) {
- Activity activity = activities[index];
- ResourceResponse response = new ResourceResponse();
-
- if (activity.type().toString().equals("delay")) {
- // The Activity Schema doesn't have a delay type build in, so it's simulated
- // here in the Bot. This matches the behavior in the Node connector.
- int delayMs = (int) activity.value();
- Thread.sleep(delayMs);
- //await(Task.Delay(delayMs));
- // No need to create a response. One will be created below.
- } else if (activity.type().toString().equals("invokeResponse")) // Aligning name with Node
- {
- context.getServices().Add(InvokeReponseKey, activity);
- // No need to create a response. One will be created below.
- } else if (activity.type() == ActivityTypes.TRACE && !activity.channelId().equals("emulator")) {
- // if it is a Trace activity we only send to the channel if it's the emulator.
- } else if (!StringUtils.isEmpty(activity.replyToId())) {
- ConnectorClient connectorClient = context.getServices().Get("ConnectorClient");
- response = connectorClient.conversations().replyToActivity(activity.conversation().id(), activity.id(), activity);
- } else {
- ConnectorClient connectorClient = context.getServices().Get("ConnectorClient");
- response = connectorClient.conversations().sendToConversation(activity.conversation().id(), activity);
- }
-
- // If No response is set, then defult to a "simple" response. This can't really be done
- // above, as there are cases where the ReplyTo/SendTo methods will also return null
- // (See below) so the check has to happen here.
-
- // Note: In addition to the Invoke / Delay / Activity cases, this code also applies
- // with Skype and Teams with regards to typing events. When sending a typing event in
- // these channels they do not return a RequestResponse which causes the bot to blow up.
- // https://github.com/Microsoft/botbuilder-dotnet/issues/460
- // bug report : https://github.com/Microsoft/botbuilder-dotnet/issues/465
- if (response == null) {
- response = new ResourceResponse().withId((activity.id() == null) ? "" : activity.id());
- }
-
- responses[index] = response;
- }
-
- return responses;
- }
-
- /**
- * Replaces an existing activity in the conversation.
- *
- * @param context The context object for the turn.
- * @param activity New replacement activity.
- * @return A task that represents the work queued to execute.
- * If the activity is successfully sent, the task result contains
- * a {@link ResourceResponse} object containing the ID that the receiving
- * channel assigned to the activity.
- * Before calling this, set the ID of the replacement activity to the ID
- * of the activity to replace.
- * {@linkalso TurnContext.OnUpdateActivity(UpdateActivityHandler)}
- */
- @Override
- public ResourceResponse UpdateActivity(TurnContext context, Activity activity) {
- ConnectorClient connectorClient = context.getServices().Get("ConnectorClient");
- // TODO String conversationId, String activityId, Activity activity)
- return connectorClient.conversations().updateActivity(activity.conversation().id(), activity.id(), activity);
- }
-
- /**
- * Deletes an existing activity in the conversation.
- *
- * @param context The context object for the turn.
- * @param reference Conversation reference for the activity to delete.
- * @return A task that represents the work queued to execute.
- * The {@link ConversationReference.ActivityId} of the conversation
- * reference identifies the activity to delete.
- * {@linkalso TurnContext.OnDeleteActivity(DeleteActivityHandler)}
- */
- public void DeleteActivity(TurnContext context, ConversationReference reference) {
- ConnectorClientImpl connectorClient = context.getServices().Get("ConnectorClient");
- try {
- connectorClient.conversations().deleteConversationMemberFuture(reference.conversation().id(), reference.activityId()).join();
- } catch (ExecutionException e) {
- e.printStackTrace();
- throw new RuntimeException(String.format("Failed deleting activity (%s)", e.toString()));
- } catch (InterruptedException e) {
- e.printStackTrace();
- throw new RuntimeException(String.format("Failed deleting activity (%s)", e.toString()));
- }
- return;
- }
-
- /**
- * Deletes a member from the current conversation
- *
- * @param context The context object for the turn.
- * @param memberId ID of the member to delete from the conversation
- * @return
- */
- public void DeleteConversationMember(TurnContextImpl context, String memberId) {
- if (context.getActivity().conversation() == null)
- throw new IllegalArgumentException("BotFrameworkAdapter.deleteConversationMember(): missing conversation");
-
- if (StringUtils.isEmpty(context.getActivity().conversation().id()))
- throw new IllegalArgumentException("BotFrameworkAdapter.deleteConversationMember(): missing conversation.id");
-
- ConnectorClient connectorClient = context.getServices().Get("ConnectorClient");
-
- String conversationId = context.getActivity().conversation().id();
-
- // TODO:
- //await (connectorClient.conversations().DeleteConversationMemberAsync(conversationId, memberId));
- return;
- }
-
- /**
- * Lists the members of a given activity.
- *
- * @param context The context object for the turn.
- * @param activityId (Optional) Activity ID to enumerate. If not specified the current activities ID will be used.
- * @return List of Members of the activity
- */
- public CompletableFuture> GetActivityMembers(TurnContextImpl context) {
- return GetActivityMembers(context, null);
- }
-
- public CompletableFuture> GetActivityMembers(TurnContextImpl context, String activityId) {
- // If no activity was passed in, use the current activity.
- if (activityId == null)
- activityId = context.getActivity().id();
-
- if (context.getActivity().conversation() == null)
- throw new IllegalArgumentException("BotFrameworkAdapter.GetActivityMembers(): missing conversation");
-
- if (StringUtils.isEmpty((context.getActivity().conversation().id())))
- throw new IllegalArgumentException("BotFrameworkAdapter.GetActivityMembers(): missing conversation.id");
-
- ConnectorClient connectorClient = context.getServices().Get("ConnectorClient");
- String conversationId = context.getActivity().conversation().id();
-
- // TODO:
- //List accounts = await(connectorClient.conversations().GetActivityMembersAsync(conversationId, activityId));
-
- return completedFuture(null);
- }
-
- /**
- * Lists the members of the current conversation.
- *
- * @param context The context object for the turn.
- * @return List of Members of the current conversation
- */
- public CompletableFuture> GetConversationMembers(TurnContextImpl context) {
- if (context.getActivity().conversation() == null)
- throw new IllegalArgumentException("BotFrameworkAdapter.GetActivityMembers(): missing conversation");
-
- if (StringUtils.isEmpty(context.getActivity().conversation().id()))
- throw new IllegalArgumentException("BotFrameworkAdapter.GetActivityMembers(): missing conversation.id");
-
- ConnectorClient connectorClient = context.getServices().Get("ConnectorClient");
- String conversationId = context.getActivity().conversation().id();
-
- // TODO
- //List accounts = await(connectorClient.conversations().getConversationMembersAsync(conversationId));
- return completedFuture(null);
- }
-
- /**
- * Lists the Conversations in which this bot has participated for a given channel server. The
- * channel server returns results in pages and each page will include a `continuationToken`
- * that can be used to fetch the next page of results from the server.
- *
- * @param serviceUrl The URL of the channel server to query. This can be retrieved
- * from `context.activity.serviceUrl`.
- * @param credentials The credentials needed for the Bot to connect to the.services().
- * @param continuationToken (Optional) token used to fetch the next page of results
- * from the channel server. This should be left as `null` to retrieve the first page
- * of results.
- * @return List of Members of the current conversation
- *
- * This overload may be called from outside the context of a conversation, as only the
- * Bot's ServiceUrl and credentials are required.
- */
- public CompletableFuture GetConversations(String serviceUrl, MicrosoftAppCredentials credentials) throws MalformedURLException, URISyntaxException {
- return GetConversations(serviceUrl, credentials, null);
- }
-
- public CompletableFuture GetConversations(String serviceUrl, MicrosoftAppCredentials credentials, String continuationToken) throws MalformedURLException, URISyntaxException {
- if (StringUtils.isEmpty(serviceUrl))
- throw new IllegalArgumentException("serviceUrl");
-
- if (credentials == null)
- throw new IllegalArgumentException("credentials");
-
- ConnectorClient connectorClient = this.CreateConnectorClient(serviceUrl, credentials);
- // TODO
- //ConversationsResult results = await(connectorClient.conversations().getConversationsAsync(continuationToken));
- return completedFuture(null);
- }
-
- /**
- * Lists the Conversations in which this bot has participated for a given channel server. The
- * channel server returns results in pages and each page will include a `continuationToken`
- * that can be used to fetch the next page of results from the server.
- *
- * @param context The context object for the turn.
- * @param continuationToken (Optional) token used to fetch the next page of results
- * from the channel server. This should be left as `null` to retrieve the first page
- * of results.
- * @return List of Members of the current conversation
- *
- * This overload may be called during standard Activity processing, at which point the Bot's
- * service URL and credentials that are part of the current activity processing pipeline
- * will be used.
- */
- public CompletableFuture GetConversations(TurnContextImpl context) {
- return GetConversations(context, null);
- }
-
- public CompletableFuture GetConversations(TurnContextImpl context, String continuationToken) {
- ConnectorClient connectorClient = context.getServices().Get("ConnectorClient");
- // TODO
- //ConversationsResult results = await(connectorClient.conversations().getConversationsAsync());
- return completedFuture(null);
- }
-
-
- /**
- * Attempts to retrieve the token for a user that's in a login flow.
- *
- * @param context Context for the current turn of conversation with the user.
- * @param connectionName Name of the auth connection to use.
- * @param magicCode (Optional) Optional user entered code to validate.
- * @return Token Response
- */
- public CompletableFuture GetUserToken(TurnContextImpl context, String connectionName, String magicCode) {
- BotAssert.ContextNotNull(context);
- if (context.getActivity().from() == null || StringUtils.isEmpty(context.getActivity().from().id()))
- throw new IllegalArgumentException("BotFrameworkAdapter.GetuserToken(): missing from or from.id");
-
- if (StringUtils.isEmpty(connectionName))
- throw new IllegalArgumentException("connectionName");
-
- //OAuthClient client = this.CreateOAuthApiClient(context);
- //return await(client.GetUserTokenAsync(context.getActivity().from().id(), connectionName, magicCode));
- return completedFuture(null);
- }
-
- /**
- * 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 connectionName Name of the auth connection to use.
- * @return
- */
- public CompletableFuture GetOauthSignInLink(TurnContextImpl context, String connectionName) {
- BotAssert.ContextNotNull(context);
- if (StringUtils.isEmpty(connectionName))
- throw new IllegalArgumentException("connectionName");
-
- //OAuthClient client = this.CreateOAuthApiClient(context);
- //return await(client.GetSignInLinkAsync(context.getActivity(), connectionName));
- return completedFuture(null);
- }
-
- /**
- * Signs the user out with the token server.
- *
- * @param context Context for the current turn of conversation with the user.
- * @param connectionName Name of the auth connection to use.
- * @return
- */
- public CompletableFuture SignOutUser(TurnContextImpl context, String connectionName) {
- BotAssert.ContextNotNull(context);
- if (StringUtils.isEmpty(connectionName))
- throw new IllegalArgumentException("connectionName");
-
- //OAuthClient client = this.CreateOAuthApiClient(context);
- //await(client.SignOutUserAsync(context.Activity.From.Id, connectionName));
- return completedFuture(null);
- }
-
- /**
- * Creates a conversation on the specified channel.
- *
- * @param channelId The ID for the channel.
- * @param serviceUrl The channel's service URL endpoint.
- * @param credentials The application credentials for the bot.
- * @param conversationParameters The conversation information to use to
- * create the conversation.
- * @param callback The method to call for the resulting bot turn.
- * @return A task that represents the work queued to execute.
- * To start a conversation, your bot must know its account information
- * and the user's account information on that channel.
- * Most channels only support initiating a direct message (non-group) conversation.
- * The adapter attempts to create a new conversation on the channel, and
- * then sends a {@code conversationUpdate} activity through its middleware pipeline
- * to the {@code callback} method.
- * If the conversation is established with the
- * specified users, the ID of the activity's {@link Activity.Conversation}
- * will contain the ID of the new conversation.
- */
- public CompletableFuture CreateConversation(String channelId, String serviceUrl, MicrosoftAppCredentials
- credentials, ConversationParameters conversationParameters, Consumer callback) throws Exception {
- // Validate serviceUrl - can throw
- URI uri = new URI(serviceUrl);
- return CompletableFuture.runAsync(() -> {
- ConnectorClient connectorClient = null;
- try {
- connectorClient = this.CreateConnectorClient(serviceUrl, credentials);
- } catch (MalformedURLException e) {
- e.printStackTrace();
- throw new RuntimeException(String.format("Bad serviceUrl: %s", serviceUrl));
- } catch (URISyntaxException e) {
- e.printStackTrace();
- throw new RuntimeException(String.format("Bad serviceUrl: %s", serviceUrl));
- }
-
- Conversations conv = connectorClient.conversations();
- List results = null;
- if (conv instanceof ConversationsImpl) {
- ConversationsImpl convImpl = (ConversationsImpl) conv;
- results = convImpl.CreateConversationAsync(conversationParameters).join();
- } else {
- results = new ArrayList();
- results.add(conv.createConversation(conversationParameters));
- }
- if (results.size() == 1) {
-
- ConversationResourceResponse result = results.get(0);
- // Create a conversation update activity to represent the result.
-
- ConversationUpdateActivity conversationUpdate = (ConversationUpdateActivity) MessageActivity.CreateConversationUpdateActivity()
- .withChannelId(channelId)
- .withTopicName(conversationParameters.topicName())
- .withServiceUrl(serviceUrl)
- .withMembersAdded(conversationParameters.members())
- .withId((result.activityId() != null) ? result.activityId() : UUID.randomUUID().toString())
- .withConversation(new ConversationAccount().withId(result.id()))
- .withRecipient(conversationParameters.bot());
-
- try (TurnContextImpl context = new TurnContextImpl(this, conversationUpdate)) {
- try {
- this.RunPipeline(context, callback);
- } catch (Exception e) {
- e.printStackTrace();
- throw new RuntimeException(String.format("Running pipeline failed : %s", e));
- }
- } catch (Exception e) {
- e.printStackTrace();
- throw new RuntimeException(String.format("Turn Context Error: %s", e));
- }
- } else {
- // Should never happen
- throw new RuntimeException(String.format("Conversations create issue - returned %d conversations", results.size()));
- }
- });
-
- }
-
- protected CompletableFuture TrySetEmulatingOAuthCards(TurnContext turnContext) {
- if (!isEmulatingOAuthCards &&
- turnContext.getActivity().channelId().equals("emulator") &&
- (_credentialProvider.isAuthenticationDisabledAsync().join())) {
- isEmulatingOAuthCards = true;
- }
- return completedFuture(isEmulatingOAuthCards);
-
- }
-
- protected OAuthClient CreateOAuthApiClient(TurnContext context) throws MalformedURLException, URISyntaxException {
- ConnectorClientImpl client = context.getServices().Get("ConnectorClient");
- if (client == null) {
- throw new IllegalArgumentException("CreateOAuthApiClient: OAuth requires a valid ConnectorClient instance");
- }
- if (isEmulatingOAuthCards) {
- return new OAuthClient(client, context.getActivity().serviceUrl());
- }
- return new OAuthClient(client, AuthenticationConstants.OAuthUrl);
- }
-
- /**
- * Creates the connector client asynchronous.
- *
- * @param serviceUrl The service URL.
- * @param claimsIdentity The claims identity.
- * @return ConnectorClient instance.
- * @throws UnsupportedOperationException ClaimsIdemtity cannot be null. Pass Anonymous ClaimsIdentity if authentication is turned off.
- */
- private CompletableFuture CreateConnectorClientAsync(String serviceUrl, ClaimsIdentity claimsIdentity) {
-
- return CompletableFuture.supplyAsync(() -> {
- if (claimsIdentity == null) {
- throw 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 emulator it is in AppId claim. For
- // unauthenticated requests we have anonymous identity provided auth is disabled.
- if (claimsIdentity.claims() == null) {
- try {
- return CreateConnectorClient(serviceUrl);
- } catch (MalformedURLException e) {
- e.printStackTrace();
- throw new RuntimeException(String.format("Invalid Service URL: %s", serviceUrl));
- } catch (URISyntaxException e) {
- e.printStackTrace();
- throw new RuntimeException(String.format("Invalid Service URL: %s", serviceUrl));
- }
- }
-
- // For Activities coming from Emulator AppId claim contains the Bot's AAD AppId.
- // For anonymous requests (requests with no header) appId is not set in claims.
-
- Map.Entry botAppIdClaim = claimsIdentity.claims().entrySet().stream()
- .filter(claim -> claim.getKey() == AuthenticationConstants.AudienceClaim)
- .findFirst()
- .orElse(null);
- if (botAppIdClaim == null) {
- botAppIdClaim = claimsIdentity.claims().entrySet().stream()
- .filter(claim -> claim.getKey() == AuthenticationConstants.AppIdClaim)
- .findFirst()
- .orElse(null);
- }
-
- if (botAppIdClaim != null) {
- String botId = botAppIdClaim.getValue();
- MicrosoftAppCredentials appCredentials = this.GetAppCredentialsAsync(botId).join();
- try {
- return this.CreateConnectorClient(serviceUrl, appCredentials);
- } catch (MalformedURLException e) {
- e.printStackTrace();
- throw new RuntimeException(String.format("Bad Service URL: %s", serviceUrl));
- } catch (URISyntaxException e) {
- e.printStackTrace();
- throw new RuntimeException(String.format("Bad Service URL: %s", serviceUrl));
- }
- } else {
- try {
- return this.CreateConnectorClient(serviceUrl);
- } catch (MalformedURLException e) {
- e.printStackTrace();
- throw new RuntimeException(String.format("Bad Service URL: %s", serviceUrl));
- } catch (URISyntaxException e) {
- e.printStackTrace();
- throw new RuntimeException(String.format("Bad Service URL: %s", serviceUrl));
- }
- }
- });
-
- }
-
- /**
- * Creates the connector client.
- *
- * @param serviceUrl The service URL.
- * @param appCredentials The application credentials for the bot.
- * @return Connector client instance.
- */
- private ConnectorClient CreateConnectorClient(String serviceUrl) throws MalformedURLException, URISyntaxException {
- return CreateConnectorClient(serviceUrl, null);
- }
-
- private ConnectorClient CreateConnectorClient(String serviceUrl, MicrosoftAppCredentials appCredentials) throws MalformedURLException, URISyntaxException {
- ConnectorClientImpl connectorClient = null;
- if (appCredentials != null) {
- connectorClient = new ConnectorClientImpl(new URI(serviceUrl).toURL().toString(), appCredentials);
- }
- // TODO: Constructor necessary?
-// else {
-//
-// connectorClient = new ConnectorClientImpl(new URI(serviceUrl).toURL().toString());
-// }
-
- if (this.connectorClientRetryStrategy != null)
- connectorClient.withRestRetryStrategy(this.connectorClientRetryStrategy);
-
-
- return connectorClient;
-
- }
-
- /**
- * Gets the application credentials. App Credentials are cached so as to ensure we are not refreshing
- * token everytime.
- *
- * @param appId The application identifier (AAD Id for the bot).
- * @return App credentials.
- */
- private CompletableFuture GetAppCredentialsAsync(String appId) {
- CompletableFuture result = CompletableFuture.supplyAsync(() -> {
- if (appId == null) {
- return MicrosoftAppCredentials.Empty;
- }
- if (this.appCredentialMap.containsKey(appId))
- return this.appCredentialMap.get(appId);
- String appPassword = this._credentialProvider.getAppPasswordAsync(appId).join();
- MicrosoftAppCredentials appCredentials = new MicrosoftAppCredentials(appId, appPassword);
- this.appCredentialMap.put(appId, appCredentials);
- return appCredentials;
-
- });
- return result;
- }
-
-}
+package com.microsoft.bot.builder;
+
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+import com.microsoft.bot.connector.ConnectorClient;
+import com.microsoft.bot.connector.Conversations;
+import com.microsoft.bot.connector.ExecutorFactory;
+import com.microsoft.bot.connector.authentication.*;
+import com.microsoft.bot.connector.rest.RestConnectorClient;
+import com.microsoft.bot.connector.rest.RestConversations;
+import com.microsoft.bot.schema.ActivityImpl;
+import com.microsoft.bot.schema.models.*;
+import com.microsoft.rest.retry.RetryStrategy;
+import org.apache.commons.lang3.StringUtils;
+import sun.net.www.http.HttpClient;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import static java.util.concurrent.CompletableFuture.completedFuture;
+
+/**
+ * A bot adapter that can connect a bot to a service endpoint.
+ * The bot adapter encapsulates authentication processes and sends
+ * activities to and receives activities from the Bot Connector Service. When your
+ * bot receives an activity, the adapter creates a context object, passes it to your
+ * bot's application logic, and sends responses back to the user's channel.
+ * Use {@link Use(Middleware)} to add {@link Middleware} objects
+ * to your adapter’s middleware collection. The adapter processes and directs
+ * incoming activities in through the bot middleware pipeline to your bot’s logic
+ * and then back out again. As each activity flows in and out of the bot, each piece
+ * of middleware can inspect or act upon the activity, both before and after the bot
+ * logic runs.
+ *
+ * {@linkalso TurnContext}
+ * {@linkalso Activity}
+ * {@linkalso Bot}
+ * {@linkalso Middleware}
+ */
+public class BotFrameworkAdapter extends BotAdapter {
+ private final CredentialProvider _credentialProvider;
+
+ private final RetryStrategy connectorClientRetryStrategy;
+ private Map appCredentialMap = new HashMap();
+
+ private final String InvokeReponseKey = "BotFrameworkAdapter.InvokeResponse";
+ private boolean isEmulatingOAuthCards = false;
+
+ /**
+ * Initializes a new instance of the {@link BotFrameworkAdapter} class,
+ * using a credential provider.
+ *
+ * @param credentialProvider The credential provider.
+ * @param connectorClientRetryStrategy Retry strategy for retrying HTTP operations.
+ * @param httpClient The HTTP client.
+ * @param middleware The middleware to initially add to the adapter.
+ * @throws IllegalArgumentException {@code credentialProvider} is {@code null}.
+ * Use a {@link MiddlewareSet} object to add multiple middleware
+ * components in the conustructor. Use the {@link Use(Middleware)} method to
+ * add additional middleware to the adapter after construction.
+ */
+ public BotFrameworkAdapter(CredentialProvider credentialProvider) {
+ this(credentialProvider, null, null, null);
+ }
+
+ public BotFrameworkAdapter(CredentialProvider credentialProvider, RetryStrategy connectorClientRetryStrategy) {
+ this(credentialProvider, connectorClientRetryStrategy, null, null);
+ }
+
+ public BotFrameworkAdapter(CredentialProvider credentialProvider, RetryStrategy connectorClientRetryStrategy, HttpClient httpClient) {
+ this(credentialProvider, connectorClientRetryStrategy, httpClient, null);
+ }
+
+ public BotFrameworkAdapter(CredentialProvider credentialProvider, RetryStrategy connectorClientRetryStrategy, HttpClient httpClient, Middleware middleware) {
+ if (credentialProvider == null)
+ throw new IllegalArgumentException("credentialProvider");
+ _credentialProvider = credentialProvider;
+ //_httpClient = httpClient ?? new HttpClient();
+ this.connectorClientRetryStrategy = connectorClientRetryStrategy;
+
+ if (middleware != null) {
+ this.Use(middleware);
+ }
+ }
+
+ /**
+ * Sends a proactive message from the bot to a conversation.
+ *
+ * @param botAppId The application ID of the bot. This is the appId returned by Portal registration, and is
+ * generally found in the "MicrosoftAppId" parameter in appSettings.json.
+ * @param reference A reference to the conversation to continue.
+ * @param callback The method to call for the resulting bot turn.
+ * @return A task that represents the work queued to execute.
+ * @throws IllegalArgumentException {@code botAppId}, {@code reference}, or
+ * {@code callback} is {@code null}.
+ * Call this method to proactively send a message to a conversation.
+ * Most channels require a user to initaiate a conversation with a bot
+ * before the bot can send activities to the user.
+ * This method registers the following.services().for the turn.
+ * - {@link ConnectorClient}, the channel connector client to use this turn.
+ *
+ *
+ * This overload differers from the Node implementation by requiring the BotId to be
+ * passed in. The .Net code allows multiple bots to be hosted in a single adapter which
+ * isn't something supported by Node.
+ *
+ *
+ * {@linkalso ProcessActivity(String, Activity, Func { TurnContext, Task })}
+ * {@linkalso BotAdapter.RunPipeline(TurnContext, Func { TurnContext, Task } }
+ */
+ @Override
+ public void ContinueConversation(String botAppId, ConversationReference reference, Consumer callback) throws Exception {
+ if (StringUtils.isEmpty(botAppId))
+ throw new IllegalArgumentException("botAppId");
+
+ if (reference == null)
+ throw new IllegalArgumentException("reference");
+
+ if (callback == null)
+ throw new IllegalArgumentException("callback");
+
+ try (TurnContextImpl context = new TurnContextImpl(this, new ConversationReferenceHelper(reference).GetPostToBotMessage())) {
+ // Hand craft Claims Identity.
+ HashMap claims = new HashMap();
+ claims.put(AuthenticationConstants.AUDIENCE_CLAIM, botAppId);
+ claims.put(AuthenticationConstants.APPID_CLAIM, botAppId);
+ ClaimsIdentity claimsIdentity = new ClaimsIdentity("ExternalBearer", claims);
+
+ context.getServices().Add("BotIdentity", claimsIdentity);
+
+ ConnectorClient connectorClient = this.CreateConnectorClientAsync(reference.serviceUrl(), claimsIdentity).join();
+ context.getServices().Add("ConnectorClient", connectorClient);
+ RunPipeline(context, callback);
+ }
+ return;
+ }
+
+ /**
+ * Initializes a new instance of the {@link BotFrameworkAdapter} class,
+ * using an application ID and secret.
+ *
+ * @param appId The application ID of the bot.
+ * @param appPassword The application secret for the bot.
+ * @param connectorClientRetryStrategy Retry policy for retrying HTTP operations.
+ * @param httpClient The HTTP client.
+ * @param middleware The middleware to initially add to the adapter.
+ * Use a {@link MiddlewareSet} object to add multiple middleware
+ * components in the conustructor. Use the {@link Use(Middleware)} method to
+ * add additional middleware to the adapter after construction.
+ */
+ public BotFrameworkAdapter(String appId, String appPassword) {
+ this(appId, appPassword, null, null, null);
+ }
+
+ public BotFrameworkAdapter(String appId, String appPassword, RetryStrategy connectorClientRetryStrategy) {
+ this(appId, appPassword, connectorClientRetryStrategy, null, null);
+ }
+
+ public BotFrameworkAdapter(String appId, String appPassword, RetryStrategy connectorClientRetryStrategy, HttpClient httpClient) {
+ this(appId, appPassword, connectorClientRetryStrategy, httpClient, null);
+ }
+
+ public BotFrameworkAdapter(String appId, String appPassword, RetryStrategy connectorClientRetryStrategy, HttpClient httpClient, Middleware middleware) {
+ this(new SimpleCredentialProvider(appId, appPassword), connectorClientRetryStrategy, httpClient, middleware);
+ }
+
+ /**
+ * Adds middleware to the adapter's pipeline.
+ *
+ * @param middleware The middleware to add.
+ * @return The updated adapter object.
+ * Middleware is added to the adapter at initialization time.
+ * For each turn, the adapter calls middleware in the order in which you added it.
+ */
+
+ public BotFrameworkAdapter Use(Middleware middleware) {
+ super._middlewareSet.Use(middleware);
+ return this;
+ }
+
+ /**
+ * Creates a turn context and runs the middleware pipeline for an incoming activity.
+ *
+ * @param authHeader The HTTP authentication header of the request.
+ * @param activity The incoming activity.
+ * @param callback The code to run at the end of the adapter's middleware
+ * pipeline.
+ * @return A task that represents the work queued to execute. If the activity type
+ * was 'Invoke' and the corresponding key (channelId + activityId) was found
+ * then an InvokeResponse is returned, otherwise null is returned.
+ * @throws IllegalArgumentException {@code activity} is {@code null}.
+ * @throws UnauthorizedAccessException authentication failed.
+ * Call this method to reactively send a message to a conversation.
+ * This method registers the following.services().for the turn.
+ * - {@link ConnectorClient}, the channel connector client to use this turn.
+ *
+ *
+ * {@linkalso ContinueConversation(String, ConversationReference, Func { TurnContext, Task })}
+ * {@linkalso BotAdapter.RunPipeline(TurnContext, Func { TurnContext, Task })}
+ */
+ public CompletableFuture ProcessActivity(String authHeader, ActivityImpl activity, Function callback) throws Exception {
+ BotAssert.ActivityNotNull(activity);
+
+ //ClaimsIdentity claimsIdentity = await(JwtTokenValidation.validateAuthHeader(activity, authHeader, _credentialProvider));
+
+ //return completedFuture(await(ProcessActivity(claimsIdentity, activity, callback)));
+ return completedFuture(null);
+ }
+
+ public CompletableFuture ProcessActivity(ClaimsIdentity identity, ActivityImpl activity, Consumer callback) throws Exception {
+ BotAssert.ActivityNotNull(activity);
+
+ try (TurnContextImpl context = new TurnContextImpl(this, activity)) {
+ context.getServices().Add("BotIdentity", identity);
+
+ ConnectorClient connectorClient = this.CreateConnectorClientAsync(activity.serviceUrl(), identity).join();
+ // TODO: Verify key that C# uses
+ context.getServices().Add("ConnectorClient", connectorClient);
+
+ super.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.
+ if (activity.type() == ActivityTypes.INVOKE) {
+ Activity invokeResponse = context.getServices().Get(InvokeReponseKey);
+ if (invokeResponse == null) {
+ // ToDo: Trace Here
+ throw new IllegalStateException("Bot failed to return a valid 'invokeResponse' activity.");
+ } else {
+ return completedFuture((InvokeResponse) invokeResponse.value());
+ }
+ }
+
+ // For all non-invoke scenarios, the HTTP layers above don't have to mess
+ // withthe Body and return codes.
+ return null;
+ }
+ }
+
+ /**
+ * Sends activities to the conversation.
+ *
+ * @param context The context object for the turn.
+ * @param activities The activities to send.
+ * @return A task that represents the work queued to execute.
+ * If the activities are successfully sent, the task result contains
+ * an array of {@link ResourceResponse} objects containing the IDs that
+ * the receiving channel assigned to the activities.
+ * {@linkalso TurnContext.OnSendActivities(SendActivitiesHandler)}
+ */
+ public ResourceResponse[] SendActivities(TurnContext context, Activity[] activities) throws InterruptedException {
+ if (context == null) {
+ throw new IllegalArgumentException("context");
+ }
+
+ if (activities == null) {
+ throw new IllegalArgumentException("activities");
+ }
+
+ if (activities.length == 0) {
+ throw new IllegalArgumentException("Expecting one or more activities, but the array was empty.");
+ }
+
+ ResourceResponse[] responses = new ResourceResponse[activities.length];
+
+ /*
+ * NOTE: we're using for here (vs. foreach) because we want to simultaneously index into the
+ * activities array to get the activity to process as well as use that index to assign
+ * the response to the responses array and this is the most cost effective way to do that.
+ */
+ for (int index = 0; index < activities.length; index++) {
+ Activity activity = activities[index];
+ ResourceResponse response = new ResourceResponse();
+
+ if (activity.type().toString().equals("delay")) {
+ // The Activity Schema doesn't have a delay type build in, so it's simulated
+ // here in the Bot. This matches the behavior in the Node connector.
+ int delayMs = (int) activity.value();
+ Thread.sleep(delayMs);
+ //await(Task.Delay(delayMs));
+ // No need to create a response. One will be created below.
+ } else if (activity.type().toString().equals("invokeResponse")) // Aligning name with Node
+ {
+ context.getServices().Add(InvokeReponseKey, activity);
+ // No need to create a response. One will be created below.
+ } else if (activity.type() == ActivityTypes.TRACE && !activity.channelId().equals("emulator")) {
+ // if it is a Trace activity we only send to the channel if it's the emulator.
+ } else if (!StringUtils.isEmpty(activity.replyToId())) {
+ ConnectorClient connectorClient = context.getServices().Get("ConnectorClient");
+ response = connectorClient.conversations().replyToActivity(activity.conversation().id(), activity.id(), activity);
+ } else {
+ ConnectorClient connectorClient = context.getServices().Get("ConnectorClient");
+ response = connectorClient.conversations().sendToConversation(activity.conversation().id(), activity);
+ }
+
+ // If No response is set, then defult to a "simple" response. This can't really be done
+ // above, as there are cases where the ReplyTo/SendTo methods will also return null
+ // (See below) so the check has to happen here.
+
+ // Note: In addition to the Invoke / Delay / Activity cases, this code also applies
+ // with Skype and Teams with regards to typing events. When sending a typing event in
+ // these channels they do not return a RequestResponse which causes the bot to blow up.
+ // https://github.com/Microsoft/botbuilder-dotnet/issues/460
+ // bug report : https://github.com/Microsoft/botbuilder-dotnet/issues/465
+ if (response == null) {
+ response = new ResourceResponse().withId((activity.id() == null) ? "" : activity.id());
+ }
+
+ responses[index] = response;
+ }
+
+ return responses;
+ }
+
+ /**
+ * Replaces an existing activity in the conversation.
+ *
+ * @param context The context object for the turn.
+ * @param activity New replacement activity.
+ * @return A task that represents the work queued to execute.
+ * If the activity is successfully sent, the task result contains
+ * a {@link ResourceResponse} object containing the ID that the receiving
+ * channel assigned to the activity.
+ * Before calling this, set the ID of the replacement activity to the ID
+ * of the activity to replace.
+ * {@linkalso TurnContext.OnUpdateActivity(UpdateActivityHandler)}
+ */
+ @Override
+ public ResourceResponse UpdateActivity(TurnContext context, Activity activity) {
+ ConnectorClient connectorClient = context.getServices().Get("ConnectorClient");
+ // TODO String conversationId, String activityId, Activity activity)
+ return connectorClient.conversations().updateActivity(activity.conversation().id(), activity.id(), activity);
+ }
+
+ /**
+ * Deletes an existing activity in the conversation.
+ *
+ * @param context The context object for the turn.
+ * @param reference Conversation reference for the activity to delete.
+ * @return A task that represents the work queued to execute.
+ * The {@link ConversationReference.ActivityId} of the conversation
+ * reference identifies the activity to delete.
+ * {@linkalso TurnContext.OnDeleteActivity(DeleteActivityHandler)}
+ */
+ public void DeleteActivity(TurnContext context, ConversationReference reference) {
+ RestConnectorClient connectorClient = context.getServices().Get("ConnectorClient");
+ try {
+ connectorClient.conversations().deleteConversationMemberFuture(reference.conversation().id(), reference.activityId()).join();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ throw new RuntimeException(String.format("Failed deleting activity (%s)", e.toString()));
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ throw new RuntimeException(String.format("Failed deleting activity (%s)", e.toString()));
+ }
+ return;
+ }
+
+ /**
+ * Deletes a member from the current conversation
+ *
+ * @param context The context object for the turn.
+ * @param memberId ID of the member to delete from the conversation
+ * @return
+ */
+ public void DeleteConversationMember(TurnContextImpl context, String memberId) {
+ if (context.getActivity().conversation() == null)
+ throw new IllegalArgumentException("BotFrameworkAdapter.deleteConversationMember(): missing conversation");
+
+ if (StringUtils.isEmpty(context.getActivity().conversation().id()))
+ throw new IllegalArgumentException("BotFrameworkAdapter.deleteConversationMember(): missing conversation.id");
+
+ ConnectorClient connectorClient = context.getServices().Get("ConnectorClient");
+
+ String conversationId = context.getActivity().conversation().id();
+
+ // TODO:
+ //await (connectorClient.conversations().DeleteConversationMemberAsync(conversationId, memberId));
+ return;
+ }
+
+ /**
+ * Lists the members of a given activity.
+ *
+ * @param context The context object for the turn.
+ * @param activityId (Optional) Activity ID to enumerate. If not specified the current activities ID will be used.
+ * @return List of Members of the activity
+ */
+ public CompletableFuture> GetActivityMembers(TurnContextImpl context) {
+ return GetActivityMembers(context, null);
+ }
+
+ public CompletableFuture> GetActivityMembers(TurnContextImpl context, String activityId) {
+ // If no activity was passed in, use the current activity.
+ if (activityId == null)
+ activityId = context.getActivity().id();
+
+ if (context.getActivity().conversation() == null)
+ throw new IllegalArgumentException("BotFrameworkAdapter.GetActivityMembers(): missing conversation");
+
+ if (StringUtils.isEmpty((context.getActivity().conversation().id())))
+ throw new IllegalArgumentException("BotFrameworkAdapter.GetActivityMembers(): missing conversation.id");
+
+ ConnectorClient connectorClient = context.getServices().Get("ConnectorClient");
+ String conversationId = context.getActivity().conversation().id();
+
+ // TODO:
+ //List accounts = await(connectorClient.conversations().GetActivityMembersAsync(conversationId, activityId));
+
+ return completedFuture(null);
+ }
+
+ /**
+ * Lists the members of the current conversation.
+ *
+ * @param context The context object for the turn.
+ * @return List of Members of the current conversation
+ */
+ public CompletableFuture> GetConversationMembers(TurnContextImpl context) {
+ if (context.getActivity().conversation() == null)
+ throw new IllegalArgumentException("BotFrameworkAdapter.GetActivityMembers(): missing conversation");
+
+ if (StringUtils.isEmpty(context.getActivity().conversation().id()))
+ throw new IllegalArgumentException("BotFrameworkAdapter.GetActivityMembers(): missing conversation.id");
+
+ ConnectorClient connectorClient = context.getServices().Get("ConnectorClient");
+ String conversationId = context.getActivity().conversation().id();
+
+ // TODO
+ //List accounts = await(connectorClient.conversations().getConversationMembersAsync(conversationId));
+ return completedFuture(null);
+ }
+
+ /**
+ * Lists the Conversations in which this bot has participated for a given channel server. The
+ * channel server returns results in pages and each page will include a `continuationToken`
+ * that can be used to fetch the next page of results from the server.
+ *
+ * @param serviceUrl The URL of the channel server to query. This can be retrieved
+ * from `context.activity.serviceUrl`.
+ * @param credentials The credentials needed for the Bot to connect to the.services().
+ * @param continuationToken (Optional) token used to fetch the next page of results
+ * from the channel server. This should be left as `null` to retrieve the first page
+ * of results.
+ * @return List of Members of the current conversation
+ *
+ * This overload may be called from outside the context of a conversation, as only the
+ * Bot's ServiceUrl and credentials are required.
+ */
+ public CompletableFuture GetConversations(String serviceUrl, MicrosoftAppCredentials credentials) throws MalformedURLException, URISyntaxException {
+ return GetConversations(serviceUrl, credentials, null);
+ }
+
+ public CompletableFuture GetConversations(String serviceUrl, MicrosoftAppCredentials credentials, String continuationToken) throws MalformedURLException, URISyntaxException {
+ if (StringUtils.isEmpty(serviceUrl))
+ throw new IllegalArgumentException("serviceUrl");
+
+ if (credentials == null)
+ throw new IllegalArgumentException("credentials");
+
+ ConnectorClient connectorClient = this.CreateConnectorClient(serviceUrl, credentials);
+ // TODO
+ //ConversationsResult results = await(connectorClient.conversations().getConversationsAsync(continuationToken));
+ return completedFuture(null);
+ }
+
+ /**
+ * Lists the Conversations in which this bot has participated for a given channel server. The
+ * channel server returns results in pages and each page will include a `continuationToken`
+ * that can be used to fetch the next page of results from the server.
+ *
+ * @param context The context object for the turn.
+ * @param continuationToken (Optional) token used to fetch the next page of results
+ * from the channel server. This should be left as `null` to retrieve the first page
+ * of results.
+ * @return List of Members of the current conversation
+ *
+ * This overload may be called during standard Activity processing, at which point the Bot's
+ * service URL and credentials that are part of the current activity processing pipeline
+ * will be used.
+ */
+ public CompletableFuture GetConversations(TurnContextImpl context) {
+ return GetConversations(context, null);
+ }
+
+ public CompletableFuture GetConversations(TurnContextImpl context, String continuationToken) {
+ ConnectorClient connectorClient = context.getServices().Get("ConnectorClient");
+ // TODO
+ //ConversationsResult results = await(connectorClient.conversations().getConversationsAsync());
+ return completedFuture(null);
+ }
+
+
+ /**
+ * Attempts to retrieve the token for a user that's in a login flow.
+ *
+ * @param context Context for the current turn of conversation with the user.
+ * @param connectionName Name of the auth connection to use.
+ * @param magicCode (Optional) Optional user entered code to validate.
+ * @return Token Response
+ */
+ public CompletableFuture GetUserToken(TurnContextImpl context, String connectionName, String magicCode) {
+ BotAssert.ContextNotNull(context);
+ if (context.getActivity().from() == null || StringUtils.isEmpty(context.getActivity().from().id()))
+ throw new IllegalArgumentException("BotFrameworkAdapter.GetuserToken(): missing from or from.id");
+
+ if (StringUtils.isEmpty(connectionName))
+ throw new IllegalArgumentException("connectionName");
+
+ //OAuthClient client = this.CreateOAuthApiClient(context);
+ //return await(client.GetUserTokenAsync(context.getActivity().from().id(), connectionName, magicCode));
+ return completedFuture(null);
+ }
+
+ /**
+ * 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 connectionName Name of the auth connection to use.
+ * @return
+ */
+ public CompletableFuture GetOauthSignInLink(TurnContextImpl context, String connectionName) {
+ BotAssert.ContextNotNull(context);
+ if (StringUtils.isEmpty(connectionName))
+ throw new IllegalArgumentException("connectionName");
+
+ //OAuthClient client = this.CreateOAuthApiClient(context);
+ //return await(client.GetSignInLinkAsync(context.getActivity(), connectionName));
+ return completedFuture(null);
+ }
+
+ /**
+ * Signs the user out with the token server.
+ *
+ * @param context Context for the current turn of conversation with the user.
+ * @param connectionName Name of the auth connection to use.
+ * @return
+ */
+ public CompletableFuture SignOutUser(TurnContextImpl context, String connectionName) {
+ BotAssert.ContextNotNull(context);
+ if (StringUtils.isEmpty(connectionName))
+ throw new IllegalArgumentException("connectionName");
+
+ //OAuthClient client = this.CreateOAuthApiClient(context);
+ //await(client.SignOutUserAsync(context.Activity.From.Id, connectionName));
+ return completedFuture(null);
+ }
+
+ /**
+ * Creates a conversation on the specified channel.
+ *
+ * @param channelId The ID for the channel.
+ * @param serviceUrl The channel's service URL endpoint.
+ * @param credentials The application credentials for the bot.
+ * @param conversationParameters The conversation information to use to
+ * create the conversation.
+ * @param callback The method to call for the resulting bot turn.
+ * @return A task that represents the work queued to execute.
+ * To start a conversation, your bot must know its account information
+ * and the user's account information on that channel.
+ * Most channels only support initiating a direct message (non-group) conversation.
+ * The adapter attempts to create a new conversation on the channel, and
+ * then sends a {@code conversationUpdate} activity through its middleware pipeline
+ * to the {@code callback} method.
+ * If the conversation is established with the
+ * specified users, the ID of the activity's {@link Activity.Conversation}
+ * will contain the ID of the new conversation.
+ */
+ public CompletableFuture CreateConversation(String channelId, String serviceUrl, MicrosoftAppCredentials
+ credentials, ConversationParameters conversationParameters, Consumer callback) throws Exception {
+ // Validate serviceUrl - can throw
+ URI uri = new URI(serviceUrl);
+ return CompletableFuture.runAsync(() -> {
+ ConnectorClient connectorClient = null;
+ try {
+ connectorClient = this.CreateConnectorClient(serviceUrl, credentials);
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ throw new RuntimeException(String.format("Bad serviceUrl: %s", serviceUrl));
+ } catch (URISyntaxException e) {
+ e.printStackTrace();
+ throw new RuntimeException(String.format("Bad serviceUrl: %s", serviceUrl));
+ }
+
+ Conversations conv = connectorClient.conversations();
+ List results = null;
+ if (conv instanceof RestConversations) {
+ RestConversations convImpl = (RestConversations) conv;
+ results = convImpl.CreateConversationAsync(conversationParameters).join();
+ } else {
+ results = new ArrayList();
+ results.add(conv.createConversation(conversationParameters));
+ }
+ if (results.size() == 1) {
+
+ ConversationResourceResponse result = results.get(0);
+ // Create a conversation update activity to represent the result.
+
+ ConversationUpdateActivity conversationUpdate = (ConversationUpdateActivity) MessageActivity.CreateConversationUpdateActivity()
+ .withChannelId(channelId)
+ .withTopicName(conversationParameters.topicName())
+ .withServiceUrl(serviceUrl)
+ .withMembersAdded(conversationParameters.members())
+ .withId((result.activityId() != null) ? result.activityId() : UUID.randomUUID().toString())
+ .withConversation(new ConversationAccount().withId(result.id()))
+ .withRecipient(conversationParameters.bot());
+
+ try (TurnContextImpl context = new TurnContextImpl(this, conversationUpdate)) {
+ try {
+ this.RunPipeline(context, callback);
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException(String.format("Running pipeline failed : %s", e));
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException(String.format("Turn Context Error: %s", e));
+ }
+ } else {
+ // Should never happen
+ throw new RuntimeException(String.format("Conversations create issue - returned %d conversations", results.size()));
+ }
+ }, ExecutorFactory.getExecutor());
+
+ }
+
+ protected CompletableFuture TrySetEmulatingOAuthCards(TurnContext turnContext) {
+ if (!isEmulatingOAuthCards &&
+ turnContext.getActivity().channelId().equals("emulator") &&
+ (_credentialProvider.isAuthenticationDisabledAsync().join())) {
+ isEmulatingOAuthCards = true;
+ }
+ return completedFuture(isEmulatingOAuthCards);
+
+ }
+
+ protected OAuthClient CreateOAuthApiClient(TurnContext context) throws MalformedURLException, URISyntaxException {
+ RestConnectorClient client = context.getServices().Get("ConnectorClient");
+ if (client == null) {
+ throw new IllegalArgumentException("CreateOAuthApiClient: OAuth requires a valid ConnectorClient instance");
+ }
+ if (isEmulatingOAuthCards) {
+ return new OAuthClient(client, context.getActivity().serviceUrl());
+ }
+ return new OAuthClient(client, AuthenticationConstants.OAUTH_URL);
+ }
+
+ /**
+ * Creates the connector client asynchronous.
+ *
+ * @param serviceUrl The service URL.
+ * @param claimsIdentity The claims identity.
+ * @return ConnectorClient instance.
+ * @throws UnsupportedOperationException ClaimsIdemtity cannot be null. Pass Anonymous ClaimsIdentity if authentication is turned off.
+ */
+ private CompletableFuture CreateConnectorClientAsync(String serviceUrl, ClaimsIdentity claimsIdentity) {
+
+ return CompletableFuture.supplyAsync(() -> {
+ if (claimsIdentity == null) {
+ throw 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 emulator it is in AppId claim. For
+ // unauthenticated requests we have anonymous identity provided auth is disabled.
+ if (claimsIdentity.claims() == null) {
+ try {
+ return CreateConnectorClient(serviceUrl);
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ throw new RuntimeException(String.format("Invalid Service URL: %s", serviceUrl));
+ } catch (URISyntaxException e) {
+ e.printStackTrace();
+ throw new RuntimeException(String.format("Invalid Service URL: %s", serviceUrl));
+ }
+ }
+
+ // For Activities coming from Emulator AppId claim contains the Bot's AAD AppId.
+ // For anonymous requests (requests with no header) appId is not set in claims.
+
+ Map.Entry botAppIdClaim = claimsIdentity.claims().entrySet().stream()
+ .filter(claim -> claim.getKey() == AuthenticationConstants.AUDIENCE_CLAIM)
+ .findFirst()
+ .orElse(null);
+ if (botAppIdClaim == null) {
+ botAppIdClaim = claimsIdentity.claims().entrySet().stream()
+ .filter(claim -> claim.getKey() == AuthenticationConstants.APPID_CLAIM)
+ .findFirst()
+ .orElse(null);
+ }
+
+ if (botAppIdClaim != null) {
+ String botId = botAppIdClaim.getValue();
+ MicrosoftAppCredentials appCredentials = this.GetAppCredentialsAsync(botId).join();
+ try {
+ return this.CreateConnectorClient(serviceUrl, appCredentials);
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ throw new RuntimeException(String.format("Bad Service URL: %s", serviceUrl));
+ } catch (URISyntaxException e) {
+ e.printStackTrace();
+ throw new RuntimeException(String.format("Bad Service URL: %s", serviceUrl));
+ }
+ } else {
+ try {
+ return this.CreateConnectorClient(serviceUrl);
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ throw new RuntimeException(String.format("Bad Service URL: %s", serviceUrl));
+ } catch (URISyntaxException e) {
+ e.printStackTrace();
+ throw new RuntimeException(String.format("Bad Service URL: %s", serviceUrl));
+ }
+ }
+ }, ExecutorFactory.getExecutor());
+
+ }
+
+ /**
+ * Creates the connector client.
+ *
+ * @param serviceUrl The service URL.
+ * @param appCredentials The application credentials for the bot.
+ * @return Connector client instance.
+ */
+ private ConnectorClient CreateConnectorClient(String serviceUrl) throws MalformedURLException, URISyntaxException {
+ return CreateConnectorClient(serviceUrl, null);
+ }
+
+ private ConnectorClient CreateConnectorClient(String serviceUrl, MicrosoftAppCredentials appCredentials) throws MalformedURLException, URISyntaxException {
+ RestConnectorClient connectorClient = null;
+ if (appCredentials != null) {
+ connectorClient = new RestConnectorClient(new URI(serviceUrl).toURL().toString(), appCredentials);
+ }
+ // TODO: Constructor necessary?
+// else {
+//
+// connectorClient = new ConnectorClientImpl(new URI(serviceUrl).toURL().toString());
+// }
+
+ if (this.connectorClientRetryStrategy != null)
+ connectorClient.withRestRetryStrategy(this.connectorClientRetryStrategy);
+
+
+ return connectorClient;
+
+ }
+
+ /**
+ * Gets the application credentials. App Credentials are cached so as to ensure we are not refreshing
+ * token everytime.
+ *
+ * @param appId The application identifier (AAD Id for the bot).
+ * @return App credentials.
+ */
+ private CompletableFuture GetAppCredentialsAsync(String appId) {
+ CompletableFuture result = CompletableFuture.supplyAsync(() -> {
+ if (appId == null) {
+ return MicrosoftAppCredentials.empty();
+ }
+ if (this.appCredentialMap.containsKey(appId))
+ return this.appCredentialMap.get(appId);
+ String appPassword = this._credentialProvider.getAppPasswordAsync(appId).join();
+ MicrosoftAppCredentials appCredentials = new MicrosoftAppCredentials(appId, appPassword);
+ this.appCredentialMap.put(appId, appCredentials);
+ return appCredentials;
+
+ }, ExecutorFactory.getExecutor());
+ return result;
+ }
+
+}
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 cf36ef092..6ddbde3da 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
@@ -1,310 +1,300 @@
-package com.microsoft.bot.builder;
-
-
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-
-import com.microsoft.bot.schema.models.Activity;
-import org.joda.time.DateTime;
-
-import java.time.Instant;
-import java.time.OffsetDateTime;
-import java.time.ZoneId;
-import java.time.ZoneOffset;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.ForkJoinPool;
-import java.util.concurrent.ForkJoinWorkerThread;
-import java.util.function.Function;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-
-/**
- * The memory transcript store stores transcripts in volatile memory in a Dictionary.
- *
- *
- * Because this uses an unbounded volitile dictionary this should only be used for unit tests or non-production environments.
- */
-public class MemoryTranscriptStore implements TranscriptStore {
- private HashMap>> channels = new HashMap>>();
- final ForkJoinPool.ForkJoinWorkerThreadFactory factory = new ForkJoinPool.ForkJoinWorkerThreadFactory() {
- @Override
- public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
- final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
- worker.setName("TestFlow-" + worker.getPoolIndex());
- return worker;
- }
- };
-
- final ExecutorService executor = new ForkJoinPool(Runtime.getRuntime().availableProcessors(), factory, null, false);
-
-
- /**
- * Logs an activity to the transcript.
- *
- * @param activity The activity to log.
- * @return A CompletableFuture that represents the work queued to execute.
- */
- public final void LogActivityAsync(Activity activity) {
- if (activity == null) {
- throw new NullPointerException("activity cannot be null for LogActivity()");
- }
-
- synchronized (this.channels) {
- HashMap> channel;
- if (!this.channels.containsKey(activity.channelId())) {
- channel = new HashMap>();
- this.channels.put(activity.channelId(), channel);
- } else {
- channel = this.channels.get(activity.channelId());
- }
-
- ArrayList transcript = null;
-
-
- if (!channel.containsKey(activity.conversation().id())) {
- transcript = new ArrayList();
- channel.put(activity.conversation().id(), transcript);
- } else {
- transcript = channel.get(activity.conversation().id());
- }
-
- transcript.add(activity);
- }
-
- }
-
- /**
- * Gets from the store activities that match a set of criteria.
- *
- * @param channelId The ID of the channel the conversation is in.
- * @param conversationId The ID of the conversation.
- * @param continuationToken
- * @return A task that represents the work queued to execute.
- * If the task completes successfully, the result contains the matching activities.
- */
-
- public final CompletableFuture> GetTranscriptActivitiesAsync(String channelId, String conversationId, String continuationToken) {
- return GetTranscriptActivitiesAsync(channelId, conversationId, continuationToken, null);
- }
-
- /**
- * Gets from the store activities that match a set of criteria.
- *
- * @param channelId The ID of the channel the conversation is in.
- * @param conversationId The ID of the conversation.
- * @return A task that represents the work queued to execute.
- * If the task completes successfully, the result contains the matching activities.
- */
-
- public final CompletableFuture> GetTranscriptActivitiesAsync(String channelId, String conversationId) {
- return GetTranscriptActivitiesAsync(channelId, conversationId, null, null);
- }
-
- /**
- * Gets from the store activities that match a set of criteria.
- *
- * @param channelId The ID of the channel the conversation is in.
- * @param conversationId The ID of the conversation.
- * @param continuationToken
- * @param startDate A cutoff date. Activities older than this date are not included.
- * @return A task that represents the work queued to execute.
- * If the task completes successfully, the result contains the matching activities.
- */
- public final CompletableFuture> GetTranscriptActivitiesAsync(String channelId, String conversationId, String continuationToken, DateTime startDate) {
- return CompletableFuture.supplyAsync(() -> {
- if (channelId == null) {
- throw new NullPointerException(String.format("missing %1$s", "channelId"));
- }
-
- if (conversationId == null) {
- throw new NullPointerException(String.format("missing %1$s", "conversationId"));
- }
-
- PagedResult pagedResult = new PagedResult();
- synchronized (channels) {
- HashMap> channel;
- if (!channels.containsKey(channelId)) {
- return pagedResult;
- }
- channel = channels.get(channelId);
- ArrayList transcript;
-
- if (!channel.containsKey(conversationId)) {
- return pagedResult;
- }
- transcript = channel.get(conversationId);
- if (continuationToken != null) {
- List items = transcript.stream()
- .sorted(Comparator.comparing(Activity::timestamp))
- .filter(a -> a.timestamp().compareTo(startDate) >= 0)
- .filter(skipwhile(a -> !a.id().equals(continuationToken)))
- .skip(1)
- .limit(20)
- .collect(Collectors.toList());
-
- pagedResult.items(items.toArray(new Activity[items.size()]));
-
- if (pagedResult.getItems().length == 20) {
- pagedResult.withContinuationToken(items.get(items.size() - 1).id());
- }
- } else {
- List items = transcript.stream()
- .sorted(Comparator.comparing(Activity::timestamp))
- .filter(a -> a.timestamp().compareTo((startDate == null) ? new DateTime(Long.MIN_VALUE) : startDate) >= 0)
- .limit(20)
- .collect(Collectors.toList());
- pagedResult.items(items.toArray(new Activity[items.size()]));
- if (items.size() == 20) {
- pagedResult.withContinuationToken(items.get(items.size() - 1).id());
- }
- }
- }
-
- return pagedResult;
-
- }, this.executor);
- }
-
- /**
- * Deletes conversation data from the store.
- *
- * @param channelId The ID of the channel the conversation is in.
- * @param conversationId The ID of the conversation to delete.
- * @return A task that represents the work queued to execute.
- */
- public final CompletableFuture DeleteTranscriptAsync(String channelId, String conversationId) {
- return CompletableFuture.runAsync(() -> {
- if (channelId == null) {
- throw new NullPointerException(String.format("%1$s should not be null", "channelId"));
- }
-
- if (conversationId == null) {
- throw new NullPointerException(String.format("%1$s should not be null", "conversationId"));
- }
-
- synchronized (this.channels) {
- if (!this.channels.containsKey(channelId)) {
- return;
- }
- HashMap> channel = this.channels.get(channelId);
- if (channel.containsKey(conversationId)) {
- channel.remove(conversationId);
- }
- }
- }, this.executor);
- }
-
- /**
- * Gets the conversations on a channel from the store.
- *
- * @param channelId The ID of the channel.
- * @return A task that represents the work queued to execute.
- */
-
- public final CompletableFuture> ListTranscriptsAsync(String channelId) {
- return ListTranscriptsAsync(channelId, null);
- }
-
- /**
- * Gets the conversations on a channel from the store.
- *
- * @param channelId The ID of the channel.
- * @param continuationToken
- * @return A task that represents the work queued to execute.
- */
-
- public final CompletableFuture> ListTranscriptsAsync(String channelId, String continuationToken) {
- return CompletableFuture.supplyAsync(() -> {
- if (channelId == null) {
- throw new NullPointerException(String.format("missing %1$s", "channelId"));
- }
-
- PagedResult pagedResult = new PagedResult();
- synchronized (channels) {
-
- if (!channels.containsKey(channelId)) {
- return pagedResult;
- }
-
- HashMap> channel = channels.get(channelId);
- if (continuationToken != null) {
- List items = channel.entrySet().stream()
- .map(c -> {
- OffsetDateTime offsetDateTime = null;
- if (c.getValue().stream().findFirst().isPresent()) {
- DateTime dt = c.getValue().stream().findFirst().get().timestamp();
- // convert to DateTime to OffsetDateTime
- Instant instant = Instant.ofEpochMilli(dt.getMillis());
- ZoneOffset offset = ZoneId.of(dt.getZone().getID()).getRules().getOffset(instant);
- offsetDateTime = instant.atOffset(offset);
- } else {
- offsetDateTime = OffsetDateTime.now();
- }
- return new Transcript()
- .withChannelId(channelId)
- .withId(c.getKey())
- .withCreated(offsetDateTime);
- }
- )
- .sorted(Comparator.comparing(Transcript::getCreated))
- .filter(skipwhile(c -> !c.getId().equals(continuationToken)))
- .skip(1)
- .limit(20)
- .collect(Collectors.toList());
- pagedResult.items(items.toArray(new Transcript[items.size()]));
- if (items.size() == 20) {
- pagedResult.withContinuationToken(items.get(items.size() - 1).getId());
- }
- } else {
-
- List items = channel.entrySet().stream()
- .map(c -> {
- OffsetDateTime offsetDateTime = null;
- if (c.getValue().stream().findFirst().isPresent()) {
- DateTime dt = c.getValue().stream().findFirst().get().timestamp();
- // convert to DateTime to OffsetDateTime
- Instant instant = Instant.ofEpochMilli(dt.getMillis());
- ZoneOffset offset = ZoneId.of(dt.getZone().getID()).getRules().getOffset(instant);
- offsetDateTime = instant.atOffset(offset);
- } else {
- offsetDateTime = OffsetDateTime.now();
- }
- return new Transcript()
- .withChannelId(channelId)
- .withId(c.getKey())
- .withCreated(offsetDateTime);
- }
- )
- .sorted(Comparator.comparing(Transcript::getCreated))
- .limit(20)
- .collect(Collectors.toList());
- pagedResult.items(items.toArray(new Transcript[items.size()]));
- if (items.size() == 20) {
- pagedResult.withContinuationToken(items.get(items.size() - 1).getId());
- }
- }
- }
- return pagedResult;
- }, this.executor);
- }
-
- /**
- * Emulate C# SkipWhile.
- * Stateful
- *
- * @param func1 predicate to apply
- * @param type
- * @return if the predicate condition is true
- */
- public static Predicate skipwhile(Function super T, Object> func1) {
- final boolean[] started = {false};
- return t -> started[0] || (started[0] = (boolean) func1.apply(t));
- }
-
-}
\ No newline at end of file
+package com.microsoft.bot.builder;
+
+
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+
+import com.microsoft.bot.connector.ExecutorFactory;
+import com.microsoft.bot.schema.models.Activity;
+import org.joda.time.DateTime;
+
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.ForkJoinWorkerThread;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/**
+ * The memory transcript store stores transcripts in volatile memory in a Dictionary.
+ *
+ *
+ * Because this uses an unbounded volitile dictionary this should only be used for unit tests or non-production environments.
+ */
+public class MemoryTranscriptStore implements TranscriptStore {
+ private HashMap>> channels = new HashMap>>();
+
+ /**
+ * Logs an activity to the transcript.
+ *
+ * @param activity The activity to log.
+ * @return A CompletableFuture that represents the work queued to execute.
+ */
+ public final void LogActivityAsync(Activity activity) {
+ if (activity == null) {
+ throw new NullPointerException("activity cannot be null for LogActivity()");
+ }
+
+ synchronized (this.channels) {
+ HashMap> channel;
+ if (!this.channels.containsKey(activity.channelId())) {
+ channel = new HashMap>();
+ this.channels.put(activity.channelId(), channel);
+ } else {
+ channel = this.channels.get(activity.channelId());
+ }
+
+ ArrayList transcript = null;
+
+
+ if (!channel.containsKey(activity.conversation().id())) {
+ transcript = new ArrayList();
+ channel.put(activity.conversation().id(), transcript);
+ } else {
+ transcript = channel.get(activity.conversation().id());
+ }
+
+ transcript.add(activity);
+ }
+
+ }
+
+ /**
+ * Gets from the store activities that match a set of criteria.
+ *
+ * @param channelId The ID of the channel the conversation is in.
+ * @param conversationId The ID of the conversation.
+ * @param continuationToken
+ * @return A task that represents the work queued to execute.
+ * If the task completes successfully, the result contains the matching activities.
+ */
+
+ public final CompletableFuture> GetTranscriptActivitiesAsync(String channelId, String conversationId, String continuationToken) {
+ return GetTranscriptActivitiesAsync(channelId, conversationId, continuationToken, null);
+ }
+
+ /**
+ * Gets from the store activities that match a set of criteria.
+ *
+ * @param channelId The ID of the channel the conversation is in.
+ * @param conversationId The ID of the conversation.
+ * @return A task that represents the work queued to execute.
+ * If the task completes successfully, the result contains the matching activities.
+ */
+
+ public final CompletableFuture> GetTranscriptActivitiesAsync(String channelId, String conversationId) {
+ return GetTranscriptActivitiesAsync(channelId, conversationId, null, null);
+ }
+
+ /**
+ * Gets from the store activities that match a set of criteria.
+ *
+ * @param channelId The ID of the channel the conversation is in.
+ * @param conversationId The ID of the conversation.
+ * @param continuationToken
+ * @param startDate A cutoff date. Activities older than this date are not included.
+ * @return A task that represents the work queued to execute.
+ * If the task completes successfully, the result contains the matching activities.
+ */
+ public final CompletableFuture> GetTranscriptActivitiesAsync(String channelId, String conversationId, String continuationToken, DateTime startDate) {
+ return CompletableFuture.supplyAsync(() -> {
+ if (channelId == null) {
+ throw new NullPointerException(String.format("missing %1$s", "channelId"));
+ }
+
+ if (conversationId == null) {
+ throw new NullPointerException(String.format("missing %1$s", "conversationId"));
+ }
+
+ PagedResult pagedResult = new PagedResult();
+ synchronized (channels) {
+ HashMap> channel;
+ if (!channels.containsKey(channelId)) {
+ return pagedResult;
+ }
+ channel = channels.get(channelId);
+ ArrayList transcript;
+
+ if (!channel.containsKey(conversationId)) {
+ return pagedResult;
+ }
+ transcript = channel.get(conversationId);
+ if (continuationToken != null) {
+ List items = transcript.stream()
+ .sorted(Comparator.comparing(Activity::timestamp))
+ .filter(a -> a.timestamp().compareTo(startDate) >= 0)
+ .filter(skipwhile(a -> !a.id().equals(continuationToken)))
+ .skip(1)
+ .limit(20)
+ .collect(Collectors.toList());
+
+ pagedResult.items(items.toArray(new Activity[items.size()]));
+
+ if (pagedResult.getItems().length == 20) {
+ pagedResult.withContinuationToken(items.get(items.size() - 1).id());
+ }
+ } else {
+ List items = transcript.stream()
+ .sorted(Comparator.comparing(Activity::timestamp))
+ .filter(a -> a.timestamp().compareTo((startDate == null) ? new DateTime(Long.MIN_VALUE) : startDate) >= 0)
+ .limit(20)
+ .collect(Collectors.toList());
+ pagedResult.items(items.toArray(new Activity[items.size()]));
+ if (items.size() == 20) {
+ pagedResult.withContinuationToken(items.get(items.size() - 1).id());
+ }
+ }
+ }
+
+ return pagedResult;
+
+ }, ExecutorFactory.getExecutor());
+ }
+
+ /**
+ * Deletes conversation data from the store.
+ *
+ * @param channelId The ID of the channel the conversation is in.
+ * @param conversationId The ID of the conversation to delete.
+ * @return A task that represents the work queued to execute.
+ */
+ public final CompletableFuture DeleteTranscriptAsync(String channelId, String conversationId) {
+ return CompletableFuture.runAsync(() -> {
+ if (channelId == null) {
+ throw new NullPointerException(String.format("%1$s should not be null", "channelId"));
+ }
+
+ if (conversationId == null) {
+ throw new NullPointerException(String.format("%1$s should not be null", "conversationId"));
+ }
+
+ synchronized (this.channels) {
+ if (!this.channels.containsKey(channelId)) {
+ return;
+ }
+ HashMap> channel = this.channels.get(channelId);
+ if (channel.containsKey(conversationId)) {
+ channel.remove(conversationId);
+ }
+ }
+ }, ExecutorFactory.getExecutor());
+ }
+
+ /**
+ * Gets the conversations on a channel from the store.
+ *
+ * @param channelId The ID of the channel.
+ * @return A task that represents the work queued to execute.
+ */
+
+ public final CompletableFuture> ListTranscriptsAsync(String channelId) {
+ return ListTranscriptsAsync(channelId, null);
+ }
+
+ /**
+ * Gets the conversations on a channel from the store.
+ *
+ * @param channelId The ID of the channel.
+ * @param continuationToken
+ * @return A task that represents the work queued to execute.
+ */
+
+ public final CompletableFuture> ListTranscriptsAsync(String channelId, String continuationToken) {
+ return CompletableFuture.supplyAsync(() -> {
+ if (channelId == null) {
+ throw new NullPointerException(String.format("missing %1$s", "channelId"));
+ }
+
+ PagedResult pagedResult = new PagedResult();
+ synchronized (channels) {
+
+ if (!channels.containsKey(channelId)) {
+ return pagedResult;
+ }
+
+ HashMap> channel = channels.get(channelId);
+ if (continuationToken != null) {
+ List items = channel.entrySet().stream()
+ .map(c -> {
+ OffsetDateTime offsetDateTime = null;
+ if (c.getValue().stream().findFirst().isPresent()) {
+ DateTime dt = c.getValue().stream().findFirst().get().timestamp();
+ // convert to DateTime to OffsetDateTime
+ Instant instant = Instant.ofEpochMilli(dt.getMillis());
+ ZoneOffset offset = ZoneId.of(dt.getZone().getID()).getRules().getOffset(instant);
+ offsetDateTime = instant.atOffset(offset);
+ } else {
+ offsetDateTime = OffsetDateTime.now();
+ }
+ return new Transcript()
+ .withChannelId(channelId)
+ .withId(c.getKey())
+ .withCreated(offsetDateTime);
+ }
+ )
+ .sorted(Comparator.comparing(Transcript::getCreated))
+ .filter(skipwhile(c -> !c.getId().equals(continuationToken)))
+ .skip(1)
+ .limit(20)
+ .collect(Collectors.toList());
+ pagedResult.items(items.toArray(new Transcript[items.size()]));
+ if (items.size() == 20) {
+ pagedResult.withContinuationToken(items.get(items.size() - 1).getId());
+ }
+ } else {
+
+ List items = channel.entrySet().stream()
+ .map(c -> {
+ OffsetDateTime offsetDateTime = null;
+ if (c.getValue().stream().findFirst().isPresent()) {
+ DateTime dt = c.getValue().stream().findFirst().get().timestamp();
+ // convert to DateTime to OffsetDateTime
+ Instant instant = Instant.ofEpochMilli(dt.getMillis());
+ ZoneOffset offset = ZoneId.of(dt.getZone().getID()).getRules().getOffset(instant);
+ offsetDateTime = instant.atOffset(offset);
+ } else {
+ offsetDateTime = OffsetDateTime.now();
+ }
+ return new Transcript()
+ .withChannelId(channelId)
+ .withId(c.getKey())
+ .withCreated(offsetDateTime);
+ }
+ )
+ .sorted(Comparator.comparing(Transcript::getCreated))
+ .limit(20)
+ .collect(Collectors.toList());
+ pagedResult.items(items.toArray(new Transcript[items.size()]));
+ if (items.size() == 20) {
+ pagedResult.withContinuationToken(items.get(items.size() - 1).getId());
+ }
+ }
+ }
+ return pagedResult;
+ }, ExecutorFactory.getExecutor());
+ }
+
+ /**
+ * Emulate C# SkipWhile.
+ * Stateful
+ *
+ * @param func1 predicate to apply
+ * @param type
+ * @return if the predicate condition is true
+ */
+ public static Predicate skipwhile(Function super T, Object> func1) {
+ final boolean[] started = {false};
+ return t -> started[0] || (started[0] = (boolean) func1.apply(t));
+ }
+
+}
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 c955db952..315248a0e 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
@@ -1,615 +1,602 @@
-package com.microsoft.bot.builder;
-
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-import com.microsoft.bot.schema.ActivityImpl;
-import com.microsoft.bot.schema.models.Activity;
-import com.microsoft.bot.schema.models.ConversationReference;
-import com.microsoft.bot.schema.models.InputHints;
-import com.microsoft.bot.schema.models.ResourceResponse;
-import org.apache.commons.lang3.StringUtils;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-import java.util.concurrent.*;
-
-import static com.microsoft.bot.schema.models.ActivityTypes.MESSAGE;
-import static com.microsoft.bot.schema.models.ActivityTypes.TRACE;
-import static java.util.stream.Collectors.toList;
-
-/**
- * Provides context for a turn of a bot.
- * Context provides information needed to process an incoming activity.
- * The context object is created by a {@link BotAdapter} and persists for the
- * length of the turn.
- * {@linkalso Bot}
- * {@linkalso Middleware}
- */
-public class TurnContextImpl implements TurnContext, AutoCloseable {
- private final BotAdapter adapter;
- private final ActivityImpl activity;
- private Boolean responded = false;
-
- private final List onSendActivities = new ArrayList();
- private final List onUpdateActivity = new ArrayList();
- private final List onDeleteActivity = new ArrayList();
-
- private final TurnContextServiceCollection turnServices;
- ForkJoinPool.ForkJoinWorkerThreadFactory factory = new ForkJoinPool.ForkJoinWorkerThreadFactory()
- {
- @Override
- public ForkJoinWorkerThread newThread(ForkJoinPool pool)
- {
- final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
- worker.setName("TestFlow-" + worker.getPoolIndex());
- return worker;
- }
- };
-
- ExecutorService executor = new ForkJoinPool(Runtime.getRuntime().availableProcessors(), factory, null, true);
-
-
-
- /**
- * Creates a context object.
- *
- * @param adapter The adapter creating the context.
- * @param activity The incoming activity for the turn;
- * or {@code null} for a turn for a proactive message.
- * @throws IllegalArgumentException {@code activity} or
- * {@code adapter} is {@code null}.
- * For use by bot adapter implementations only.
- */
- public TurnContextImpl(BotAdapter adapter, ActivityImpl activity) {
- if (adapter == null)
- throw new IllegalArgumentException("adapter");
- this.adapter = adapter;
- if (activity == null)
- throw new IllegalArgumentException("activity");
- this.activity = activity;
-
- turnServices = new TurnContextServiceCollectionImpl();
- }
-
-
- /**
- * Adds a response handler for send activity operations.
- *
- * @param handler The handler to add to the context object.
- * @return The updated context object.
- * @throws IllegalArgumentException {@code handler} is {@code null}.
- * When the context's {@link SendActivity(Activity)}
- * or {@link SendActivities(Activity[])} methods are called,
- * the adapter calls the registered handlers in the order in which they were
- * added to the context object.
- */
- public TurnContextImpl OnSendActivities(SendActivitiesHandler handler) {
- if (handler == null)
- throw new IllegalArgumentException("handler");
-
- this.onSendActivities.add(handler);
- return this;
- }
-
- /**
- * Adds a response handler for update activity operations.
- *
- * @param handler The handler to add to the context object.
- * @return The updated context object.
- * @throws IllegalArgumentException {@code handler} is {@code null}.
- * When the context's {@link UpdateActivity(Activity)} is called,
- * the adapter calls the registered handlers in the order in which they were
- * added to the context object.
- */
- public TurnContextImpl OnUpdateActivity(UpdateActivityHandler handler) {
- if (handler == null)
- throw new IllegalArgumentException("handler");
-
- this.onUpdateActivity.add(handler);
- return this;
- }
-
- /**
- * Adds a response handler for delete activity operations.
- *
- * @param handler The handler to add to the context object.
- * @return The updated context object.
- * @throws IllegalArgumentException {@code handler} is {@code null}.
- * When the context's {@link DeleteActivity(string)} is called,
- * the adapter calls the registered handlers in the order in which they were
- * added to the context object.
- */
- public TurnContextImpl OnDeleteActivity(DeleteActivityHandler handler) {
- if (handler == null)
- throw new IllegalArgumentException("handler");
-
- this.onDeleteActivity.add(handler);
- return this;
- }
-
- /**
- * Gets the bot adapter that created this context object.
- */
- public BotAdapter getAdapter() {
- return this.adapter;
- }
-
- /**
- * Gets the services registered on this context object.
- */
- public TurnContextServiceCollection getServices() {
- return this.turnServices;
- }
-
- /**
- * Gets the activity associated with this turn; or {@code null} when processing
- * a proactive message.
- */
- @Override
- public Activity getActivity() {
- return this.activity;
- }
-
- /**
- * Indicates whether at least one response was sent for the current turn.
- *
- * @return {@code true} if at least one response was sent for the current turn.
- * @throws IllegalArgumentException You attempted to set the value to {@code false}.
- */
- public boolean getResponded() {
- return this.responded;
- }
-
- public void setResponded(boolean responded) {
- if (responded == false) {
- throw new IllegalArgumentException("TurnContext: cannot set 'responded' to a value of 'false'.");
- }
- this.responded = true;
- }
-
- /**
- * Sends a message activity to the sender of the incoming activity.
- *
- * @param textReplyToSend The text of the message to send.
- * @param speak Optional, text to be spoken by your bot on a speech-enabled
- * channel.
- * @param inputHint Optional, indicates whether your bot is accepting,
- * expecting, or ignoring user input after the message is delivered to the client.
- * One of: "acceptingInput", "ignoringInput", or "expectingInput".
- * Default is null.
- * @return A task that represents the work queued to execute.
- * @throws IllegalArgumentException {@code textReplyToSend} is {@code null} or whitespace.
- * If the activity is successfully sent, the task result contains
- * a {@link ResourceResponse} object containing the ID that the receiving
- * channel assigned to the activity.
- * See the channel's documentation for limits imposed upon the contents of
- * {@code textReplyToSend}.
- * To control various characteristics of your bot's speech such as voice,
- * rate, volume, pronunciation, and pitch, specify {@code speak} in
- * Speech Synthesis Markup Language (SSML) format.
- */
- @Override
- public ResourceResponse SendActivity(String textReplyToSend) throws Exception {
- return SendActivity(textReplyToSend, null, null);
- }
-
- @Override
- public ResourceResponse SendActivity(String textReplyToSend, String speak) throws Exception {
- return SendActivity(textReplyToSend, speak, null);
- }
-
- @Override
- public ResourceResponse SendActivity(String textReplyToSend, String speak, String inputHint) throws Exception {
- if (StringUtils.isEmpty(textReplyToSend))
- throw new IllegalArgumentException("textReplyToSend");
-
- ActivityImpl activityToSend = (ActivityImpl) new ActivityImpl()
- .withType(MESSAGE)
- .withText(textReplyToSend);
- if (speak != null)
- activityToSend.withSpeak(speak);
-
- if (StringUtils.isNotEmpty(inputHint))
- activityToSend.withInputHint(InputHints.fromString(inputHint));
-
- return SendActivity(activityToSend);
- }
-
- /**
- * Sends an activity to the sender of the incoming activity.
- *
- * @param activity The activity to send.
- * @return A task that represents the work queued to execute.
- * @throws IllegalArgumentException {@code activity} is {@code null}.
- * If the activity is successfully sent, the task result contains
- * a {@link ResourceResponse} object containing the ID that the receiving
- * channel assigned to the activity.
- */
- @Override
- public ResourceResponse SendActivity(Activity activity) throws Exception {
- if (activity == null)
- throw new IllegalArgumentException("activity");
-
- System.out.printf("In SENDEACTIVITYASYNC:");
- System.out.flush();
- Activity[] activities = {activity};
- ResourceResponse[] responses;
- try {
- responses = SendActivities(activities);
- } catch (Exception e) {
- e.printStackTrace();
- throw new RuntimeException(String.format("TurnContext:SendActivity fail %s", e.toString()));
- }
- if (responses == null || responses.length == 0) {
- // It's possible an interceptor prevented the activity from having been sent.
- // Just return an empty response in that case.
- return null;
- } else {
- return responses[0];
- }
-
- }
-
- /**
- * Sends a set of activities to the sender of the incoming activity.
- *
- * @param activities The activities to send.
- * @return A task that represents the work queued to execute.
- * If the activities are successfully sent, the task result contains
- * an array of {@link ResourceResponse} objects containing the IDs that
- * the receiving channel assigned to the activities.
- */
- @Override
- public ResourceResponse[] SendActivities(Activity[] activities) throws Exception {
- // Bind the relevant Conversation Reference properties, such as URLs and
- // ChannelId's, to the activities we're about to send.
- ConversationReference cr = GetConversationReference(this.activity);
- for (Activity a : activities) {
- ApplyConversationReference(a, cr);
- }
-
- // Convert the IActivities to Activies.
- // Activity[] activityArray = Array.ConvertAll(activities, (input) => (Activity)input);
- List activityArray = Arrays.stream(activities).map(input -> (Activity) input).collect(toList());
-
-
- // Create the list used by the recursive methods.
- List activityList = new ArrayList(activityArray);
-
- Callable ActuallySendStuff = () -> {
- // Are the any non-trace activities to send?
- // The thinking here is that a Trace event isn't user relevant data
- // so the "Responded" flag should not be set by Trace messages being
- // sent out.
- boolean sentNonTraceActivities = false;
- if (!activityList.stream().anyMatch((a) -> a.type() == TRACE)) {
- sentNonTraceActivities = true;
- }
- // Send from the list, which may have been manipulated via the event handlers.
- // Note that 'responses' was captured from the root of the call, and will be
- // returned to the original caller.
- ResourceResponse[] responses = new ResourceResponse[0];
- responses = this.getAdapter().SendActivities(this, activityList.toArray(new ActivityImpl[activityList.size()]));
- if (responses != null && responses.length == activityList.size()) {
- // stitch up activity ids
- for (int i = 0; i < responses.length; i++) {
- ResourceResponse response = responses[i];
- Activity activity = activityList.get(i);
- activity.withId(response.id());
- }
- }
-
- // If we actually sent something (that's not Trace), set the flag.
- if (sentNonTraceActivities) {
- this.setResponded(true);
- }
- return responses;
- };
-
- List act_list = new ArrayList<>(activityList);
- return SendActivitiesInternal(act_list, onSendActivities.iterator(), ActuallySendStuff);
- }
-
- /**
- * Replaces an existing activity.
- *
- * @param activity New replacement activity.
- * @return A task that represents the work queued to execute.
- * @throws Microsoft.Bot.Schema.ErrorResponseException The HTTP operation failed and the response contained additional information.
- * @throws System.AggregateException One or more exceptions occurred during the operation.
- * If the activity is successfully sent, the task result contains
- * a {@link ResourceResponse} object containing the ID that the receiving
- * channel assigned to the activity.
- * Before calling this, set the ID of the replacement activity to the ID
- * of the activity to replace.
- */
- @Override
- public ResourceResponse UpdateActivity(Activity activity) throws Exception {
-
-
- Callable ActuallyUpdateStuff = () -> {
- return this.getAdapter().UpdateActivity(this, activity);
- };
-
- return UpdateActivityInternal(activity, onUpdateActivity.iterator(), ActuallyUpdateStuff);
- }
-
-
-
- /**
- * Deletes an existing activity.
- *
- * @param activityId The ID of the activity to delete.
- * @return A task that represents the work queued to execute.
- * @throws Exception The HTTP operation failed and the response contained additional information.
- */
- public CompletableFuture DeleteActivity(String activityId) throws Exception {
- if (StringUtils.isWhitespace(activityId) || activityId == null)
- throw new IllegalArgumentException("activityId");
-
- return CompletableFuture.runAsync(() -> {
- ConversationReference cr = this.GetConversationReference(this.getActivity());
- cr.withActivityId(activityId);
-
- Runnable ActuallyDeleteStuff = () -> {
- try {
- this.getAdapter().DeleteActivity(this, cr);
- } catch (ExecutionException e) {
- e.printStackTrace();
- throw new RuntimeException(String.format("Failed to delete activity %s", e.toString()));
- } catch (InterruptedException e) {
- e.printStackTrace();
- throw new RuntimeException(String.format("Failed to delete activity %s", e.toString()));
- }
- return;
- };
-
- try {
- DeleteActivityInternal(cr, onDeleteActivity.iterator(), ActuallyDeleteStuff);
- } catch (Exception e) {
- e.printStackTrace();
- throw new RuntimeException(String.format("Failed to delete activity %s", e.getMessage()));
- }
- return;
-
- }, executor);
-
- }
-
- /**
- * Deletes an existing activity.
- *
- * @param conversationReference The conversation containing the activity to delete.
- * @return A task that represents the work queued to execute.
- * @throws Microsoft.Bot.Schema.ErrorResponseException The HTTP operation failed and the response contained additional information.
- * The conversation reference's {@link ConversationReference.ActivityId}
- * indicates the activity in the conversation to delete.
- */
- public void DeleteActivity(ConversationReference conversationReference) throws Exception {
- if (conversationReference == null)
- throw new IllegalArgumentException("conversationReference");
-
- Runnable ActuallyDeleteStuff = () -> {
- try {
- this.getAdapter().DeleteActivity(this, conversationReference);
- return;
- } catch (ExecutionException e) {
- e.printStackTrace();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- throw new RuntimeException("DeleteActivity failed");
- };
-
- DeleteActivityInternal(conversationReference, onDeleteActivity.iterator(), ActuallyDeleteStuff);
- return ;
- }
-
- private ResourceResponse[] SendActivitiesInternal(List activities, Iterator sendHandlers, Callable callAtBottom) throws Exception {
- if (activities == null)
- throw new IllegalArgumentException("activities");
- if (sendHandlers == null)
- throw new IllegalArgumentException("sendHandlers");
-
- if (false == sendHandlers.hasNext()) { // No middleware to run.
- if (callAtBottom != null)
- return callAtBottom.call();
- return new ResourceResponse[0];
- }
-
- // Default to "No more Middleware after this".
- Callable next = () -> {
- // Remove the first item from the list of middleware to call,
- // so that the next call just has the remaining items to worry about.
- //Iterable remaining = sendHandlers.Skip(1);
- //Iterator remaining = sendHandlers.iterator();
- if (sendHandlers.hasNext())
- sendHandlers.next();
- return SendActivitiesInternal(activities, sendHandlers, callAtBottom);
- };
-
- // Grab the current middleware, which is the 1st element in the array, and execute it
- SendActivitiesHandler caller = sendHandlers.next();
- return caller.handle(this, activities, next);
- }
-
- // private async Task UpdateActivityInternal(Activity activity,
- // IEnumerable updateHandlers,
- // Func> callAtBottom)
- // {
- // BotAssert.ActivityNotNull(activity);
- // if (updateHandlers == null)
- // throw new ArgumentException(nameof(updateHandlers));
- //
- // if (updateHandlers.Count() == 0) // No middleware to run.
- // {
- // if (callAtBottom != null)
- // {
- // return await callAtBottom();
- // }
- //
- // return null;
- // }
- //
- // /**
- // */ Default to "No more Middleware after this".
- // */
- // async Task next()
- // {
- // /**
- // */ Remove the first item from the list of middleware to call,
- // */ so that the next call just has the remaining items to worry about.
- // */
- // IEnumerable remaining = updateHandlers.Skip(1);
- // var result = await UpdateActivityInternal(activity, remaining, callAtBottom).ConfigureAwait(false);
- // activity.Id = result.Id;
- // return result;
- // }
- //
- // /**
- // */ Grab the current middleware, which is the 1st element in the array, and execute it
- // */
- // UpdateActivityHandler toCall = updateHandlers.First();
- // return await toCall(this, activity, next);
- // }
- private ResourceResponse UpdateActivityInternal(Activity activity,
- Iterator updateHandlers,
- Callable callAtBottom) throws Exception {
- BotAssert.ActivityNotNull(activity);
- if (updateHandlers == null)
- throw new IllegalArgumentException("updateHandlers");
-
- if (false == updateHandlers.hasNext()) { // No middleware to run.
- if (callAtBottom != null) {
- return callAtBottom.call();
- }
- return null;
- }
-
- // Default to "No more Middleware after this".
- Callable next = () -> {
- // Remove the first item from the list of middleware to call,
- // so that the next call just has the remaining items to worry about.
- if (updateHandlers.hasNext())
- updateHandlers.next();
- ResourceResponse result = null;
- try {
- result = UpdateActivityInternal(activity, updateHandlers, callAtBottom);
- } catch (Exception e) {
- e.printStackTrace();
- throw new RuntimeException(String.format("Error updating activity: %s", e.toString()));
- }
- activity.withId(result.id());
- return result;
- };
-
- // Grab the current middleware, which is the 1st element in the array, and execute it
- UpdateActivityHandler toCall = updateHandlers.next();
- return toCall.handle(this, activity, next);
- }
-
-
- private void DeleteActivityInternal(ConversationReference cr,
- Iterator deleteHandlers,
- Runnable callAtBottom) throws Exception {
- BotAssert.ConversationReferenceNotNull(cr);
- if (deleteHandlers == null)
- throw new IllegalArgumentException("deleteHandlers");
-
- if (deleteHandlers.hasNext() == false) { // No middleware to run.
- if (callAtBottom != null) {
- callAtBottom.run();
- }
- return;
- }
-
- // Default to "No more Middleware after this".
- Runnable next = () -> {
- // Remove the first item from the list of middleware to call,
- // so that the next call just has the remaining items to worry about.
-
- //Iterator remaining = (deleteHandlers.hasNext()) ? deleteHandlers.next() : null;
- if (deleteHandlers.hasNext())
- deleteHandlers.next();
-
-
- try {
- DeleteActivityInternal(cr, deleteHandlers, callAtBottom);
- } catch (Exception e) {
- e.printStackTrace();
- throw new RuntimeException("DeleteActivityInternal failed");
- }
- return;
- };
-
- // Grab the current middleware, which is the 1st element in the array, and execute it.
- DeleteActivityHandler toCall = deleteHandlers.next();
- toCall.handle(this, cr, next);
- }
-
- /**
- * Creates a conversation reference from an activity.
- *
- * @param activity The activity.
- * @return A conversation reference for the conversation that contains the activity.
- * @throws IllegalArgumentException {@code activity} is {@code null}.
- */
- public static ConversationReference GetConversationReference(Activity activity) {
- BotAssert.ActivityNotNull(activity);
-
- ConversationReference r = new ConversationReference()
- .withActivityId(activity.id())
- .withUser(activity.from())
- .withBot(activity.recipient())
- .withConversation(activity.conversation())
- .withChannelId(activity.channelId())
- .withServiceUrl(activity.serviceUrl());
-
- return r;
- }
-
- /**
- * Updates an activity with the delivery information from an existing
- * conversation reference.
- *
- * @param activity The activity to update.
- * @param reference The conversation reference.
- * @param isIncoming (Optional) {@code true} to treat the activity as an
- * incoming activity, where the bot is the recipient; otherwaire {@code false}.
- * Default is {@code false}, and the activity will show the bot as the sender.
- * Call {@link GetConversationReference(Activity)} on an incoming
- * activity to get a conversation reference that you can then use to update an
- * outgoing activity with the correct delivery information.
- * The {@link SendActivity(Activity)} and {@link SendActivities(Activity[])}
- * methods do this for you.
- */
- public static Activity ApplyConversationReference(Activity activity, ConversationReference reference) {
- return ApplyConversationReference(activity, reference, false);
- }
-
- public static Activity ApplyConversationReference(Activity activity, ConversationReference reference, boolean isIncoming) {
- activity.withChannelId(reference.channelId());
- activity.withServiceUrl(reference.serviceUrl());
- activity.withConversation(reference.conversation());
-
- if (isIncoming) {
- activity.withFrom(reference.user());
- activity.withRecipient(reference.bot());
- if (reference.activityId() != null)
- activity.withId(reference.activityId());
- } else { // Outgoing
- activity.withFrom(reference.bot());
- activity.withRecipient(reference.user());
- if (reference.activityId() != null)
- activity.withReplyToId(reference.activityId());
- }
- return activity;
- }
-
- public void close() throws Exception {
- turnServices.close();
- }
-}
+package com.microsoft.bot.builder;
+
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+import com.microsoft.bot.connector.ExecutorFactory;
+import com.microsoft.bot.schema.ActivityImpl;
+import com.microsoft.bot.schema.models.Activity;
+import com.microsoft.bot.schema.models.ConversationReference;
+import com.microsoft.bot.schema.models.InputHints;
+import com.microsoft.bot.schema.models.ResourceResponse;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.*;
+
+import static com.microsoft.bot.schema.models.ActivityTypes.MESSAGE;
+import static com.microsoft.bot.schema.models.ActivityTypes.TRACE;
+import static java.util.stream.Collectors.toList;
+
+/**
+ * Provides context for a turn of a bot.
+ * Context provides information needed to process an incoming activity.
+ * The context object is created by a {@link BotAdapter} and persists for the
+ * length of the turn.
+ * {@linkalso Bot}
+ * {@linkalso Middleware}
+ */
+public class TurnContextImpl implements TurnContext, AutoCloseable {
+ private final BotAdapter adapter;
+ private final ActivityImpl activity;
+ private Boolean responded = false;
+
+ private final List onSendActivities = new ArrayList();
+ private final List onUpdateActivity = new ArrayList();
+ private final List onDeleteActivity = new ArrayList();
+
+ private final TurnContextServiceCollection turnServices;
+
+ /**
+ * Creates a context object.
+ *
+ * @param adapter The adapter creating the context.
+ * @param activity The incoming activity for the turn;
+ * or {@code null} for a turn for a proactive message.
+ * @throws IllegalArgumentException {@code activity} or
+ * {@code adapter} is {@code null}.
+ * For use by bot adapter implementations only.
+ */
+ public TurnContextImpl(BotAdapter adapter, ActivityImpl activity) {
+ if (adapter == null)
+ throw new IllegalArgumentException("adapter");
+ this.adapter = adapter;
+ if (activity == null)
+ throw new IllegalArgumentException("activity");
+ this.activity = activity;
+
+ turnServices = new TurnContextServiceCollectionImpl();
+ }
+
+
+ /**
+ * Adds a response handler for send activity operations.
+ *
+ * @param handler The handler to add to the context object.
+ * @return The updated context object.
+ * @throws IllegalArgumentException {@code handler} is {@code null}.
+ * When the context's {@link SendActivity(Activity)}
+ * or {@link SendActivities(Activity[])} methods are called,
+ * the adapter calls the registered handlers in the order in which they were
+ * added to the context object.
+ */
+ public TurnContextImpl OnSendActivities(SendActivitiesHandler handler) {
+ if (handler == null)
+ throw new IllegalArgumentException("handler");
+
+ this.onSendActivities.add(handler);
+ return this;
+ }
+
+ /**
+ * Adds a response handler for update activity operations.
+ *
+ * @param handler The handler to add to the context object.
+ * @return The updated context object.
+ * @throws IllegalArgumentException {@code handler} is {@code null}.
+ * When the context's {@link UpdateActivity(Activity)} is called,
+ * the adapter calls the registered handlers in the order in which they were
+ * added to the context object.
+ */
+ public TurnContextImpl OnUpdateActivity(UpdateActivityHandler handler) {
+ if (handler == null)
+ throw new IllegalArgumentException("handler");
+
+ this.onUpdateActivity.add(handler);
+ return this;
+ }
+
+ /**
+ * Adds a response handler for delete activity operations.
+ *
+ * @param handler The handler to add to the context object.
+ * @return The updated context object.
+ * @throws IllegalArgumentException {@code handler} is {@code null}.
+ * When the context's {@link DeleteActivity(string)} is called,
+ * the adapter calls the registered handlers in the order in which they were
+ * added to the context object.
+ */
+ public TurnContextImpl OnDeleteActivity(DeleteActivityHandler handler) {
+ if (handler == null)
+ throw new IllegalArgumentException("handler");
+
+ this.onDeleteActivity.add(handler);
+ return this;
+ }
+
+ /**
+ * Gets the bot adapter that created this context object.
+ */
+ public BotAdapter getAdapter() {
+ return this.adapter;
+ }
+
+ /**
+ * Gets the services registered on this context object.
+ */
+ public TurnContextServiceCollection getServices() {
+ return this.turnServices;
+ }
+
+ /**
+ * Gets the activity associated with this turn; or {@code null} when processing
+ * a proactive message.
+ */
+ @Override
+ public Activity getActivity() {
+ return this.activity;
+ }
+
+ /**
+ * Indicates whether at least one response was sent for the current turn.
+ *
+ * @return {@code true} if at least one response was sent for the current turn.
+ * @throws IllegalArgumentException You attempted to set the value to {@code false}.
+ */
+ public boolean getResponded() {
+ return this.responded;
+ }
+
+ public void setResponded(boolean responded) {
+ if (responded == false) {
+ throw new IllegalArgumentException("TurnContext: cannot set 'responded' to a value of 'false'.");
+ }
+ this.responded = true;
+ }
+
+ /**
+ * Sends a message activity to the sender of the incoming activity.
+ *
+ * @param textReplyToSend The text of the message to send.
+ * @param speak Optional, text to be spoken by your bot on a speech-enabled
+ * channel.
+ * @param inputHint Optional, indicates whether your bot is accepting,
+ * expecting, or ignoring user input after the message is delivered to the client.
+ * One of: "acceptingInput", "ignoringInput", or "expectingInput".
+ * Default is null.
+ * @return A task that represents the work queued to execute.
+ * @throws IllegalArgumentException {@code textReplyToSend} is {@code null} or whitespace.
+ * If the activity is successfully sent, the task result contains
+ * a {@link ResourceResponse} object containing the ID that the receiving
+ * channel assigned to the activity.
+ * See the channel's documentation for limits imposed upon the contents of
+ * {@code textReplyToSend}.
+ * To control various characteristics of your bot's speech such as voice,
+ * rate, volume, pronunciation, and pitch, specify {@code speak} in
+ * Speech Synthesis Markup Language (SSML) format.
+ */
+ @Override
+ public ResourceResponse SendActivity(String textReplyToSend) throws Exception {
+ return SendActivity(textReplyToSend, null, null);
+ }
+
+ @Override
+ public ResourceResponse SendActivity(String textReplyToSend, String speak) throws Exception {
+ return SendActivity(textReplyToSend, speak, null);
+ }
+
+ @Override
+ public ResourceResponse SendActivity(String textReplyToSend, String speak, String inputHint) throws Exception {
+ if (StringUtils.isEmpty(textReplyToSend))
+ throw new IllegalArgumentException("textReplyToSend");
+
+ ActivityImpl activityToSend = (ActivityImpl) new ActivityImpl()
+ .withType(MESSAGE)
+ .withText(textReplyToSend);
+ if (speak != null)
+ activityToSend.withSpeak(speak);
+
+ if (StringUtils.isNotEmpty(inputHint))
+ activityToSend.withInputHint(InputHints.fromString(inputHint));
+
+ return SendActivity(activityToSend);
+ }
+
+ /**
+ * Sends an activity to the sender of the incoming activity.
+ *
+ * @param activity The activity to send.
+ * @return A task that represents the work queued to execute.
+ * @throws IllegalArgumentException {@code activity} is {@code null}.
+ * If the activity is successfully sent, the task result contains
+ * a {@link ResourceResponse} object containing the ID that the receiving
+ * channel assigned to the activity.
+ */
+ @Override
+ public ResourceResponse SendActivity(Activity activity) throws Exception {
+ if (activity == null)
+ throw new IllegalArgumentException("activity");
+
+ System.out.printf("In SENDEACTIVITYASYNC:");
+ System.out.flush();
+ Activity[] activities = {activity};
+ ResourceResponse[] responses;
+ try {
+ responses = SendActivities(activities);
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException(String.format("TurnContext:SendActivity fail %s", e.toString()));
+ }
+ if (responses == null || responses.length == 0) {
+ // It's possible an interceptor prevented the activity from having been sent.
+ // Just return an empty response in that case.
+ return null;
+ } else {
+ return responses[0];
+ }
+
+ }
+
+ /**
+ * Sends a set of activities to the sender of the incoming activity.
+ *
+ * @param activities The activities to send.
+ * @return A task that represents the work queued to execute.
+ * If the activities are successfully sent, the task result contains
+ * an array of {@link ResourceResponse} objects containing the IDs that
+ * the receiving channel assigned to the activities.
+ */
+ @Override
+ public ResourceResponse[] SendActivities(Activity[] activities) throws Exception {
+ // Bind the relevant Conversation Reference properties, such as URLs and
+ // ChannelId's, to the activities we're about to send.
+ ConversationReference cr = GetConversationReference(this.activity);
+ for (Activity a : activities) {
+ ApplyConversationReference(a, cr);
+ }
+
+ // Convert the IActivities to Activies.
+ // Activity[] activityArray = Array.ConvertAll(activities, (input) => (Activity)input);
+ List activityArray = Arrays.stream(activities).map(input -> (Activity) input).collect(toList());
+
+
+ // Create the list used by the recursive methods.
+ List activityList = new ArrayList(activityArray);
+
+ Callable ActuallySendStuff = () -> {
+ // Are the any non-trace activities to send?
+ // The thinking here is that a Trace event isn't user relevant data
+ // so the "Responded" flag should not be set by Trace messages being
+ // sent out.
+ boolean sentNonTraceActivities = false;
+ if (!activityList.stream().anyMatch((a) -> a.type() == TRACE)) {
+ sentNonTraceActivities = true;
+ }
+ // Send from the list, which may have been manipulated via the event handlers.
+ // Note that 'responses' was captured from the root of the call, and will be
+ // returned to the original caller.
+ ResourceResponse[] responses = new ResourceResponse[0];
+ responses = this.getAdapter().SendActivities(this, activityList.toArray(new ActivityImpl[activityList.size()]));
+ if (responses != null && responses.length == activityList.size()) {
+ // stitch up activity ids
+ for (int i = 0; i < responses.length; i++) {
+ ResourceResponse response = responses[i];
+ Activity activity = activityList.get(i);
+ activity.withId(response.id());
+ }
+ }
+
+ // If we actually sent something (that's not Trace), set the flag.
+ if (sentNonTraceActivities) {
+ this.setResponded(true);
+ }
+ return responses;
+ };
+
+ List act_list = new ArrayList<>(activityList);
+ return SendActivitiesInternal(act_list, onSendActivities.iterator(), ActuallySendStuff);
+ }
+
+ /**
+ * Replaces an existing activity.
+ *
+ * @param activity New replacement activity.
+ * @return A task that represents the work queued to execute.
+ * @throws Microsoft.Bot.Schema.ErrorResponseException The HTTP operation failed and the response contained additional information.
+ * @throws System.AggregateException One or more exceptions occurred during the operation.
+ * If the activity is successfully sent, the task result contains
+ * a {@link ResourceResponse} object containing the ID that the receiving
+ * channel assigned to the activity.
+ * Before calling this, set the ID of the replacement activity to the ID
+ * of the activity to replace.
+ */
+ @Override
+ public ResourceResponse UpdateActivity(Activity activity) throws Exception {
+
+
+ Callable ActuallyUpdateStuff = () -> {
+ return this.getAdapter().UpdateActivity(this, activity);
+ };
+
+ return UpdateActivityInternal(activity, onUpdateActivity.iterator(), ActuallyUpdateStuff);
+ }
+
+
+
+ /**
+ * Deletes an existing activity.
+ *
+ * @param activityId The ID of the activity to delete.
+ * @return A task that represents the work queued to execute.
+ * @throws Exception The HTTP operation failed and the response contained additional information.
+ */
+ public CompletableFuture DeleteActivity(String activityId) throws Exception {
+ if (StringUtils.isWhitespace(activityId) || activityId == null)
+ throw new IllegalArgumentException("activityId");
+
+ return CompletableFuture.runAsync(() -> {
+ ConversationReference cr = this.GetConversationReference(this.getActivity());
+ cr.withActivityId(activityId);
+
+ Runnable ActuallyDeleteStuff = () -> {
+ try {
+ this.getAdapter().DeleteActivity(this, cr);
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ throw new RuntimeException(String.format("Failed to delete activity %s", e.toString()));
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ throw new RuntimeException(String.format("Failed to delete activity %s", e.toString()));
+ }
+ return;
+ };
+
+ try {
+ DeleteActivityInternal(cr, onDeleteActivity.iterator(), ActuallyDeleteStuff);
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException(String.format("Failed to delete activity %s", e.getMessage()));
+ }
+ return;
+
+ }, ExecutorFactory.getExecutor());
+
+ }
+
+ /**
+ * Deletes an existing activity.
+ *
+ * @param conversationReference The conversation containing the activity to delete.
+ * @return A task that represents the work queued to execute.
+ * @throws Microsoft.Bot.Schema.ErrorResponseException The HTTP operation failed and the response contained additional information.
+ * The conversation reference's {@link ConversationReference.ActivityId}
+ * indicates the activity in the conversation to delete.
+ */
+ public void DeleteActivity(ConversationReference conversationReference) throws Exception {
+ if (conversationReference == null)
+ throw new IllegalArgumentException("conversationReference");
+
+ Runnable ActuallyDeleteStuff = () -> {
+ try {
+ this.getAdapter().DeleteActivity(this, conversationReference);
+ return;
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ throw new RuntimeException("DeleteActivity failed");
+ };
+
+ DeleteActivityInternal(conversationReference, onDeleteActivity.iterator(), ActuallyDeleteStuff);
+ return ;
+ }
+
+ private ResourceResponse[] SendActivitiesInternal(List activities, Iterator sendHandlers, Callable callAtBottom) throws Exception {
+ if (activities == null)
+ throw new IllegalArgumentException("activities");
+ if (sendHandlers == null)
+ throw new IllegalArgumentException("sendHandlers");
+
+ if (false == sendHandlers.hasNext()) { // No middleware to run.
+ if (callAtBottom != null)
+ return callAtBottom.call();
+ return new ResourceResponse[0];
+ }
+
+ // Default to "No more Middleware after this".
+ Callable next = () -> {
+ // Remove the first item from the list of middleware to call,
+ // so that the next call just has the remaining items to worry about.
+ //Iterable remaining = sendHandlers.Skip(1);
+ //Iterator remaining = sendHandlers.iterator();
+ if (sendHandlers.hasNext())
+ sendHandlers.next();
+ return SendActivitiesInternal(activities, sendHandlers, callAtBottom);
+ };
+
+ // Grab the current middleware, which is the 1st element in the array, and execute it
+ SendActivitiesHandler caller = sendHandlers.next();
+ return caller.handle(this, activities, next);
+ }
+
+ // private async Task UpdateActivityInternal(Activity activity,
+ // IEnumerable updateHandlers,
+ // Func> callAtBottom)
+ // {
+ // BotAssert.ActivityNotNull(activity);
+ // if (updateHandlers == null)
+ // throw new ArgumentException(nameof(updateHandlers));
+ //
+ // if (updateHandlers.Count() == 0) // No middleware to run.
+ // {
+ // if (callAtBottom != null)
+ // {
+ // return await callAtBottom();
+ // }
+ //
+ // return null;
+ // }
+ //
+ // /**
+ // */ Default to "No more Middleware after this".
+ // */
+ // async Task next()
+ // {
+ // /**
+ // */ Remove the first item from the list of middleware to call,
+ // */ so that the next call just has the remaining items to worry about.
+ // */
+ // IEnumerable remaining = updateHandlers.Skip(1);
+ // var result = await UpdateActivityInternal(activity, remaining, callAtBottom).ConfigureAwait(false);
+ // activity.Id = result.Id;
+ // return result;
+ // }
+ //
+ // /**
+ // */ Grab the current middleware, which is the 1st element in the array, and execute it
+ // */
+ // UpdateActivityHandler toCall = updateHandlers.First();
+ // return await toCall(this, activity, next);
+ // }
+ private ResourceResponse UpdateActivityInternal(Activity activity,
+ Iterator updateHandlers,
+ Callable callAtBottom) throws Exception {
+ BotAssert.ActivityNotNull(activity);
+ if (updateHandlers == null)
+ throw new IllegalArgumentException("updateHandlers");
+
+ if (false == updateHandlers.hasNext()) { // No middleware to run.
+ if (callAtBottom != null) {
+ return callAtBottom.call();
+ }
+ return null;
+ }
+
+ // Default to "No more Middleware after this".
+ Callable next = () -> {
+ // Remove the first item from the list of middleware to call,
+ // so that the next call just has the remaining items to worry about.
+ if (updateHandlers.hasNext())
+ updateHandlers.next();
+ ResourceResponse result = null;
+ try {
+ result = UpdateActivityInternal(activity, updateHandlers, callAtBottom);
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException(String.format("Error updating activity: %s", e.toString()));
+ }
+ activity.withId(result.id());
+ return result;
+ };
+
+ // Grab the current middleware, which is the 1st element in the array, and execute it
+ UpdateActivityHandler toCall = updateHandlers.next();
+ return toCall.handle(this, activity, next);
+ }
+
+
+ private void DeleteActivityInternal(ConversationReference cr,
+ Iterator deleteHandlers,
+ Runnable callAtBottom) throws Exception {
+ BotAssert.ConversationReferenceNotNull(cr);
+ if (deleteHandlers == null)
+ throw new IllegalArgumentException("deleteHandlers");
+
+ if (deleteHandlers.hasNext() == false) { // No middleware to run.
+ if (callAtBottom != null) {
+ callAtBottom.run();
+ }
+ return;
+ }
+
+ // Default to "No more Middleware after this".
+ Runnable next = () -> {
+ // Remove the first item from the list of middleware to call,
+ // so that the next call just has the remaining items to worry about.
+
+ //Iterator remaining = (deleteHandlers.hasNext()) ? deleteHandlers.next() : null;
+ if (deleteHandlers.hasNext())
+ deleteHandlers.next();
+
+
+ try {
+ DeleteActivityInternal(cr, deleteHandlers, callAtBottom);
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException("DeleteActivityInternal failed");
+ }
+ return;
+ };
+
+ // Grab the current middleware, which is the 1st element in the array, and execute it.
+ DeleteActivityHandler toCall = deleteHandlers.next();
+ toCall.handle(this, cr, next);
+ }
+
+ /**
+ * Creates a conversation reference from an activity.
+ *
+ * @param activity The activity.
+ * @return A conversation reference for the conversation that contains the activity.
+ * @throws IllegalArgumentException {@code activity} is {@code null}.
+ */
+ public static ConversationReference GetConversationReference(Activity activity) {
+ BotAssert.ActivityNotNull(activity);
+
+ ConversationReference r = new ConversationReference()
+ .withActivityId(activity.id())
+ .withUser(activity.from())
+ .withBot(activity.recipient())
+ .withConversation(activity.conversation())
+ .withChannelId(activity.channelId())
+ .withServiceUrl(activity.serviceUrl());
+
+ return r;
+ }
+
+ /**
+ * Updates an activity with the delivery information from an existing
+ * conversation reference.
+ *
+ * @param activity The activity to update.
+ * @param reference The conversation reference.
+ * @param isIncoming (Optional) {@code true} to treat the activity as an
+ * incoming activity, where the bot is the recipient; otherwaire {@code false}.
+ * Default is {@code false}, and the activity will show the bot as the sender.
+ * Call {@link GetConversationReference(Activity)} on an incoming
+ * activity to get a conversation reference that you can then use to update an
+ * outgoing activity with the correct delivery information.
+ * The {@link SendActivity(Activity)} and {@link SendActivities(Activity[])}
+ * methods do this for you.
+ */
+ public static Activity ApplyConversationReference(Activity activity, ConversationReference reference) {
+ return ApplyConversationReference(activity, reference, false);
+ }
+
+ public static Activity ApplyConversationReference(Activity activity, ConversationReference reference, boolean isIncoming) {
+ activity.withChannelId(reference.channelId());
+ activity.withServiceUrl(reference.serviceUrl());
+ activity.withConversation(reference.conversation());
+
+ if (isIncoming) {
+ activity.withFrom(reference.user());
+ activity.withRecipient(reference.bot());
+ if (reference.activityId() != null)
+ activity.withId(reference.activityId());
+ } else { // Outgoing
+ activity.withFrom(reference.bot());
+ activity.withRecipient(reference.user());
+ if (reference.activityId() != null)
+ activity.withReplyToId(reference.activityId());
+ }
+ return activity;
+ }
+
+ public void close() throws Exception {
+ turnServices.close();
+ }
+}
diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotStateTest.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotStateTest.java
index 8b8587848..d344ec620 100644
--- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotStateTest.java
+++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotStateTest.java
@@ -1,379 +1,379 @@
-
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-package com.microsoft.bot.builder;
-
-
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.microsoft.bot.builder.adapters.TestAdapter;
-import com.microsoft.bot.builder.adapters.TestFlow;
-import com.microsoft.bot.connector.implementation.ConnectorClientImpl;
-import com.microsoft.bot.schema.models.ChannelAccount;
-import com.microsoft.bot.schema.models.ResourceResponse;
-import com.microsoft.rest.RestClient;
-import org.apache.commons.lang3.StringUtils;
-import org.junit.Assert;
-import org.junit.Test;
-
-import java.util.UUID;
-import java.util.concurrent.ExecutionException;
-import java.util.function.Consumer;
-
-
-// [TestClass]
-// [TestCategory("State Management")]
-public class BotStateTest {
- protected ConnectorClientImpl connector;
- protected ChannelAccount bot;
- protected ChannelAccount user;
-
-
- protected void initializeClients(RestClient restClient, String botId, String userId) {
-
- connector = new ConnectorClientImpl(restClient);
- bot = new ChannelAccount().withId(botId);
- user = new ChannelAccount().withId(userId);
-
- }
-
-
- protected void cleanUpResources() {
- }
-
- @Test
- public void State_DoNOTRememberContextState() throws ExecutionException, InterruptedException {
-
- TestAdapter adapter = new TestAdapter();
-
- new TestFlow(adapter, (context) -> {
- TestPocoState obj = StateTurnContextExtensions.GetConversationState(context);
- Assert.assertNull("context.state should not exist", obj); }
- )
- .Send("set value")
- .StartTest();
-
- }
-
- //@Test
- public void State_RememberIStoreItemUserState() throws ExecutionException, InterruptedException {
- TestAdapter adapter = new TestAdapter()
- .Use(new UserState(new MemoryStorage(), TestState::new));
-
-
- Consumer callback = (context) -> {
- System.out.print(String.format("State_RememberIStoreItemUserState CALLBACK called.."));
- System.out.flush();
- TestState userState = StateTurnContextExtensions.GetUserState(context);
- Assert.assertNotNull("user state should exist", userState);
- switch (context.getActivity().text()) {
- case "set value":
- userState.withValue("test");
- try {
- ((TurnContextImpl)context).SendActivity("value saved");
- } catch (Exception e) {
- e.printStackTrace();
- Assert.fail(String.format("Error sending activity! - set value"));
- }
- break;
- case "get value":
- try {
- Assert.assertFalse(StringUtils.isBlank(userState.value()));
- ((TurnContextImpl)context).SendActivity(userState.value());
- } catch (Exception e) {
- e.printStackTrace();
- Assert.fail(String.format("Error sending activity! - get value"));
- }
- break;
- }
-
- };
-
- new TestFlow(adapter, callback)
- .Test("set value", "value saved")
- .Test("get value", "test")
- .StartTest();
-
- }
-
- @Test
- public void State_RememberPocoUserState() throws ExecutionException, InterruptedException {
- TestAdapter adapter = new TestAdapter()
- .Use(new UserState(new MemoryStorage(), TestPocoState::new));
- new TestFlow(adapter,
- (context) ->
- {
- TestPocoState userState = StateTurnContextExtensions.GetUserState(context);
-
- Assert.assertNotNull("user state should exist", userState);
- switch (context.getActivity().text()) {
- case "set value":
- userState.setValue("test");
- try {
- context.SendActivity("value saved");
- } catch (Exception e) {
- e.printStackTrace();
- Assert.fail(String.format("Error sending activity! - set value"));
- }
- break;
- case "get value":
- try {
- Assert.assertFalse(StringUtils.isBlank(userState.getValue()));
- context.SendActivity(userState.getValue());
- } catch (Exception e) {
- e.printStackTrace();
- Assert.fail(String.format("Error sending activity! - get value"));
- }
- break;
- }
- })
- .Test("set value", "value saved")
- .Test("get value", "test")
- .StartTest();
- }
-
- //@Test
- public void State_RememberIStoreItemConversationState() throws ExecutionException, InterruptedException {
- TestAdapter adapter = new TestAdapter()
- .Use(new ConversationState(new MemoryStorage(), TestState::new));
- new TestFlow(adapter,
- (context) ->
- {
- TestState conversationState = StateTurnContextExtensions.GetConversationState(context);
- Assert.assertNotNull("state.conversation should exist", conversationState);
- switch (context.getActivity().text()) {
- case "set value":
- conversationState.withValue("test");
- try {
- context.SendActivity("value saved");
- } catch (Exception e) {
- e.printStackTrace();
- Assert.fail(String.format("Error sending activity! - set value"));
- }
- break;
- case "get value":
- try {
- Assert.assertFalse(StringUtils.isBlank(conversationState.value()));
- context.SendActivity(conversationState.value());
- } catch (Exception e) {
- e.printStackTrace();
- Assert.fail(String.format("Error sending activity! - get value"));
- }
- break;
- }
- })
- .Test("set value", "value saved")
- .Test("get value", "test")
- .StartTest();
- }
-
- //@Test
- public void State_RememberPocoConversationState() throws ExecutionException, InterruptedException {
- TestAdapter adapter = new TestAdapter()
- .Use(new ConversationState(new MemoryStorage(), TestPocoState::new));
- new TestFlow(adapter,
- (context) ->
- {
- TestPocoState conversationState = StateTurnContextExtensions.GetConversationState(context);
- Assert.assertNotNull("state.conversation should exist", conversationState);
- switch (context.getActivity().text()) {
- case "set value":
- conversationState.setValue("test");
- try {
- context.SendActivity("value saved");
- } catch (Exception e) {
- e.printStackTrace();
- Assert.fail(String.format("Error sending activity! - set value"));
- }
- break;
- case "get value":
- try {
- Assert.assertFalse(StringUtils.isBlank(conversationState.getValue()));
- context.SendActivity(conversationState.getValue());
- } catch (Exception e) {
- e.printStackTrace();
- Assert.fail(String.format("Error sending activity! - get value"));
- }
- break;
- }
- })
-
- .Test("set value", "value saved")
- .Test("get value", "test")
- .StartTest();
- }
-
- @Test
- public void State_CustomStateManagerTest() throws ExecutionException, InterruptedException {
-
- String testGuid = UUID.randomUUID().toString();
- TestAdapter adapter = new TestAdapter()
- .Use(new CustomKeyState(new MemoryStorage()));
- new TestFlow(adapter,
- (context) ->
- {
- CustomState customState = CustomKeyState.Get(context);
-
- switch (context.getActivity().text()) {
- case "set value":
- customState.setCustomString(testGuid);
- try {
- context.SendActivity("value saved");
- } catch (Exception e) {
- e.printStackTrace();
- Assert.fail(String.format("Error sending activity! - set value"));
- }
- break;
- case "get value":
- try {
- Assert.assertFalse(StringUtils.isBlank(customState.getCustomString()));
- context.SendActivity(customState.getCustomString());
- } catch (Exception e) {
- e.printStackTrace();
- Assert.fail(String.format("Error sending activity! - get value"));
- }
- break;
- }
- })
- .Test("set value", "value saved")
- .Test("get value", testGuid.toString())
- .StartTest();
- }
- @Test
- public void State_RoundTripTypedObjectwTrace() throws ExecutionException, InterruptedException {
- TestAdapter adapter = new TestAdapter()
- .Use(new ConversationState(new MemoryStorage(), TypedObject::new));
- new TestFlow(adapter,
- (context) ->
- {
- System.out.println(String.format(">>Test Callback(tid:%s): STARTING : %s", Thread.currentThread().getId(), context.getActivity().text()));
- System.out.flush();
- TypedObject conversation = StateTurnContextExtensions.GetConversationState(context);
- Assert.assertNotNull("conversationstate should exist", conversation);
- System.out.println(String.format(">>Test Callback(tid:%s): Text is : %s", Thread.currentThread().getId(), context.getActivity().text()));
- System.out.flush();
- switch (context.getActivity().text()) {
- case "set value":
- conversation.withName("test");
- try {
- System.out.println(String.format(">>Test Callback(tid:%s): Send activity : %s", Thread.currentThread().getId(),
- "value saved"));
- System.out.flush();
- ResourceResponse response = context.SendActivity("value saved");
- System.out.println(String.format(">>Test Callback(tid:%s): Response Id: %s", Thread.currentThread().getId(),
- response.id()));
- System.out.flush();
-
- } catch (Exception e) {
- e.printStackTrace();
- Assert.fail(String.format("Error sending activity! - set value"));
- }
- break;
- case "get value":
- try {
- System.out.println(String.format(">>Test Callback(tid:%s): Send activity : %s", Thread.currentThread().getId(),
- "TypedObject"));
- System.out.flush();
- context.SendActivity("TypedObject");
- } catch (Exception e) {
- e.printStackTrace();
- Assert.fail(String.format("Error sending activity! - get value"));
- }
- break;
- }
- })
- .Turn("set value", "value saved", "Description", 50000)
- .Turn("get value", "TypedObject", "Description", 50000)
- .StartTest();
-
- }
-
-
- @Test
- public void State_RoundTripTypedObject() throws ExecutionException, InterruptedException {
- TestAdapter adapter = new TestAdapter()
- .Use(new ConversationState(new MemoryStorage(), TypedObject::new));
-
- new TestFlow(adapter,
- (context) ->
- {
- TypedObject conversation = StateTurnContextExtensions.GetConversationState(context);
- Assert.assertNotNull("conversationstate should exist", conversation);
- switch (context.getActivity().text()) {
- case "set value":
- conversation.withName("test");
- try {
- context.SendActivity("value saved");
- } catch (Exception e) {
- e.printStackTrace();
- Assert.fail(String.format("Error sending activity! - set value"));
- }
- break;
- case "get value":
- try {
- context.SendActivity("TypedObject");
- } catch (Exception e) {
- e.printStackTrace();
- Assert.fail(String.format("Error sending activity! - get value"));
- }
- break;
- }
- })
- .Test("set value", "value saved")
- .Test("get value", "TypedObject")
- .StartTest();
-
- }
-
- @Test
- public void State_UseBotStateDirectly() throws ExecutionException, InterruptedException {
- TestAdapter adapter = new TestAdapter();
-
- new TestFlow(adapter,
- (context) ->
- {
- BotState botStateManager = new BotState(new MemoryStorage(), "BotState:com.microsoft.bot.builder.core.extensions.BotState",
- (ctx) -> String.format("botstate/%s/%s/com.microsoft.bot.builder.core.extensions.BotState",
- ctx.getActivity().channelId(), ctx.getActivity().conversation().id()), CustomState::new);
-
- // read initial state object
- CustomState customState = null;
- try {
- customState = (CustomState) botStateManager.Read(context).join();
- } catch (JsonProcessingException e) {
- e.printStackTrace();
- Assert.fail("Error reading custom state");
- }
-
- // this should be a 'new CustomState' as nothing is currently stored in storage
- Assert.assertEquals(customState, new CustomState());
-
- // amend property and write to storage
- customState.setCustomString("test");
- try {
- botStateManager.Write(context, customState).join();
- } catch (Exception e) {
- e.printStackTrace();
- Assert.fail("Could not write customstate");
- }
-
- // set customState to null before reading from storage
- customState = null;
- try {
- customState = (CustomState) botStateManager.Read(context).join();
- } catch (JsonProcessingException e) {
- e.printStackTrace();
- Assert.fail("Could not read customstate back");
- }
-
- // check object read from value has the correct value for CustomString
- Assert.assertEquals(customState.getCustomString(), "test");
- }
- )
- .StartTest();
- }
-
-
-}
-
+
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.microsoft.bot.builder;
+
+
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.microsoft.bot.builder.adapters.TestAdapter;
+import com.microsoft.bot.builder.adapters.TestFlow;
+import com.microsoft.bot.connector.rest.RestConnectorClient;
+import com.microsoft.bot.schema.models.ChannelAccount;
+import com.microsoft.bot.schema.models.ResourceResponse;
+import com.microsoft.rest.RestClient;
+import org.apache.commons.lang3.StringUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+
+
+// [TestClass]
+// [TestCategory("State Management")]
+public class BotStateTest {
+ protected RestConnectorClient connector;
+ protected ChannelAccount bot;
+ protected ChannelAccount user;
+
+
+ protected void initializeClients(RestClient restClient, String botId, String userId) {
+
+ connector = new RestConnectorClient(restClient);
+ bot = new ChannelAccount().withId(botId);
+ user = new ChannelAccount().withId(userId);
+
+ }
+
+
+ protected void cleanUpResources() {
+ }
+
+ @Test
+ public void State_DoNOTRememberContextState() throws ExecutionException, InterruptedException {
+
+ TestAdapter adapter = new TestAdapter();
+
+ new TestFlow(adapter, (context) -> {
+ TestPocoState obj = StateTurnContextExtensions.GetConversationState(context);
+ Assert.assertNull("context.state should not exist", obj); }
+ )
+ .Send("set value")
+ .StartTest();
+
+ }
+
+ //@Test
+ public void State_RememberIStoreItemUserState() throws ExecutionException, InterruptedException {
+ TestAdapter adapter = new TestAdapter()
+ .Use(new UserState(new MemoryStorage(), TestState::new));
+
+
+ Consumer callback = (context) -> {
+ System.out.print(String.format("State_RememberIStoreItemUserState CALLBACK called.."));
+ System.out.flush();
+ TestState userState = StateTurnContextExtensions.GetUserState(context);
+ Assert.assertNotNull("user state should exist", userState);
+ switch (context.getActivity().text()) {
+ case "set value":
+ userState.withValue("test");
+ try {
+ ((TurnContextImpl)context).SendActivity("value saved");
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.fail(String.format("Error sending activity! - set value"));
+ }
+ break;
+ case "get value":
+ try {
+ Assert.assertFalse(StringUtils.isBlank(userState.value()));
+ ((TurnContextImpl)context).SendActivity(userState.value());
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.fail(String.format("Error sending activity! - get value"));
+ }
+ break;
+ }
+
+ };
+
+ new TestFlow(adapter, callback)
+ .Test("set value", "value saved")
+ .Test("get value", "test")
+ .StartTest();
+
+ }
+
+ @Test
+ public void State_RememberPocoUserState() throws ExecutionException, InterruptedException {
+ TestAdapter adapter = new TestAdapter()
+ .Use(new UserState(new MemoryStorage(), TestPocoState::new));
+ new TestFlow(adapter,
+ (context) ->
+ {
+ TestPocoState userState = StateTurnContextExtensions.GetUserState(context);
+
+ Assert.assertNotNull("user state should exist", userState);
+ switch (context.getActivity().text()) {
+ case "set value":
+ userState.setValue("test");
+ try {
+ context.SendActivity("value saved");
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.fail(String.format("Error sending activity! - set value"));
+ }
+ break;
+ case "get value":
+ try {
+ Assert.assertFalse(StringUtils.isBlank(userState.getValue()));
+ context.SendActivity(userState.getValue());
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.fail(String.format("Error sending activity! - get value"));
+ }
+ break;
+ }
+ })
+ .Test("set value", "value saved")
+ .Test("get value", "test")
+ .StartTest();
+ }
+
+ //@Test
+ public void State_RememberIStoreItemConversationState() throws ExecutionException, InterruptedException {
+ TestAdapter adapter = new TestAdapter()
+ .Use(new ConversationState(new MemoryStorage(), TestState::new));
+ new TestFlow(adapter,
+ (context) ->
+ {
+ TestState conversationState = StateTurnContextExtensions.GetConversationState(context);
+ Assert.assertNotNull("state.conversation should exist", conversationState);
+ switch (context.getActivity().text()) {
+ case "set value":
+ conversationState.withValue("test");
+ try {
+ context.SendActivity("value saved");
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.fail(String.format("Error sending activity! - set value"));
+ }
+ break;
+ case "get value":
+ try {
+ Assert.assertFalse(StringUtils.isBlank(conversationState.value()));
+ context.SendActivity(conversationState.value());
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.fail(String.format("Error sending activity! - get value"));
+ }
+ break;
+ }
+ })
+ .Test("set value", "value saved")
+ .Test("get value", "test")
+ .StartTest();
+ }
+
+ //@Test
+ public void State_RememberPocoConversationState() throws ExecutionException, InterruptedException {
+ TestAdapter adapter = new TestAdapter()
+ .Use(new ConversationState(new MemoryStorage(), TestPocoState::new));
+ new TestFlow(adapter,
+ (context) ->
+ {
+ TestPocoState conversationState = StateTurnContextExtensions.GetConversationState(context);
+ Assert.assertNotNull("state.conversation should exist", conversationState);
+ switch (context.getActivity().text()) {
+ case "set value":
+ conversationState.setValue("test");
+ try {
+ context.SendActivity("value saved");
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.fail(String.format("Error sending activity! - set value"));
+ }
+ break;
+ case "get value":
+ try {
+ Assert.assertFalse(StringUtils.isBlank(conversationState.getValue()));
+ context.SendActivity(conversationState.getValue());
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.fail(String.format("Error sending activity! - get value"));
+ }
+ break;
+ }
+ })
+
+ .Test("set value", "value saved")
+ .Test("get value", "test")
+ .StartTest();
+ }
+
+ @Test
+ public void State_CustomStateManagerTest() throws ExecutionException, InterruptedException {
+
+ String testGuid = UUID.randomUUID().toString();
+ TestAdapter adapter = new TestAdapter()
+ .Use(new CustomKeyState(new MemoryStorage()));
+ new TestFlow(adapter,
+ (context) ->
+ {
+ CustomState customState = CustomKeyState.Get(context);
+
+ switch (context.getActivity().text()) {
+ case "set value":
+ customState.setCustomString(testGuid);
+ try {
+ context.SendActivity("value saved");
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.fail(String.format("Error sending activity! - set value"));
+ }
+ break;
+ case "get value":
+ try {
+ Assert.assertFalse(StringUtils.isBlank(customState.getCustomString()));
+ context.SendActivity(customState.getCustomString());
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.fail(String.format("Error sending activity! - get value"));
+ }
+ break;
+ }
+ })
+ .Test("set value", "value saved")
+ .Test("get value", testGuid.toString())
+ .StartTest();
+ }
+ @Test
+ public void State_RoundTripTypedObjectwTrace() throws ExecutionException, InterruptedException {
+ TestAdapter adapter = new TestAdapter()
+ .Use(new ConversationState(new MemoryStorage(), TypedObject::new));
+ new TestFlow(adapter,
+ (context) ->
+ {
+ System.out.println(String.format(">>Test Callback(tid:%s): STARTING : %s", Thread.currentThread().getId(), context.getActivity().text()));
+ System.out.flush();
+ TypedObject conversation = StateTurnContextExtensions.GetConversationState(context);
+ Assert.assertNotNull("conversationstate should exist", conversation);
+ System.out.println(String.format(">>Test Callback(tid:%s): Text is : %s", Thread.currentThread().getId(), context.getActivity().text()));
+ System.out.flush();
+ switch (context.getActivity().text()) {
+ case "set value":
+ conversation.withName("test");
+ try {
+ System.out.println(String.format(">>Test Callback(tid:%s): Send activity : %s", Thread.currentThread().getId(),
+ "value saved"));
+ System.out.flush();
+ ResourceResponse response = context.SendActivity("value saved");
+ System.out.println(String.format(">>Test Callback(tid:%s): Response Id: %s", Thread.currentThread().getId(),
+ response.id()));
+ System.out.flush();
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.fail(String.format("Error sending activity! - set value"));
+ }
+ break;
+ case "get value":
+ try {
+ System.out.println(String.format(">>Test Callback(tid:%s): Send activity : %s", Thread.currentThread().getId(),
+ "TypedObject"));
+ System.out.flush();
+ context.SendActivity("TypedObject");
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.fail(String.format("Error sending activity! - get value"));
+ }
+ break;
+ }
+ })
+ .Turn("set value", "value saved", "Description", 50000)
+ .Turn("get value", "TypedObject", "Description", 50000)
+ .StartTest();
+
+ }
+
+
+ @Test
+ public void State_RoundTripTypedObject() throws ExecutionException, InterruptedException {
+ TestAdapter adapter = new TestAdapter()
+ .Use(new ConversationState(new MemoryStorage(), TypedObject::new));
+
+ new TestFlow(adapter,
+ (context) ->
+ {
+ TypedObject conversation = StateTurnContextExtensions.GetConversationState(context);
+ Assert.assertNotNull("conversationstate should exist", conversation);
+ switch (context.getActivity().text()) {
+ case "set value":
+ conversation.withName("test");
+ try {
+ context.SendActivity("value saved");
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.fail(String.format("Error sending activity! - set value"));
+ }
+ break;
+ case "get value":
+ try {
+ context.SendActivity("TypedObject");
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.fail(String.format("Error sending activity! - get value"));
+ }
+ break;
+ }
+ })
+ .Test("set value", "value saved")
+ .Test("get value", "TypedObject")
+ .StartTest();
+
+ }
+
+ @Test
+ public void State_UseBotStateDirectly() throws ExecutionException, InterruptedException {
+ TestAdapter adapter = new TestAdapter();
+
+ new TestFlow(adapter,
+ (context) ->
+ {
+ BotState botStateManager = new BotState(new MemoryStorage(), "BotState:com.microsoft.bot.builder.core.extensions.BotState",
+ (ctx) -> String.format("botstate/%s/%s/com.microsoft.bot.builder.core.extensions.BotState",
+ ctx.getActivity().channelId(), ctx.getActivity().conversation().id()), CustomState::new);
+
+ // read initial state object
+ CustomState customState = null;
+ try {
+ customState = (CustomState) botStateManager.Read(context).join();
+ } catch (JsonProcessingException e) {
+ e.printStackTrace();
+ Assert.fail("Error reading custom state");
+ }
+
+ // this should be a 'new CustomState' as nothing is currently stored in storage
+ Assert.assertEquals(customState, new CustomState());
+
+ // amend property and write to storage
+ customState.setCustomString("test");
+ try {
+ botStateManager.Write(context, customState).join();
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.fail("Could not write customstate");
+ }
+
+ // set customState to null before reading from storage
+ customState = null;
+ try {
+ customState = (CustomState) botStateManager.Read(context).join();
+ } catch (JsonProcessingException e) {
+ e.printStackTrace();
+ Assert.fail("Could not read customstate back");
+ }
+
+ // check object read from value has the correct value for CustomString
+ Assert.assertEquals(customState.getCustomString(), "test");
+ }
+ )
+ .StartTest();
+ }
+
+
+}
+
diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/CatchException_MiddlewareTest.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/CatchException_MiddlewareTest.java
index 16d3029f5..9c29fbe77 100644
--- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/CatchException_MiddlewareTest.java
+++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/CatchException_MiddlewareTest.java
@@ -1,109 +1,110 @@
-package com.microsoft.bot.builder;
-
-import com.microsoft.bot.builder.adapters.TestAdapter;
-import com.microsoft.bot.builder.adapters.TestFlow;
-import com.microsoft.bot.schema.ActivityImpl;
-import com.microsoft.bot.schema.models.Activity;
-import org.junit.Assert;
-import org.junit.Test;
-
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-
-public class CatchException_MiddlewareTest {
-
- @Test
- public void CatchException_TestMiddleware_TestStackedErrorMiddleware() throws ExecutionException, InterruptedException {
-
- TestAdapter adapter = new TestAdapter()
- .Use(new CatchExceptionMiddleware(new CallOnException() {
- @Override
- public CompletableFuture apply(TurnContext context, T t) throws Exception {
- return CompletableFuture.runAsync(() -> {
- Activity activity = context.getActivity();
- if (activity instanceof ActivityImpl) {
- try {
- context.SendActivity(((ActivityImpl) activity).CreateReply(t.toString()));
- } catch (Exception e) {
- e.printStackTrace();
- throw new RuntimeException(String.format("CatchException_TestMiddleware_TestStackedErrorMiddleware:SendActivity failed %s", e.toString()));
- }
- } else
- Assert.assertTrue("Test was built for ActivityImpl", false);
-
- });
-
- }
- }, Exception.class))
- // Add middleware to catch NullReferenceExceptions before throwing up to the general exception instance
- .Use(new CatchExceptionMiddleware(new CallOnException() {
- @Override
- public CompletableFuture apply(TurnContext context, T t) throws Exception {
- context.SendActivity("Sorry - Null Reference Exception");
- return CompletableFuture.completedFuture(null);
- }
- }, NullPointerException.class));
-
-
- new TestFlow(adapter, (context) ->
- {
-
- if (context.getActivity().text() == "foo") {
- try {
- context.SendActivity(context.getActivity().text());
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- if (context.getActivity().text() == "UnsupportedOperationException") {
- throw new UnsupportedOperationException("Test");
- }
-
- }
- )
- .Send("foo")
- .AssertReply("foo", "passthrough")
- .Send("UnsupportedOperationException")
- .AssertReply("Test")
- .StartTest();
-
- }
-
-/* @Test
- // [TestCategory("Middleware")]
- public void CatchException_TestMiddleware_SpecificExceptionType()
-{
- TestAdapter adapter = new TestAdapter()
- .Use(new CatchExceptionMiddleware((context, exception) =>
- {
- context.SendActivity("Generic Exception Caught");
- return CompletableFuture.CompletedTask;
- }))
- .Use(new CatchExceptionMiddleware((context, exception) =>
- {
- context.SendActivity(exception.Message);
- return CompletableFuture.CompletedTask;
- }));
-
-
- await new TestFlow(adapter, (context) =>
- {
- if (context.Activity.AsMessageActivity().Text == "foo")
- {
- context.SendActivity(context.Activity.AsMessageActivity().Text);
- }
-
- if (context.Activity.AsMessageActivity().Text == "NullReferenceException")
- {
- throw new NullReferenceException("Test");
- }
-
- return CompletableFuture.CompletedTask;
- })
- .Send("foo")
- .AssertReply("foo", "passthrough")
- .Send("NullReferenceException")
- .AssertReply("Test")
- .StartTest();
-}*/
-}
+package com.microsoft.bot.builder;
+
+import com.microsoft.bot.builder.adapters.TestAdapter;
+import com.microsoft.bot.builder.adapters.TestFlow;
+import com.microsoft.bot.connector.ExecutorFactory;
+import com.microsoft.bot.schema.ActivityImpl;
+import com.microsoft.bot.schema.models.Activity;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+public class CatchException_MiddlewareTest {
+
+ @Test
+ public void CatchException_TestMiddleware_TestStackedErrorMiddleware() throws ExecutionException, InterruptedException {
+
+ TestAdapter adapter = new TestAdapter()
+ .Use(new CatchExceptionMiddleware(new CallOnException() {
+ @Override
+ public CompletableFuture apply(TurnContext context, T t) throws Exception {
+ return CompletableFuture.runAsync(() -> {
+ Activity activity = context.getActivity();
+ if (activity instanceof ActivityImpl) {
+ try {
+ context.SendActivity(((ActivityImpl) activity).CreateReply(t.toString()));
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException(String.format("CatchException_TestMiddleware_TestStackedErrorMiddleware:SendActivity failed %s", e.toString()));
+ }
+ } else
+ Assert.assertTrue("Test was built for ActivityImpl", false);
+
+ }, ExecutorFactory.getExecutor());
+
+ }
+ }, Exception.class))
+ // Add middleware to catch NullReferenceExceptions before throwing up to the general exception instance
+ .Use(new CatchExceptionMiddleware(new CallOnException() {
+ @Override
+ public CompletableFuture apply(TurnContext context, T t) throws Exception {
+ context.SendActivity("Sorry - Null Reference Exception");
+ return CompletableFuture.completedFuture(null);
+ }
+ }, NullPointerException.class));
+
+
+ new TestFlow(adapter, (context) ->
+ {
+
+ if (context.getActivity().text() == "foo") {
+ try {
+ context.SendActivity(context.getActivity().text());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ if (context.getActivity().text() == "UnsupportedOperationException") {
+ throw new UnsupportedOperationException("Test");
+ }
+
+ }
+ )
+ .Send("foo")
+ .AssertReply("foo", "passthrough")
+ .Send("UnsupportedOperationException")
+ .AssertReply("Test")
+ .StartTest();
+
+ }
+
+/* @Test
+ // [TestCategory("Middleware")]
+ public void CatchException_TestMiddleware_SpecificExceptionType()
+{
+ TestAdapter adapter = new TestAdapter()
+ .Use(new CatchExceptionMiddleware((context, exception) =>
+ {
+ context.SendActivity("Generic Exception Caught");
+ return CompletableFuture.CompletedTask;
+ }))
+ .Use(new CatchExceptionMiddleware((context, exception) =>
+ {
+ context.SendActivity(exception.Message);
+ return CompletableFuture.CompletedTask;
+ }));
+
+
+ await new TestFlow(adapter, (context) =>
+ {
+ if (context.Activity.AsMessageActivity().Text == "foo")
+ {
+ context.SendActivity(context.Activity.AsMessageActivity().Text);
+ }
+
+ if (context.Activity.AsMessageActivity().Text == "NullReferenceException")
+ {
+ throw new NullReferenceException("Test");
+ }
+
+ return CompletableFuture.CompletedTask;
+ })
+ .Send("foo")
+ .AssertReply("foo", "passthrough")
+ .Send("NullReferenceException")
+ .AssertReply("Test")
+ .StartTest();
+}*/
+}
diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/DictionaryStorage.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/DictionaryStorage.java
index 289ced22c..b8525b1c3 100644
--- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/DictionaryStorage.java
+++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/DictionaryStorage.java
@@ -1,129 +1,130 @@
-package com.microsoft.bot.builder;
-
-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 java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-
-import static java.util.concurrent.CompletableFuture.completedFuture;
-
-/**
- * Models IStorage around a dictionary
- */
-public class DictionaryStorage implements Storage {
- private static ObjectMapper objectMapper;
-
- // TODO: Object needs to be defined
- private final Map memory;
- private final Object syncroot = new Object();
- private int _eTag = 0;
- private final String typeNameForNonEntity = "__type_name_";
-
- public DictionaryStorage() {
- this(null);
- }
- public DictionaryStorage(Map dictionary ) {
- DictionaryStorage.objectMapper = new ObjectMapper()
- .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
- .findAndRegisterModules();
- this.memory = (dictionary != null) ? dictionary : new HashMap();
- }
-
- public CompletableFuture Delete(String[] keys) {
- synchronized (this.syncroot) {
- for (String key : keys) {
- Object o = this.memory.get(key);
- this.memory.remove(o);
- }
- }
- return completedFuture(null);
- }
-
- @Override
- public CompletableFuture