");
+ }
+}
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..cad5b5525 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,765 @@
-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.AsyncHelper;
+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.schema.*;
+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.CompletionException;
+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.getServiceUrl(), 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, Activity 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, Activity 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.getServiceUrl(), 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.getType() == 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.getValue());
+ }
+ }
+
+ // 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 = null;
+
+ if (activity.getType().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.getValue();
+ Thread.sleep(delayMs);
+ //await(Task.Delay(delayMs));
+ // No need to create a response. One will be created below.
+ } else if (activity.getType().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.getType() == ActivityTypes.TRACE && !activity.getChannelId().equals("emulator")) {
+ // if it is a Trace activity we only send to the channel if it's the emulator.
+ } else if (!StringUtils.isEmpty(activity.getReplyToId())) {
+ ConnectorClient connectorClient = context.getServices().Get("ConnectorClient");
+ response = connectorClient.getConversations().replyToActivity(activity.getConversation().getId(), activity.getId(), activity);
+ } else {
+ ConnectorClient connectorClient = context.getServices().Get("ConnectorClient");
+ response = connectorClient.getConversations().sendToConversation(activity.getConversation().getId(), 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((activity.getId() == null) ? "" : activity.getId());
+ }
+
+ 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.getConversations().updateActivity(activity.getConversation().getId(), activity.getId(), 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.
+ * {@linkalso TurnContext.OnDeleteActivity(DeleteActivityHandler)}
+ */
+ public void DeleteActivity(TurnContext context, ConversationReference reference) {
+ RestConnectorClient connectorClient = context.getServices().Get("ConnectorClient");
+ try {
+ AsyncHelper.completableSingleFutureFromObservable(
+ connectorClient.getConversations().deleteConversationMemberAsync(
+ reference.getConversation().getId(), reference.getActivityId())).join();
+ } catch (CompletionException 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().getConversation() == null)
+ throw new IllegalArgumentException("BotFrameworkAdapter.deleteConversationMember(): missing conversation");
+
+ if (StringUtils.isEmpty(context.getActivity().getConversation().getId()))
+ throw new IllegalArgumentException("BotFrameworkAdapter.deleteConversationMember(): missing conversation.id");
+
+ ConnectorClient connectorClient = context.getServices().Get("ConnectorClient");
+
+ String conversationId = context.getActivity().getConversation().getId();
+
+ // 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().getId();
+
+ if (context.getActivity().getConversation() == null)
+ throw new IllegalArgumentException("BotFrameworkAdapter.GetActivityMembers(): missing conversation");
+
+ if (StringUtils.isEmpty((context.getActivity().getConversation().getId())))
+ throw new IllegalArgumentException("BotFrameworkAdapter.GetActivityMembers(): missing conversation.id");
+
+ ConnectorClient connectorClient = context.getServices().Get("ConnectorClient");
+ String conversationId = context.getActivity().getConversation().getId();
+
+ // 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().getConversation() == null)
+ throw new IllegalArgumentException("BotFrameworkAdapter.GetActivityMembers(): missing conversation");
+
+ if (StringUtils.isEmpty(context.getActivity().getConversation().getId()))
+ throw new IllegalArgumentException("BotFrameworkAdapter.GetActivityMembers(): missing conversation.id");
+
+ ConnectorClient connectorClient = context.getServices().Get("ConnectorClient");
+ String conversationId = context.getActivity().getConversation().getId();
+
+ // 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().getFrom() == null || StringUtils.isEmpty(context.getActivity().getFrom().getId()))
+ 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().getFrom().getId(), 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#getConversation}
+ * 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 conversations = connectorClient.getConversations();
+ CompletableFuture result = AsyncHelper.completableSingleFutureFromObservable(
+ conversations.createConversationAsync(conversationParameters));
+
+ ConversationResourceResponse response = result.join();
+
+ // Create a conversation update activity to represent the result.
+ Activity conversationUpdate = Activity.createConversationUpdateActivity();
+ conversationUpdate.setChannelId(channelId);
+ conversationUpdate.setTopicName(conversationParameters.getTopicName());
+ conversationUpdate.setServiceUrl(serviceUrl);
+ conversationUpdate.setMembersAdded(conversationParameters.getMembers());
+ conversationUpdate.setId((response.getActivityId() != null) ? response.getActivityId() : UUID.randomUUID().toString());
+ conversationUpdate.setConversation(new ConversationAccount(response.getId()));
+ conversationUpdate.setRecipient(conversationParameters.getBot());
+
+ 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));
+ }
+ }, ExecutorFactory.getExecutor());
+
+ }
+
+ protected CompletableFuture TrySetEmulatingOAuthCards(TurnContext turnContext) {
+ if (!isEmulatingOAuthCards &&
+ turnContext.getActivity().getChannelId().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().getServiceUrl());
+ }
+ 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.setRestRetryStrategy(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/ConversationState.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ConversationState.java
index a802dd94a..b96dfeec0 100644
--- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ConversationState.java
+++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ConversationState.java
@@ -1,49 +1,49 @@
-package com.microsoft.bot.builder;
-
-import com.microsoft.bot.builder.TurnContext;
-
-import java.util.function.Supplier;
-
-/**
- * Handles persistence of a conversation state object using the conversation ID as part of the key.
- * @param TState The type of the conversation state object.
- */
-public class ConversationState extends BotState
-{
- /**
- * The key to use to read and write this conversation state object to storage.
- */
- //
- // Note: Hard coded to maintain compatibility with C#
- // "ConversationState:{typeof(ConversationState).Namespace}.{typeof(ConversationState).Name}"
- public static String PropertyName() {
- return String.format("ConversationState:Microsoft.Bot.Builder.Core.Extensions.ConversationState`1");
- }
-
- /**
- * Creates a new {@link ConversationState{TState}} object.
- * @param storage The storage provider to use.
- * @param settings The state persistance options to use.
- */
- public ConversationState(Storage storage, Supplier extends TState> ctor) {
- this(storage, null, ctor);
- }
-
- public ConversationState(Storage storage, StateSettings settings, Supplier extends TState> ctor) {
- super(storage, PropertyName(),
- (context) -> {
- return String.format("conversation/%s/%s", context.getActivity().channelId(), context.getActivity().conversation().id());
- },
- ctor,
- settings);
- }
-
- /**
- * Gets the conversation state object from turn context.
- * @param context The context object for this turn.
- * @return The coversation state object.
- */
- public static TState Get(TurnContext context) throws IllegalArgumentException {
- return context.getServices().Get(PropertyName());
- }
-}
+package com.microsoft.bot.builder;
+
+import com.microsoft.bot.builder.TurnContext;
+
+import java.util.function.Supplier;
+
+/**
+ * Handles persistence of a conversation state object using the conversation ID as part of the key.
+ * @param TState The type of the conversation state object.
+ */
+public class ConversationState extends BotState
+{
+ /**
+ * The key to use to read and write this conversation state object to storage.
+ */
+ //
+ // Note: Hard coded to maintain compatibility with C#
+ // "ConversationState:{typeof(ConversationState).Namespace}.{typeof(ConversationState).Name}"
+ public static String PropertyName() {
+ return String.format("ConversationState:Microsoft.Bot.Builder.Core.Extensions.ConversationState`1");
+ }
+
+ /**
+ * Creates a new {@link ConversationState{TState}} object.
+ * @param storage The storage provider to use.
+ * @param settings The state persistance options to use.
+ */
+ public ConversationState(Storage storage, Supplier extends TState> ctor) {
+ this(storage, null, ctor);
+ }
+
+ public ConversationState(Storage storage, StateSettings settings, Supplier extends TState> ctor) {
+ super(storage, PropertyName(),
+ (context) -> {
+ return String.format("conversation/%s/%s", context.getActivity().getChannelId(), context.getActivity().getConversation().getId());
+ },
+ ctor,
+ settings);
+ }
+
+ /**
+ * Gets the conversation state object from turn context.
+ * @param context The context object for this turn.
+ * @return The coversation state object.
+ */
+ public static TState Get(TurnContext context) throws IllegalArgumentException {
+ return context.getServices().Get(PropertyName());
+ }
+}
diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/DeleteActivityHandler.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/DeleteActivityHandler.java
index db6c2568b..2d66c3cc0 100644
--- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/DeleteActivityHandler.java
+++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/DeleteActivityHandler.java
@@ -1,25 +1,25 @@
-package com.microsoft.bot.builder;
-
-import com.microsoft.bot.schema.models.ConversationReference;
-
-/**
- * A method that can participate in delete activity events for the current turn.
- * @param context The context object for the turn.
- * @param reference The conversation containing the activity.
- * @param next The delegate to call to continue event processing.
- * @return A task that represents the work queued to execute.
- * A handler calls the {@code next} delegate to pass control to
- * the next registered handler. If a handler doesn’t call the next delegate,
- * the adapter does not call any of the subsequent handlers and does not delete the
- *activity.
- * The conversation reference's {@link ConversationReference.ActivityId}
- * indicates the activity in the conversation to replace.
- *
- * {@linkalso BotAdapter}
- * {@linkalso SendActivitiesHandler}
- * {@linkalso UpdateActivityHandler}
- */
-@FunctionalInterface
-public interface DeleteActivityHandler {
- void handle(TurnContext context, ConversationReference reference, Runnable next) throws Exception;
-}
+package com.microsoft.bot.builder;
+
+import com.microsoft.bot.schema.ConversationReference;
+
+/**
+ * A method that can participate in delete activity events for the current turn.
+ * @param context The context object for the turn.
+ * @param reference The conversation containing the activity.
+ * @param next The delegate to call to continue event processing.
+ * @return A task that represents the work queued to execute.
+ * A handler calls the {@code next} delegate to pass control to
+ * the next registered handler. If a handler doesn’t call the next delegate,
+ * the adapter does not call any of the subsequent handlers and does not delete the
+ *activity.
+ * The conversation reference's {@link ConversationReference.ActivityId}
+ * indicates the activity in the conversation to replace.
+ *
+ * {@linkalso BotAdapter}
+ * {@linkalso SendActivitiesHandler}
+ * {@linkalso UpdateActivityHandler}
+ */
+@FunctionalInterface
+public interface DeleteActivityHandler {
+ void handle(TurnContext context, ConversationReference reference, Runnable next) throws Exception;
+}
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..4b35f77c5 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,297 @@
-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.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.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.getChannelId())) {
+ channel = new HashMap>();
+ this.channels.put(activity.getChannelId(), channel);
+ } else {
+ channel = this.channels.get(activity.getChannelId());
+ }
+
+ ArrayList transcript = null;
+
+
+ if (!channel.containsKey(activity.getConversation().getId())) {
+ transcript = new ArrayList();
+ channel.put(activity.getConversation().getId(), transcript);
+ } else {
+ transcript = channel.get(activity.getConversation().getId());
+ }
+
+ 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::getTimestamp))
+ .filter(a -> a.getTimestamp().compareTo(startDate) >= 0)
+ .filter(skipwhile(a -> !a.getId().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).getId());
+ }
+ } else {
+ List items = transcript.stream()
+ .sorted(Comparator.comparing(Activity::getTimestamp))
+ .filter(a -> a.getTimestamp().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).getId());
+ }
+ }
+ }
+
+ 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().getTimestamp();
+ // 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().getTimestamp();
+ // 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/SendActivitiesHandler.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/SendActivitiesHandler.java
index aff09c6f6..61baf4c3b 100644
--- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/SendActivitiesHandler.java
+++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/SendActivitiesHandler.java
@@ -1,13 +1,12 @@
-package com.microsoft.bot.builder;
-
-import com.microsoft.bot.builder.TurnContext;
-import com.microsoft.bot.schema.models.Activity;
-import com.microsoft.bot.schema.models.ResourceResponse;
-
-import java.util.List;
-import java.util.concurrent.Callable;
-
-@FunctionalInterface
-public interface SendActivitiesHandler {
- ResourceResponse[] handle(TurnContext context, List activities, Callable next) throws Exception;
-}
+package com.microsoft.bot.builder;
+
+import com.microsoft.bot.schema.Activity;
+import com.microsoft.bot.schema.ResourceResponse;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+
+@FunctionalInterface
+public interface SendActivitiesHandler {
+ ResourceResponse[] handle(TurnContext context, List activities, Callable next) throws Exception;
+}
diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TraceTranscriptLogger.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TraceTranscriptLogger.java
index 34b67f8ae..48e372934 100644
--- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TraceTranscriptLogger.java
+++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TraceTranscriptLogger.java
@@ -1,58 +1,45 @@
-package com.microsoft.bot.builder;
-
-
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.SerializationFeature;
-import com.microsoft.bot.schema.models.Activity;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.ForkJoinPool;
-import java.util.concurrent.ForkJoinWorkerThread;
-
-/**
- * Represents a transcript logger that writes activites to a object.
- */
-public class TraceTranscriptLogger implements TranscriptLogger {
- // https://github.com/FasterXML/jackson-databind/wiki/Serialization-Features
- private static ObjectMapper mapper = new ObjectMapper()
- .enable(SerializationFeature.INDENT_OUTPUT);
- private static final Logger logger = LoggerFactory.getLogger(TraceTranscriptLogger.class);
-
- ForkJoinPool.ForkJoinWorkerThreadFactory factory = new ForkJoinPool.ForkJoinWorkerThreadFactory()
- {
- @Override
- public ForkJoinWorkerThread newThread(ForkJoinPool pool)
- {
- final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
- worker.setName("BotTrace-" + worker.getPoolIndex());
- return worker;
- }
- };
-
- ExecutorService executor = new ForkJoinPool(Runtime.getRuntime().availableProcessors(), factory, null, true);
-
- /**
- * Log an activity to the transcript.
- *
- * @param activity The activity to transcribe.
- * @return A task that represents the work queued to execute.
- */
- @Override
- public void LogActivityAsync(Activity activity) {
- BotAssert.ActivityNotNull(activity);
- String event = null;
- try {
- event = mapper.writeValueAsString(activity);
- } catch (JsonProcessingException e) {
- e.printStackTrace();
- }
- this.logger.info(event);
- }
-}
+package com.microsoft.bot.builder;
+
+
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.microsoft.bot.schema.Activity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.ForkJoinWorkerThread;
+
+/**
+ * Represents a transcript logger that writes activites to a object.
+ */
+public class TraceTranscriptLogger implements TranscriptLogger {
+ // https://github.com/FasterXML/jackson-databind/wiki/Serialization-Features
+ private static ObjectMapper mapper = new ObjectMapper()
+ .enable(SerializationFeature.INDENT_OUTPUT);
+ private static final Logger logger = LoggerFactory.getLogger(TraceTranscriptLogger.class);
+
+ /**
+ * Log an activity to the transcript.
+ *
+ * @param activity The activity to transcribe.
+ * @return A task that represents the work queued to execute.
+ */
+ @Override
+ public void LogActivityAsync(Activity activity) {
+ BotAssert.ActivityNotNull(activity);
+ String event = null;
+ try {
+ event = mapper.writeValueAsString(activity);
+ } catch (JsonProcessingException e) {
+ e.printStackTrace();
+ }
+ this.logger.info(event);
+ }
+}
diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TranscriptLogger.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TranscriptLogger.java
index 4d4204af6..1c368d444 100644
--- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TranscriptLogger.java
+++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TranscriptLogger.java
@@ -1,21 +1,21 @@
-package com.microsoft.bot.builder;
-
-
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-
-import com.microsoft.bot.schema.models.Activity;
-
-/**
- * Transcript logger stores activities for conversations for recall.
- */
-public interface TranscriptLogger {
- /**
- * Log an activity to the transcript.
- *
- * @param activity The activity to transcribe.
- * @return A task that represents the work queued to execute.
- */
- void LogActivityAsync(Activity activity);
-}
+package com.microsoft.bot.builder;
+
+
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+
+import com.microsoft.bot.schema.Activity;
+
+/**
+ * Transcript logger stores activities for conversations for recall.
+ */
+public interface TranscriptLogger {
+ /**
+ * Log an activity to the transcript.
+ *
+ * @param activity The activity to transcribe.
+ * @return A task that represents the work queued to execute.
+ */
+ void LogActivityAsync(Activity activity);
+}
diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TranscriptLoggerMiddleware.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TranscriptLoggerMiddleware.java
index 1cae34104..3a32ce99c 100644
--- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TranscriptLoggerMiddleware.java
+++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TranscriptLoggerMiddleware.java
@@ -1,191 +1,190 @@
-package com.microsoft.bot.builder;
-
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.SerializationFeature;
-import com.microsoft.bot.schema.ActivityImpl;
-import com.microsoft.bot.schema.models.Activity;
-import com.microsoft.bot.schema.models.ActivityTypes;
-import com.microsoft.bot.schema.models.ResourceResponse;
-import org.apache.commons.lang3.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-
-import java.util.Queue;
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-
-/**
- * When added, this middleware will log incoming and outgoing activitites to a ITranscriptStore.
- */
-public class TranscriptLoggerMiddleware implements Middleware {
- // https://github.com/FasterXML/jackson-databind/wiki/Serialization-Features
- private static ObjectMapper mapper;
-
- static {
- mapper = new ObjectMapper()
- .enable(SerializationFeature.INDENT_OUTPUT);
- mapper.findAndRegisterModules();
- }
-
- private TranscriptLogger transcriptLogger;
- private static final Logger logger = LoggerFactory.getLogger(TranscriptLoggerMiddleware.class);
-
- private Queue transcript = new ConcurrentLinkedQueue();
-
- /**
- * Initializes a new instance of the class.
- *
- * @param transcriptLogger The transcript logger to use.
- */
- public TranscriptLoggerMiddleware(TranscriptLogger transcriptLogger) {
- if (transcriptLogger == null)
- throw new NullPointerException("TranscriptLoggerMiddleware requires a ITranscriptLogger implementation. ");
-
- this.transcriptLogger = transcriptLogger;
-
- }
-
- /**
- * initialization for middleware turn.
- *
- * @param context
- * @param next
- * @return
- */
- @Override
- public void OnTurn(TurnContext context, NextDelegate next) throws Exception {
- // log incoming activity at beginning of turn
- if (context.getActivity() != null) {
- JsonNode role = null;
- if (context.getActivity().from() == null) {
- throw new RuntimeException("Activity does not contain From field");
- }
- if (context.getActivity().from().properties().containsKey("role")) {
- role = context.getActivity().from().properties().get("role");
- }
-
- if (role == null || StringUtils.isBlank(role.asText())) {
- context.getActivity().from().properties().put("role", mapper.createObjectNode().with("user"));
- }
- Activity activityTemp = ActivityImpl.CloneActity(context.getActivity());
-
- LogActivity(ActivityImpl.CloneActity(context.getActivity()));
- }
-
- // hook up onSend pipeline
- context.OnSendActivities((ctx, activities, nextSend) ->
- {
-
- // run full pipeline
- ResourceResponse[] responses = new ResourceResponse[0];
- try {
- if (nextSend != null) {
- responses = nextSend.call();
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- for (Activity activity : activities) {
- LogActivity(ActivityImpl.CloneActity(activity));
- }
-
- return responses;
-
-
- });
-
- // hook up update activity pipeline
- context.OnUpdateActivity((ctx, activity, nextUpdate) ->
- {
-
- // run full pipeline
- ResourceResponse response = null;
- try {
- if (nextUpdate != null) {
- response = nextUpdate.call();
- }
- } catch (Exception e) {
- e.printStackTrace();
-
-
- throw new RuntimeException(String.format("Error on Logging.OnUpdateActivity : %s", e.toString()));
- }
-
- // add Message Update activity
- Activity updateActivity = ActivityImpl.CloneActity(activity);
- updateActivity.withType(ActivityTypes.MESSAGE_UPDATE);
- LogActivity(updateActivity);
- return response;
-
-
- });
-
- // hook up delete activity pipeline
- context.OnDeleteActivity((ctxt, reference, nextDel) -> {
- // run full pipeline
-
- try {
- if (nextDel != null) {
- logger.error(String.format("Transcript logActivity next delegate: %s)", nextDel));
- nextDel.run();
- }
- } catch (Exception e) {
- e.printStackTrace();
- logger.error(String.format("Transcript logActivity failed with %s (next delegate: %s)", e.toString(), nextDel));
- throw new RuntimeException(String.format("Transcript logActivity failed with %s", e.getMessage()));
-
- }
-
- // add MessageDelete activity
- // log as MessageDelete activity
- Activity deleteActivity = new Activity()
- .withType(ActivityTypes.MESSAGE_DELETE)
- .withId(reference.activityId())
- .applyConversationReference(reference, false);
-
- LogActivity(deleteActivity);
- return;
-
- });
-
-
- // process bot logic
- try {
- next.next();
- } catch (Exception e) {
- e.printStackTrace();
- throw new RuntimeException(String.format("Error on Logging.next : %s", e.toString()));
- }
-
- // flush transcript at end of turn
- while (!transcript.isEmpty()) {
- Activity activity = transcript.poll();
- try {
- this.transcriptLogger.LogActivityAsync(activity);
- } catch (RuntimeException err) {
- logger.error(String.format("Transcript poll failed : %1$s", err));
- }
- }
-
- }
-
-
- private void LogActivity(Activity activity) {
- if (activity.timestamp() == null) {
- activity.withTimestamp(DateTime.now(DateTimeZone.UTC));
- }
- transcript.offer(activity);
- }
-
-}
-
-
-
+package com.microsoft.bot.builder;
+
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.microsoft.bot.schema.Activity;
+import com.microsoft.bot.schema.ActivityTypes;
+import com.microsoft.bot.schema.ResourceResponse;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+
+/**
+ * When added, this middleware will log incoming and outgoing activitites to a ITranscriptStore.
+ */
+public class TranscriptLoggerMiddleware implements Middleware {
+ // https://github.com/FasterXML/jackson-databind/wiki/Serialization-Features
+ private static ObjectMapper mapper;
+
+ static {
+ mapper = new ObjectMapper()
+ .enable(SerializationFeature.INDENT_OUTPUT);
+ mapper.findAndRegisterModules();
+ }
+
+ private TranscriptLogger transcriptLogger;
+ private static final Logger logger = LoggerFactory.getLogger(TranscriptLoggerMiddleware.class);
+
+ private Queue transcript = new ConcurrentLinkedQueue();
+
+ /**
+ * Initializes a new instance of the class.
+ *
+ * @param transcriptLogger The transcript logger to use.
+ */
+ public TranscriptLoggerMiddleware(TranscriptLogger transcriptLogger) {
+ if (transcriptLogger == null)
+ throw new NullPointerException("TranscriptLoggerMiddleware requires a ITranscriptLogger implementation. ");
+
+ this.transcriptLogger = transcriptLogger;
+
+ }
+
+ /**
+ * initialization for middleware turn.
+ *
+ * @param context
+ * @param next
+ * @return
+ */
+ @Override
+ public void OnTurn(TurnContext context, NextDelegate next) throws Exception {
+ // log incoming activity at beginning of turn
+ if (context.getActivity() != null) {
+ JsonNode role = null;
+ if (context.getActivity().getFrom() == null) {
+ throw new RuntimeException("Activity does not contain From field");
+ }
+ if (context.getActivity().getFrom().getProperties().containsKey("role")) {
+ role = context.getActivity().getFrom().getProperties().get("role");
+ }
+
+ if (role == null || StringUtils.isBlank(role.asText())) {
+ context.getActivity().getFrom().getProperties().put("role", mapper.createObjectNode().with("user"));
+ }
+ Activity activityTemp = Activity.clone(context.getActivity());
+
+ LogActivity(Activity.clone(context.getActivity()));
+ }
+
+ // hook up onSend pipeline
+ context.OnSendActivities((ctx, activities, nextSend) ->
+ {
+
+ // run full pipeline
+ ResourceResponse[] responses = new ResourceResponse[0];
+ try {
+ if (nextSend != null) {
+ responses = nextSend.call();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ for (Activity activity : activities) {
+ LogActivity(Activity.clone(activity));
+ }
+
+ return responses;
+
+
+ });
+
+ // hook up update activity pipeline
+ context.OnUpdateActivity((ctx, activity, nextUpdate) ->
+ {
+
+ // run full pipeline
+ ResourceResponse response = null;
+ try {
+ if (nextUpdate != null) {
+ response = nextUpdate.call();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+
+
+ throw new RuntimeException(String.format("Error on Logging.OnUpdateActivity : %s", e.toString()));
+ }
+
+ // add Message Update activity
+ Activity updateActivity = Activity.clone(activity);
+ updateActivity.setType(ActivityTypes.MESSAGE_UPDATE);
+ LogActivity(updateActivity);
+ return response;
+
+
+ });
+
+ // hook up delete activity pipeline
+ context.OnDeleteActivity((ctxt, reference, nextDel) -> {
+ // run full pipeline
+
+ try {
+ if (nextDel != null) {
+ logger.error(String.format("Transcript logActivity next delegate: %s)", nextDel));
+ nextDel.run();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ logger.error(String.format("Transcript logActivity failed with %s (next delegate: %s)", e.toString(), nextDel));
+ throw new RuntimeException(String.format("Transcript logActivity failed with %s", e.getMessage()));
+
+ }
+
+ // add MessageDelete activity
+ // log as MessageDelete activity
+ Activity deleteActivity = new Activity(ActivityTypes.MESSAGE_DELETE) {{
+ setId(reference.getActivityId());
+ applyConversationReference(reference, false);
+ }};
+
+ LogActivity(deleteActivity);
+ return;
+
+ });
+
+
+ // process bot logic
+ try {
+ next.next();
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException(String.format("Error on Logging.next : %s", e.toString()));
+ }
+
+ // flush transcript at end of turn
+ while (!transcript.isEmpty()) {
+ Activity activity = transcript.poll();
+ try {
+ this.transcriptLogger.LogActivityAsync(activity);
+ } catch (RuntimeException err) {
+ logger.error(String.format("Transcript poll failed : %1$s", err));
+ }
+ }
+
+ }
+
+
+ private void LogActivity(Activity activity) {
+ if (activity.getTimestamp() == null) {
+ activity.setTimestamp(DateTime.now(DateTimeZone.UTC));
+ }
+ transcript.offer(activity);
+ }
+
+}
+
+
+
diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TranscriptStore.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TranscriptStore.java
index 085a294f2..2a5e856a1 100644
--- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TranscriptStore.java
+++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TranscriptStore.java
@@ -1,57 +1,57 @@
-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.util.concurrent.CompletableFuture;
-
-/**
- * Transcript logger stores activities for conversations for recall.
- */
-public interface TranscriptStore extends TranscriptLogger {
- /**
- * 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.
- */
-
- CompletableFuture> GetTranscriptActivitiesAsync(String channelId, String conversationId, String continuationToken);
-
- CompletableFuture> GetTranscriptActivitiesAsync(String channelId, String conversationId);
-
- //C# TO JAVA CONVERTER NOTE: Java does not support optional parameters. Overloaded method(s) are created above:
-//ORIGINAL LINE: Task> GetTranscriptActivitiesAsync(string channelId, string conversationId, string continuationToken = null, DateTime startDate = default(DateTime));
- CompletableFuture> GetTranscriptActivitiesAsync(String channelId, String conversationId, String continuationToken, DateTime localStartDate);
-
- /**
- * 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.
- */
-
- CompletableFuture> ListTranscriptsAsync(String channelId);
-
- //C# TO JAVA CONVERTER NOTE: Java does not support optional parameters. Overloaded method(s) are created above:
-//ORIGINAL LINE: Task> ListTranscriptsAsync(string channelId, string continuationToken = null);
- CompletableFuture> ListTranscriptsAsync(String channelId, String continuationToken);
-
- /**
- * 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.
- */
- CompletableFuture DeleteTranscriptAsync(String channelId, String conversationId);
-}
+package com.microsoft.bot.builder;
+
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+
+import com.microsoft.bot.schema.Activity;
+import org.joda.time.DateTime;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Transcript logger stores activities for conversations for recall.
+ */
+public interface TranscriptStore extends TranscriptLogger {
+ /**
+ * 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.
+ */
+
+ CompletableFuture> GetTranscriptActivitiesAsync(String channelId, String conversationId, String continuationToken);
+
+ CompletableFuture> GetTranscriptActivitiesAsync(String channelId, String conversationId);
+
+ //C# TO JAVA CONVERTER NOTE: Java does not support optional parameters. Overloaded method(s) are created above:
+//ORIGINAL LINE: Task> GetTranscriptActivitiesAsync(string channelId, string conversationId, string continuationToken = null, DateTime startDate = default(DateTime));
+ CompletableFuture> GetTranscriptActivitiesAsync(String channelId, String conversationId, String continuationToken, DateTime localStartDate);
+
+ /**
+ * 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.
+ */
+
+ CompletableFuture> ListTranscriptsAsync(String channelId);
+
+ //C# TO JAVA CONVERTER NOTE: Java does not support optional parameters. Overloaded method(s) are created above:
+//ORIGINAL LINE: Task> ListTranscriptsAsync(string channelId, string continuationToken = null);
+ CompletableFuture> ListTranscriptsAsync(String channelId, String continuationToken);
+
+ /**
+ * 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.
+ */
+ CompletableFuture DeleteTranscriptAsync(String channelId, String conversationId);
+}
diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContext.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContext.java
index edf6ced6a..90337f4be 100644
--- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContext.java
+++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContext.java
@@ -1,182 +1,214 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-package com.microsoft.bot.builder;
-
-/**
- * A method that can participate in send activity events for the current turn.
- * @param context The context object for the turn.
- * @param activities The activities to send.
- * @param next The delegate to call to continue event processing.
- * @return A task that represents the work queued to execute.
- * A handler calls the {@code next} delegate to pass control to
- * the next registered handler. If a handler doesn’t call the next delegate,
- * the adapter does not call any of the subsequent handlers and does not send the
- * {@code activities}.
- *
- * {@linkalso BotAdapter}
- * {@linkalso UpdateActivityHandler}
- * {@linkalso DeleteActivityHandler}
- */
-
-import com.microsoft.bot.schema.models.Activity;
-import com.microsoft.bot.schema.models.ConversationReference;
-import com.microsoft.bot.schema.models.ResourceResponse;
-
-import java.util.concurrent.CompletableFuture;
-
-//public delegate Task DeleteActivityHandler(TurnContext context, ConversationReference reference, Func next);
-
-/**
- * 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 interface TurnContext
-{
- /**
- * Gets the bot adapter that created this context object.
- */
- BotAdapter getAdapter();
-
- /**
- * Gets the services registered on this context object.
- */
- TurnContextServiceCollection getServices();
-
- /**
- * Incoming request
- */
- Activity getActivity();
-
-
-
- /**
- * 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.
- */
- boolean getResponded();
- void setResponded(boolean responded);
-
- /**
- * 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 "acceptingInput".
- * @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.
- * 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.
- *
- */
- ResourceResponse SendActivity(String textReplyToSend) throws Exception;
- ResourceResponse SendActivity(String textReplyToSend, String speak) throws Exception;
- //CompletableFuture SendActivity(String textReplyToSend, String speak = null, String inputHint = InputHints.AcceptingInput);
- ResourceResponse SendActivity(String textReplyToSend, String speak, String inputHint) throws Exception;
-
- /**
- * 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.
- * 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.
- */
- ResourceResponse SendActivity(Activity activity) throws Exception;
-
- /**
- * 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.
- */
- ResourceResponse[] SendActivities(Activity[] activities) throws Exception;
-
- /**
- * Replaces an existing activity.
- * @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.
- */
- ResourceResponse UpdateActivity(Activity activity) throws Exception;
-
- /**
- * Replaces an existing activity.
- * @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.
- */
- //CompletableFuture UpdateActivityAsync(Activity activity) throws Exception;
-
- /**
- * Deletes an existing activity.
- * @param activityId The ID of the activity to delete.
- * @return A task that represents the work queued to execute.
- */
- CompletableFuture DeleteActivity(String activityId) throws Exception;
-
- /**
- * Deletes an existing activity.
- * @param conversationReference The conversation containing the activity to delete.
- * @return A task that represents the work queued to execute.
- * The conversation reference's {@link ConversationReference.ActivityId}
- * indicates the activity in the conversation to delete.
- */
- void DeleteActivity(ConversationReference conversationReference) throws Exception;
-
- /**
- * Adds a response handler for send activity operations.
- * @param handler The handler to add to the context object.
- * @return The updated context object.
- * 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.
- *
- */
- TurnContext OnSendActivities(SendActivitiesHandler handler);
-
- /**
- * Adds a response handler for update activity operations.
- * @param handler The handler to add to the context object.
- * @return The updated context object.
- * 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.
- *
- */
- TurnContext OnUpdateActivity(UpdateActivityHandler handler);
-
- /**
- * Adds a response handler for delete activity operations.
- * @param handler The handler to add to the context object.
- * @return The updated context object.
- * @throws NullPointerException {@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.
- *
- */
- TurnContext OnDeleteActivity(DeleteActivityHandler handler);
-}
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.bot.builder;
+
+/**
+ * A method that can participate in send activity events for the current turn.
+ * @param context The context object for the turn.
+ * @param activities The activities to send.
+ * @param next The delegate to call to continue event processing.
+ * @return A task that represents the work queued to execute.
+ * A handler calls the {@code next} delegate to pass control to
+ * the next registered handler. If a handler doesn’t call the next delegate,
+ * the adapter does not call any of the subsequent handlers and does not send the
+ * {@code activities}.
+ *
+ * {@linkalso BotAdapter}
+ * {@linkalso UpdateActivityHandler}
+ * {@linkalso DeleteActivityHandler}
+ */
+
+import com.microsoft.bot.schema.Activity;
+import com.microsoft.bot.schema.ConversationReference;
+import com.microsoft.bot.schema.ResourceResponse;
+
+import java.util.concurrent.CompletableFuture;
+
+//public delegate Task DeleteActivityHandler(TurnContext context, ConversationReference reference, Func next);
+
+/**
+ * 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 interface TurnContext
+{
+ /**
+ * Gets the bot adapter that created this context object.
+ */
+ BotAdapter getAdapter();
+
+ /**
+ * Gets the services registered on this context object.
+ */
+ TurnContextServiceCollection getServices();
+
+ /**
+ * Incoming request
+ */
+ Activity getActivity();
+
+
+
+ /**
+ * 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.
+ */
+ boolean getResponded();
+ void setResponded(boolean responded);
+
+ /**
+ * Sends a message activity to the sender of the incoming activity.
+ * @param textReplyToSend The text of the message to send.
+ * @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.
+ * 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.
+ *
+ */
+ ResourceResponse SendActivity(String textReplyToSend) throws Exception;
+
+ /**
+ * 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.
+ * @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.
+ * 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.
+ *
+ */
+ ResourceResponse SendActivity(String textReplyToSend, String speak) throws Exception;
+ //CompletableFuture SendActivity(String textReplyToSend, String speak = null, String inputHint = InputHints.AcceptingInput);
+
+ /**
+ * 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 "acceptingInput".
+ * @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.
+ * 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.
+ *
+ */
+ ResourceResponse SendActivity(String textReplyToSend, String speak, String inputHint) throws Exception;
+
+ /**
+ * 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.
+ * 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.
+ */
+ ResourceResponse SendActivity(Activity activity) throws Exception;
+
+ /**
+ * 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.
+ */
+ ResourceResponse[] SendActivities(Activity[] activities) throws Exception;
+
+ /**
+ * Replaces an existing activity.
+ * @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.
+ */
+ ResourceResponse UpdateActivity(Activity activity) throws Exception;
+
+ /**
+ * Replaces an existing activity.
+ * @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.
+ */
+ //CompletableFuture UpdateActivityAsync(Activity activity) throws Exception;
+
+ /**
+ * Deletes an existing activity.
+ * @param activityId The ID of the activity to delete.
+ * @return A task that represents the work queued to execute.
+ */
+ CompletableFuture DeleteActivity(String activityId) throws Exception;
+
+ /**
+ * Deletes an existing activity.
+ * @param conversationReference The conversation containing the activity to delete.
+ * @return A task that represents the work queued to execute.
+ * The conversation reference's {@link ConversationReference#getActivityId}
+ * indicates the activity in the conversation to delete.
+ */
+ void DeleteActivity(ConversationReference conversationReference) throws Exception;
+
+ /**
+ * Adds a response handler for send activity operations.
+ * @param handler The handler to add to the context object.
+ * @return The updated context object.
+ * 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.
+ *
+ */
+ TurnContext OnSendActivities(SendActivitiesHandler handler);
+
+ /**
+ * Adds a response handler for update activity operations.
+ * @param handler The handler to add to the context object.
+ * @return The updated context object.
+ * 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.
+ *
+ */
+ TurnContext OnUpdateActivity(UpdateActivityHandler handler);
+
+ /**
+ * Adds a response handler for delete activity operations.
+ * @param handler The handler to add to the context object.
+ * @return The updated context object.
+ * @throws NullPointerException {@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.
+ *
+ */
+ TurnContext OnDeleteActivity(DeleteActivityHandler handler);
+}
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..57fc3dcb8 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,597 @@
-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.Activity;
+import com.microsoft.bot.schema.ConversationReference;
+import com.microsoft.bot.schema.InputHints;
+import com.microsoft.bot.schema.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.ActivityTypes.MESSAGE;
+import static com.microsoft.bot.schema.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 Activity 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, Activity 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.
+ * @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");
+
+ Activity activityToSend = (Activity) new Activity(MESSAGE) {{
+ setText(textReplyToSend);
+ }};
+ if (speak != null)
+ activityToSend.setSpeak(speak);
+
+ if (StringUtils.isNotEmpty(inputHint))
+ activityToSend.setInputHint(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 -> 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.getType() == 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 Activity[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.setId(response.getId());
+ }
+ }
+
+ // 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 com.microsoft.bot.connector.rest.ErrorResponseException The HTTP operation failed and the response contained additional information.
+ */
+ @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.setActivityId(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 com.microsoft.bot.connector.rest.ErrorResponseException The HTTP operation failed and the response contained additional information.
+ * The conversation reference's {@link ConversationReference#getActivityId}
+ * 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.setId(result.getId());
+ 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() {{
+ setActivityId(activity.getId());
+ setUser(activity.getFrom());
+ setBot(activity.getRecipient());
+ setConversation(activity.getConversation());
+ setChannelId(activity.getChannelId());
+ setServiceUrl(activity.getServiceUrl());
+ }};
+
+ 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.
+ */
+ public static Activity ApplyConversationReference(Activity activity, ConversationReference reference) {
+ return ApplyConversationReference(activity, reference, false);
+ }
+
+ /**
+ * 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, boolean isIncoming) {
+ activity.setChannelId(reference.getChannelId());
+ activity.setServiceUrl(reference.getServiceUrl());
+ activity.setConversation(reference.getConversation());
+
+ if (isIncoming) {
+ activity.setFrom(reference.getUser());
+ activity.setRecipient(reference.getBot());
+ if (reference.getActivityId() != null)
+ activity.setId(reference.getActivityId());
+ } else { // Outgoing
+ activity.setFrom(reference.getBot());
+ activity.setRecipient(reference.getUser());
+ if (reference.getActivityId() != null)
+ activity.setReplyToId(reference.getActivityId());
+ }
+ return activity;
+ }
+
+ public void close() throws Exception {
+ turnServices.close();
+ }
+}
diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/UpdateActivityHandler.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/UpdateActivityHandler.java
index fc62d63d7..4fc00d1be 100644
--- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/UpdateActivityHandler.java
+++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/UpdateActivityHandler.java
@@ -1,30 +1,28 @@
-package com.microsoft.bot.builder;
-
-import com.microsoft.bot.builder.TurnContext;
-import com.microsoft.bot.schema.models.Activity;
-import com.microsoft.bot.schema.models.ResourceResponse;
-
-import java.util.concurrent.Callable;
-
-/**
- * A method that can participate in update activity events for the current turn.
- * @param context The context object for the turn.
- * @param activity The replacement activity.
- * @param next The delegate to call to continue event processing.
- * @return A task that represents the work queued to execute.
- * A handler calls the {@code next} delegate to pass control to
- * the next registered handler. If a handler doesn’t call the next delegate,
- * the adapter does not call any of the subsequent handlers and does not update the
- * activity.
- * The activity's {@link Activity.Id} indicates the activity in the
- * conversation to replace.
- *
- * {@linkalso BotAdapter}
- * {@linkalso SendActivitiesHandler}
- * {@linkalso DeleteActivityHandler}
- */
-
-@FunctionalInterface
-public interface UpdateActivityHandler {
- ResourceResponse handle(TurnContext context, Activity activity, Callable next);
-}
+package com.microsoft.bot.builder;
+
+import com.microsoft.bot.schema.Activity;
+import com.microsoft.bot.schema.ResourceResponse;
+
+import java.util.concurrent.Callable;
+
+@FunctionalInterface
+public interface UpdateActivityHandler {
+ /**
+ * A method that can participate in update activity events for the current turn.
+ * @param context The context object for the turn.
+ * @param activity The replacement activity.
+ * @param next The delegate to call to continue event processing.
+ * @return A task that represents the work queued to execute.
+ * A handler calls the {@code next} delegate to pass control to
+ * the next registered handler. If a handler doesn’t call the next delegate,
+ * the adapter does not call any of the subsequent handlers and does not update the
+ * activity.
+ * The activity's {@link Activity#getId} indicates the activity in the
+ * conversation to replace.
+ *
+ * {@linkalso BotAdapter}
+ * {@linkalso SendActivitiesHandler}
+ * {@linkalso DeleteActivityHandler}
+ */
+ ResourceResponse handle(TurnContext context, Activity activity, Callable next);
+}
diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/UserState.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/UserState.java
index 1b37447a0..9088e1d9e 100644
--- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/UserState.java
+++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/UserState.java
@@ -1,50 +1,50 @@
-package com.microsoft.bot.builder;
-
-import com.microsoft.bot.builder.StateSettings;
-import com.microsoft.bot.builder.Storage;
-import com.microsoft.bot.builder.TurnContext;
-
-
-import java.util.function.Supplier;
-
-/**
- * Handles persistence of a user state object using the user ID as part of the key.
- * @param TState The type of the user state object.
- */
-public class UserState extends BotState
-{
- /**
- * The key to use to read and write this conversation state object to storage.
- */
- // Note: Hard coded to maintain compatibility with C#
- // "UserState:{typeof(UserState).Namespace}.{typeof(UserState).Name}"
- public static String PropertyName() {
- return String.format("UserState:Microsoft.Bot.Builder.Core.Extensions.UserState`1");
- }
-
- /**
- * Creates a new {@link UserState{TState}} object.
- * @param storage The storage provider to use.
- * @param settings The state persistance options to use.
- */
- public UserState(Storage storage, Supplier extends TState> ctor) {
- this(storage, ctor, null);
- }
- public UserState(Storage storage, Supplier extends TState> ctor, StateSettings settings) {
- super(storage, PropertyName(),
- (context) -> {
- return String.format("user/%s/%s", context.getActivity().channelId(), context.getActivity().conversation().id());
- },
- ctor,
- settings);
- }
-
- /**
- * Gets the user state object from turn context.
- * @param context The context object for this turn.
- * @return The user state object.
- */
- public static TState Get(TurnContext context) throws IllegalArgumentException {
- return context.getServices().Get(PropertyName());
- }
-}
+package com.microsoft.bot.builder;
+
+import com.microsoft.bot.builder.StateSettings;
+import com.microsoft.bot.builder.Storage;
+import com.microsoft.bot.builder.TurnContext;
+
+
+import java.util.function.Supplier;
+
+/**
+ * Handles persistence of a user state object using the user ID as part of the key.
+ * @param TState The type of the user state object.
+ */
+public class UserState extends BotState
+{
+ /**
+ * The key to use to read and write this conversation state object to storage.
+ */
+ // Note: Hard coded to maintain compatibility with C#
+ // "UserState:{typeof(UserState).Namespace}.{typeof(UserState).Name}"
+ public static String PropertyName() {
+ return String.format("UserState:Microsoft.Bot.Builder.Core.Extensions.UserState`1");
+ }
+
+ /**
+ * Creates a new {@link UserState{TState}} object.
+ * @param storage The storage provider to use.
+ * @param settings The state persistance options to use.
+ */
+ public UserState(Storage storage, Supplier extends TState> ctor) {
+ this(storage, ctor, null);
+ }
+ public UserState(Storage storage, Supplier extends TState> ctor, StateSettings settings) {
+ super(storage, PropertyName(),
+ (context) -> {
+ return String.format("user/%s/%s", context.getActivity().getChannelId(), context.getActivity().getConversation().getId());
+ },
+ ctor,
+ settings);
+ }
+
+ /**
+ * Gets the user state object from turn context.
+ * @param context The context object for this turn.
+ * @return The user state object.
+ */
+ public static TState Get(TurnContext context) throws IllegalArgumentException {
+ return context.getServices().Get(PropertyName());
+ }
+}
diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/adapters/TestAdapter.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/adapters/TestAdapter.java
index 7b2ea9935..1eaf8658d 100644
--- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/adapters/TestAdapter.java
+++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/adapters/TestAdapter.java
@@ -1,234 +1,237 @@
-package com.microsoft.bot.builder.adapters;
-
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-import com.microsoft.bot.builder.BotAdapter;
-import com.microsoft.bot.builder.Middleware;
-import com.microsoft.bot.builder.TurnContext;
-import com.microsoft.bot.builder.TurnContextImpl;
-import com.microsoft.bot.schema.ActivityImpl;
-import com.microsoft.bot.schema.models.*;
-import org.apache.commons.lang3.StringUtils;
-import org.joda.time.DateTime;
-
-import java.util.*;
-import java.util.concurrent.CompletableFuture;
-import java.util.function.Consumer;
-import java.util.function.Function;
-
-public class TestAdapter extends BotAdapter {
- private int nextId = 0;
- private final Queue botReplies = new LinkedList<>();
- private ConversationReference conversationReference;
-
- public TestAdapter() {
- this(null);
- }
-
-
- public TestAdapter(ConversationReference reference) {
- if (reference != null) {
- this.withConversationReference(reference);
- } else {
- this.withConversationReference(new ConversationReference()
- .withChannelId("test")
- .withServiceUrl("https://test.com"));
-
- this.conversationReference().withUser(new ChannelAccount()
- .withId("user1")
- .withName("User1"));
- this.conversationReference().withBot(new ChannelAccount()
- .withId("bot")
- .withName("Bot"));
- this.conversationReference().withConversation(new ConversationAccount()
- .withIsGroup(Boolean.FALSE)
- .withConversationType("convo1")
- .withId("Conversation1"));
- }
- }
-
- public Queue activeQueue() {
- return botReplies;
- }
-
- public TestAdapter Use(Middleware middleware) {
- super.Use(middleware);
- return this;
- }
-
- public void ProcessActivity(ActivityImpl activity,
- Consumer callback
- ) throws Exception {
- synchronized (this.conversationReference()) {
- // ready for next reply
- if (activity.type() == null)
- activity.withType(ActivityTypes.MESSAGE);
- activity.withChannelId(this.conversationReference().channelId());
- activity.withFrom(this.conversationReference().user());
- activity.withRecipient(this.conversationReference().bot());
- activity.withConversation(this.conversationReference().conversation());
- activity.withServiceUrl(this.conversationReference().serviceUrl());
- Integer next = this.nextId++;
- activity.withId(next.toString());
- }
- // Assume Default DateTime : DateTime(0)
- if (activity.timestamp() == null || activity.timestamp() == new DateTime(0))
- activity.withTimestamp(DateTime.now());
-
- try (TurnContextImpl context = new TurnContextImpl(this, activity)) {
- super.RunPipeline(context, callback);
- }
- return;
- }
-
- public ConversationReference conversationReference() {
- return conversationReference;
- }
-
- public void withConversationReference(ConversationReference conversationReference) {
- this.conversationReference = conversationReference;
- }
-
- @Override
- public ResourceResponse[] SendActivities(TurnContext context, Activity[] activities) throws InterruptedException {
- List responses = new LinkedList();
-
- for (Activity activity : activities) {
- if (StringUtils.isEmpty(activity.id()))
- activity.withId(UUID.randomUUID().toString());
-
- if (activity.timestamp() == null)
- activity.withTimestamp(DateTime.now());
-
- responses.add(new ResourceResponse().withId(activity.id()));
- // This is simulating DELAY
-
- System.out.println(String.format("TestAdapter:SendActivities(tid:%s):Count:%s", Thread.currentThread().getId(), activities.length));
- for (Activity act : activities) {
- System.out.printf(":--------\n: To:%s\n", act.recipient().name());
- System.out.printf(": From:%s\n", (act.from() == null) ? "No from set" : act.from().name());
- System.out.printf(": Text:%s\n:---------", (act.text() == null) ? "No text set" : act.text());
- }
- if (activity.type().toString().equals("delay")) {
- // The BotFrameworkAdapter and Console adapter implement this
- // hack directly in the POST method. Replicating that here
- // to keep the behavior as close as possible to facillitate
- // more realistic tests.
- int delayMs = (int) activity.value();
- Thread.sleep(delayMs);
- } else {
- synchronized (this.botReplies) {
- this.botReplies.add(activity);
- }
- }
- }
- return responses.toArray(new ResourceResponse[responses.size()]);
- }
-
-
- @Override
- public ResourceResponse UpdateActivity(TurnContext context, Activity activity) {
- synchronized (this.botReplies) {
- List replies = new ArrayList<>(botReplies);
- for (int i = 0; i < this.botReplies.size(); i++) {
- if (replies.get(i).id().equals(activity.id())) {
- replies.set(i, activity);
- this.botReplies.clear();
-
- for (Activity item : replies) {
- this.botReplies.add(item);
- }
- return new ResourceResponse().withId(activity.id());
- }
- }
- }
- return new ResourceResponse();
- }
-
- @Override
- public void DeleteActivity(TurnContext context, ConversationReference reference) {
- synchronized (this.botReplies) {
- ArrayList replies = new ArrayList<>(this.botReplies);
- for (int i = 0; i < this.botReplies.size(); i++) {
- if (replies.get(i).id().equals(reference.activityId())) {
- replies.remove(i);
- this.botReplies.clear();
- for (Activity item : replies) {
- this.botReplies.add(item);
- }
- break;
- }
- }
- }
- return;
- }
-
- /**
- * NOTE: this resets the queue, it doesn't actually maintain multiple converstion queues
- *
- * @param channelId
- * @param callback
- * @return
- */
- //@Override
- public CompletableFuture CreateConversation(String channelId, Function callback) {
- this.activeQueue().clear();
- MessageActivity update = MessageActivity.CreateConversationUpdateActivity();
-
- update.withConversation(new ConversationAccount().withId(UUID.randomUUID().toString()));
- TurnContextImpl context = new TurnContextImpl(this, (ActivityImpl) update);
- return callback.apply(context);
- }
-
- /**
- * Called by TestFlow to check next reply
- *
- * @return
- */
- public Activity GetNextReply() {
- synchronized (this.botReplies) {
- if (this.botReplies.size() > 0) {
- return this.botReplies.remove();
- }
- }
- return null;
- }
-
- /**
- * Called by TestFlow to get appropriate activity for conversationReference of testbot
- *
- * @param text
- * @return
- */
- public Activity MakeActivity() {
- return MakeActivity(null);
- }
-
- public ActivityImpl MakeActivity(String text) {
- Integer next = nextId++;
- ActivityImpl activity = (ActivityImpl) new ActivityImpl()
- .withType(ActivityTypes.MESSAGE)
- .withFrom(conversationReference().user())
- .withRecipient(conversationReference().bot())
- .withConversation(conversationReference().conversation())
- .withServiceUrl(conversationReference().serviceUrl())
- .withId(next.toString())
- .withText(text);
-
- return activity;
- }
-
-
- /**
- * Called by TestFlow to send text to the bot
- *
- * @param userSays
- * @return
- */
- public void SendTextToBot(String userSays, Consumer callback) throws Exception {
- this.ProcessActivity(this.MakeActivity(userSays), callback);
- }
-}
-
-
+package com.microsoft.bot.builder.adapters;
+
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+import com.microsoft.bot.builder.BotAdapter;
+import com.microsoft.bot.builder.Middleware;
+import com.microsoft.bot.builder.TurnContext;
+import com.microsoft.bot.builder.TurnContextImpl;
+import com.microsoft.bot.schema.Activity;
+import com.microsoft.bot.schema.*;
+import org.apache.commons.lang3.StringUtils;
+import org.joda.time.DateTime;
+
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class TestAdapter extends BotAdapter {
+ private int nextId = 0;
+ private final Queue botReplies = new LinkedList<>();
+ private ConversationReference conversationReference;
+
+ public TestAdapter() {
+ this(null);
+ }
+
+
+ public TestAdapter(ConversationReference reference) {
+ if (reference != null) {
+ this.setConversationReference(reference);
+ } else {
+ this.setConversationReference(new ConversationReference() {{
+ setChannelId("test");
+ setServiceUrl("https://test.com");
+ }});
+
+ this.conversationReference().setUser(new ChannelAccount() {{
+ setId("user1");
+ setName("User1");
+ }});
+ this.conversationReference().setBot(new ChannelAccount() {{
+ setId("bot");
+ setName("Bot");
+ }});
+ this.conversationReference().setConversation(new ConversationAccount() {{
+ setIsGroup(Boolean.FALSE);
+ setConversationType("convo1");
+ setId("Conversation1");
+ }});
+ }
+ }
+
+ public Queue