diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotAdapter.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotAdapter.java
index 05425255..0ba7ca40 100644
--- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotAdapter.java
+++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotAdapter.java
@@ -8,6 +8,7 @@ import com.microsoft.bot.schema.ConversationReference;
import com.microsoft.bot.schema.ConversationReferenceHelper;
import com.microsoft.bot.schema.ResourceResponse;
+import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.function.Function;
@@ -81,7 +82,7 @@ public abstract class BotAdapter {
* the receiving channel assigned to the activities.
* {@link TurnContext#onSendActivities(SendActivitiesHandler)}
*/
- public abstract CompletableFuture sendActivities(TurnContext context, Activity[] activities);
+ public abstract CompletableFuture sendActivities(TurnContext context, List activities);
/**
* When overridden in a derived class, replaces an existing activity in the
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 dc683f96..646ff88e 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
@@ -279,7 +279,7 @@ public class BotFrameworkAdapter extends BotAdapter {
*/
@SuppressWarnings("checkstyle:EmptyBlock")
@Override
- public CompletableFuture sendActivities(TurnContext context, Activity[] activities) {
+ public CompletableFuture sendActivities(TurnContext context, List activities) {
if (context == null) {
throw new IllegalArgumentException("context");
}
@@ -288,20 +288,20 @@ public class BotFrameworkAdapter extends BotAdapter {
throw new IllegalArgumentException("activities");
}
- if (activities.length == 0) {
+ if (activities.size() == 0) {
throw new IllegalArgumentException("Expecting one or more activities, but the array was empty.");
}
return CompletableFuture.supplyAsync(() -> {
- ResourceResponse[] responses = new ResourceResponse[activities.length];
+ ResourceResponse[] responses = new ResourceResponse[activities.size()];
/*
* 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];
+ for (int index = 0; index < activities.size(); index++) {
+ Activity activity = activities.get(index);
ResourceResponse response = null;
if (activity.isType(ActivityTypes.DELAY)) {
diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/DelegatingTurnContext.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/DelegatingTurnContext.java
index 3d4960a8..7887c02a 100644
--- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/DelegatingTurnContext.java
+++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/DelegatingTurnContext.java
@@ -5,8 +5,10 @@ package com.microsoft.bot.builder;
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 java.util.List;
import java.util.concurrent.CompletableFuture;
/**
@@ -50,7 +52,7 @@ public class DelegatingTurnContext implements TurnContext {
}
@Override
- public CompletableFuture sendActivity(String textReplyToSend, String speak, String inputHint) {
+ public CompletableFuture sendActivity(String textReplyToSend, String speak, InputHints inputHint) {
return innerTurnContext.sendActivity(textReplyToSend, speak, inputHint);
}
@@ -60,7 +62,7 @@ public class DelegatingTurnContext implements TurnContext {
}
@Override
- public CompletableFuture sendActivities(Activity[] activities) {
+ public CompletableFuture sendActivities(List activities) {
return innerTurnContext.sendActivities(activities);
}
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 8a2ecaa2..8b950275 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
@@ -4,8 +4,10 @@ package com.microsoft.bot.builder;
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 java.util.List;
import java.util.concurrent.CompletableFuture;
/**
@@ -117,7 +119,7 @@ public interface TurnContext {
* rate, volume, pronunciation, and pitch, specify {@code speak} in
* Speech Synthesis Markup Language (SSML) format.
*/
- CompletableFuture sendActivity(String textReplyToSend, String speak, String inputHint);
+ CompletableFuture sendActivity(String textReplyToSend, String speak, InputHints inputHint);
/**
* Sends an activity to the sender of the incoming activity.
@@ -139,12 +141,12 @@ public interface TurnContext {
* an array of {@link ResourceResponse} objects containing the IDs that
* the receiving channel assigned to the activities.
*/
- CompletableFuture sendActivities(Activity[] activities);
+ CompletableFuture sendActivities(List activities);
/**
* Replaces an existing activity.
*
- * @param activity New replacement activity.
+ * @param withActivity 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
@@ -152,7 +154,7 @@ public interface TurnContext {
*
Before calling this, set the ID of the replacement activity to the ID
* of the activity to replace.
*/
- CompletableFuture updateActivity(Activity activity);
+ CompletableFuture updateActivity(Activity withActivity);
/**
* Deletes an existing activity.
@@ -178,7 +180,7 @@ public interface TurnContext {
* @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,
+ * or {@link #sendActivities(List)} methods are called,
* the adapter calls the registered handlers in the order in which they were
* added to the context object.
*/
diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java
index 889c5f37..d1951a5d 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,133 +1,80 @@
-package com.microsoft.bot.builder;
-
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+package com.microsoft.bot.builder;
+
import com.microsoft.bot.schema.Activity;
+import com.microsoft.bot.schema.ActivityTypes;
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.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
-
-import static com.microsoft.bot.schema.ActivityTypes.MESSAGE;
-import static com.microsoft.bot.schema.ActivityTypes.TRACE;
-import static java.util.stream.Collectors.toList;
+import java.util.stream.Collectors;
/**
* 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}
+ * {@link Bot}
+ * {@link Middleware}
*/
public class TurnContextImpl implements TurnContext, AutoCloseable {
+ /**
+ * The bot adapter that created this context object.
+ */
private final BotAdapter adapter;
+
+ /**
+ * The activity associated with this turn; or null when processing a proactive message.
+ */
private final Activity activity;
+
private final List onSendActivities = new ArrayList();
private final List onUpdateActivity = new ArrayList();
private final List onDeleteActivity = new ArrayList();
+
+ /**
+ * The services registered on this context object.
+ */
private final TurnContextStateCollection turnState;
+
+ /**
+ * Indicates whether at least one response was sent for the current turn.
+ */
private Boolean responded = false;
/**
* Creates a context object.
*
- * @param adapter The adapter creating the context.
- * @param activity The incoming activity for the turn;
+ * @param withAdapter The adapter creating the context.
+ * @param withActivity 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)
+ public TurnContextImpl(BotAdapter withAdapter, Activity withActivity) {
+ if (withAdapter == null) {
throw new IllegalArgumentException("adapter");
- this.adapter = adapter;
- if (activity == null)
+ }
+ adapter = withAdapter;
+
+ if (withActivity == null) {
throw new IllegalArgumentException("activity");
- this.activity = activity;
+ }
+ activity = withActivity;
turnState = new TurnContextStateCollection();
}
- /**
- * 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;
- }
-
/**
* Adds a response handler for send activity operations.
*
@@ -135,16 +82,17 @@ public class TurnContextImpl implements TurnContext, AutoCloseable {
* @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,
+ * or {@link #sendActivities(List)} methods are called,
* the adapter calls the registered handlers in the order in which they were
* added to the context object.
*/
@Override
public TurnContext onSendActivities(SendActivitiesHandler handler) {
- if (handler == null)
+ if (handler == null) {
throw new IllegalArgumentException("handler");
+ }
- this.onSendActivities.add(handler);
+ onSendActivities.add(handler);
return this;
}
@@ -160,10 +108,11 @@ public class TurnContextImpl implements TurnContext, AutoCloseable {
*/
@Override
public TurnContext onUpdateActivity(UpdateActivityHandler handler) {
- if (handler == null)
+ if (handler == null) {
throw new IllegalArgumentException("handler");
+ }
- this.onUpdateActivity.add(handler);
+ onUpdateActivity.add(handler);
return this;
}
@@ -179,10 +128,11 @@ public class TurnContextImpl implements TurnContext, AutoCloseable {
*/
@Override
public TurnContext onDeleteActivity(DeleteActivityHandler handler) {
- if (handler == null)
+ if (handler == null) {
throw new IllegalArgumentException("handler");
+ }
- this.onDeleteActivity.add(handler);
+ onDeleteActivity.add(handler);
return this;
}
@@ -213,58 +163,90 @@ public class TurnContextImpl implements TurnContext, AutoCloseable {
* 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}.
*/
@Override
public boolean getResponded() {
- return this.responded;
- }
-
- private void setResponded(boolean responded) {
- if (responded == false) {
- throw new IllegalArgumentException("TurnContext: cannot set 'responded' to a value of 'false'.");
- }
- this.responded = true;
+ return responded;
}
/**
* Sends a message activity to the sender of the incoming activity.
*
+ *
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}.
+ *
* @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 CompletableFuture sendActivity(String textReplyToSend) {
return sendActivity(textReplyToSend, null, null);
}
+ /**
+ * Sends a message activity to the sender of the incoming activity.
+ *
+ *
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}.
+ *
+ * @param textReplyToSend The text of the message to send.
+ * @param speak To control various characteristics of your bot's speech such as voice
+ * rate, volume, pronunciation, and pitch, specify Speech Synthesis Markup
+ * Language (SSML) format.
+ * @return A task that represents the work queued to execute.
+ * @throws IllegalArgumentException {@code textReplyToSend} is {@code null} or whitespace.
+ */
@Override
public CompletableFuture sendActivity(String textReplyToSend, String speak) {
return sendActivity(textReplyToSend, speak, null);
}
+ /**
+ * Sends a message activity to the sender of the incoming activity.
+ *
+ *
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}.
+ *
+ * @param textReplyToSend The text of the message to send.
+ * @param speak To control various characteristics of your bot's speech such as voice
+ * rate, volume, pronunciation, and pitch, specify Speech Synthesis Markup
+ * Language (SSML) format.
+ * @param inputHint (Optional) Input hint.
+ * @return A task that represents the work queued to execute.
+ * @throws IllegalArgumentException {@code textReplyToSend} is {@code null} or whitespace.
+ */
@Override
- public CompletableFuture sendActivity(String textReplyToSend, String speak, String inputHint) {
- if (StringUtils.isEmpty(textReplyToSend))
+ public CompletableFuture sendActivity(String textReplyToSend,
+ String speak,
+ InputHints inputHint) {
+ if (StringUtils.isEmpty(textReplyToSend)) {
throw new IllegalArgumentException("textReplyToSend");
+ }
- Activity activityToSend = new Activity(MESSAGE) {{
+ Activity activityToSend = new Activity(ActivityTypes.MESSAGE) {{
setText(textReplyToSend);
}};
- if (speak != null)
- activityToSend.setSpeak(speak);
- if (StringUtils.isNotEmpty(inputHint))
- activityToSend.setInputHint(InputHints.fromString(inputHint));
+ if (StringUtils.isNotEmpty(speak)) {
+ activityToSend.setSpeak(speak);
+ }
+
+ if (inputHint != null) {
+ activityToSend.setInputHint(inputHint);
+ }
return sendActivity(activityToSend);
}
@@ -281,15 +263,14 @@ public class TurnContextImpl implements TurnContext, AutoCloseable {
*/
@Override
public CompletableFuture sendActivity(Activity activity) {
- if (activity == null) {
- throw new IllegalArgumentException("activity");
- }
+ BotAssert.activityNotNull(activity);
- Activity[] activities = {activity};
- return sendActivities(activities)
+ return sendActivities(Collections.singletonList(activity))
.thenApply(resourceResponses -> {
if (resourceResponses == null || resourceResponses.length == 0) {
- return null;
+ // It's possible an interceptor prevented the activity from having been sent.
+ // Just return an empty response in that case.
+ return new ResourceResponse();
}
return resourceResponses[0];
});
@@ -305,201 +286,104 @@ public class TurnContextImpl implements TurnContext, AutoCloseable {
* the receiving channel assigned to the activities.
*/
@Override
- public CompletableFuture sendActivities(Activity[] activities) {
- // 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);
+ public CompletableFuture sendActivities(List activities) {
+ if (activities == null || activities.size() == 0) {
+ throw new IllegalArgumentException("activities");
}
- // Convert the IActivities to Activities.
- List activityArray = Arrays.stream(activities).map(input -> input).collect(toList());
+ // Bind the relevant Conversation Reference properties, such as URLs and
+ // ChannelId's, to the activities we're about to send.
+ ConversationReference cr = activity.getConversationReference();
- // Create the list used by the recursive methods.
- List activityList = new ArrayList(activityArray);
+ // Buffer the incoming activities into a List since we allow the set to be manipulated by the callbacks
+ // Bind the relevant Conversation Reference properties, such as URLs and
+ // ChannelId's, to the activity we're about to send
+ List bufferedActivities = activities.stream()
+ .map(a -> a.applyConversationReference(cr)).collect(Collectors.toList());
- Supplier> actuallySendStuff = () -> {
- // 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.
- return getAdapter().sendActivities(this, activityList.toArray(new Activity[activityList.size()]))
- .thenApply(responses -> {
- 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 (onSendActivities.size() == 0) {
+ return sendActivitiesThroughAdapter(bufferedActivities);
+ }
- // 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.
- if (activityList.stream().anyMatch((a) -> a.getType() == TRACE)) {
- this.setResponded(true);
- }
- return responses;
- });
- };
+ return sendActivitiesThroughCallbackPipeline(bufferedActivities, 0);
+ }
- List act_list = new ArrayList<>(activityList);
- return sendActivitiesInternal(act_list, onSendActivities.iterator(), actuallySendStuff);
+ private CompletableFuture sendActivitiesThroughAdapter(List activities) {
+ return adapter.sendActivities(this, activities)
+ .thenApply(responses -> {
+ boolean sentNonTraceActivity = false;
+
+ for (int index = 0; index < responses.length; index++) {
+ Activity activity = activities.get(index);
+ activity.setId(responses[index].getId());
+ sentNonTraceActivity |= !activity.isType(ActivityTypes.TRACE);
+ }
+
+ if (sentNonTraceActivity) {
+ responded = true;
+ }
+
+ return responses;
+ });
+ }
+
+ private CompletableFuture sendActivitiesThroughCallbackPipeline(List activities,
+ int nextCallbackIndex) {
+
+ if (nextCallbackIndex == onSendActivities.size()) {
+ return sendActivitiesThroughAdapter(activities);
+ }
+
+ return onSendActivities.get(nextCallbackIndex).invoke(this,
+ activities, () -> sendActivitiesThroughCallbackPipeline(activities, nextCallbackIndex + 1));
}
/**
* Replaces an existing activity.
*
- * @param activity New replacement activity.
+ * @param withActivity 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.
+ * @throws com.microsoft.bot.connector.rest.ErrorResponseException The HTTP operation failed and the
+ * response contained additional information.
*/
@Override
- public CompletableFuture updateActivity(Activity activity) {
- Supplier> ActuallyUpdateStuff = () -> {
- return 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) {
- if (StringUtils.isWhitespace(activityId) || activityId == null) {
- throw new IllegalArgumentException("activityId");
- }
-
- ConversationReference cr = getConversationReference(getActivity());
- cr.setActivityId(activityId);
-
- Supplier> ActuallyDeleteStuff = () ->
- getAdapter().deleteActivity(this, cr);
-
- return deleteActivityInternal(cr, onDeleteActivity.iterator(), ActuallyDeleteStuff);
- }
-
- /**
- * 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.
- */
- @Override
- public CompletableFuture deleteActivity(ConversationReference conversationReference) {
- if (conversationReference == null)
- throw new IllegalArgumentException("conversationReference");
-
- Supplier> ActuallyDeleteStuff = () ->
- getAdapter().deleteActivity(this, conversationReference);
-
- return deleteActivityInternal(conversationReference, onDeleteActivity.iterator(), ActuallyDeleteStuff);
- }
-
- private CompletableFuture sendActivitiesInternal(
- List activities,
- Iterator sendHandlers,
- Supplier> callAtBottom) {
-
- if (activities == null) {
- throw new IllegalArgumentException("activities");
- }
- if (sendHandlers == null) {
- throw new IllegalArgumentException("sendHandlers");
- }
-
- if (!sendHandlers.hasNext()) { // No middleware to run.
- if (callAtBottom != null) {
- return callAtBottom.get();
- }
- return CompletableFuture.completedFuture(new ResourceResponse[0]);
- }
-
- // Default to "No more Middleware after this".
- Supplier> 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.invoke(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 CompletableFuture updateActivityInternal(Activity activity,
- Iterator updateHandlers,
- Supplier> callAtBottom) {
+ public CompletableFuture updateActivity(Activity withActivity) {
BotAssert.activityNotNull(activity);
- if (updateHandlers == null)
- throw new IllegalArgumentException("updateHandlers");
- if (false == updateHandlers.hasNext()) { // No middleware to run.
+ ConversationReference conversationReference = activity.getConversationReference();
+ withActivity.applyConversationReference(conversationReference);
+
+ Supplier> actuallyUpdateStuff =
+ () -> getAdapter().updateActivity(this, withActivity);
+
+ return updateActivityInternal(withActivity, onUpdateActivity.iterator(), actuallyUpdateStuff);
+ }
+
+ private CompletableFuture updateActivityInternal(
+ Activity activity,
+ Iterator updateHandlers,
+ Supplier> callAtBottom) {
+
+ BotAssert.activityNotNull(activity);
+ if (updateHandlers == null) {
+ throw new IllegalArgumentException("updateHandlers");
+ }
+
+ // No middleware to run.
+ if (!updateHandlers.hasNext()) {
if (callAtBottom != null) {
return callAtBottom.get();
}
- return null;
+ return CompletableFuture.completedFuture(null);
}
// Default to "No more Middleware after this".
Supplier> 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())
+ if (updateHandlers.hasNext()) {
updateHandlers.next();
+ }
return updateActivityInternal(activity, updateHandlers, callAtBottom)
.thenApply(resourceResponse -> {
@@ -513,14 +397,57 @@ public class TurnContextImpl implements TurnContext, AutoCloseable {
return toCall.invoke(this, activity, next);
}
+ /**
+ * Deletes an existing activity.
+ *
+ * @param activityId The ID of the activity to delete.
+ * @return A task that represents the work queued to execute.
+ */
+ public CompletableFuture deleteActivity(String activityId) {
+ if (StringUtils.isWhitespace(activityId) || StringUtils.isEmpty(activityId)) {
+ throw new IllegalArgumentException("activityId");
+ }
+
+ ConversationReference cr = activity.getConversationReference();
+ cr.setActivityId(activityId);
+
+ Supplier> actuallyDeleteStuff = () ->
+ getAdapter().deleteActivity(this, cr);
+
+ return deleteActivityInternal(cr, onDeleteActivity.iterator(), actuallyDeleteStuff);
+ }
+
+ /**
+ * Deletes an existing activity.
+ *
+ * The conversation reference's {@link ConversationReference#getActivityId}
+ * indicates the activity in the conversation to delete.
+ *
+ * @param conversationReference The conversation containing the activity to delete.
+ * @return A task that represents the work queued to execute.
+ */
+ @Override
+ public CompletableFuture deleteActivity(ConversationReference conversationReference) {
+ if (conversationReference == null) {
+ throw new IllegalArgumentException("conversationReference");
+ }
+
+ Supplier> actuallyDeleteStuff = () ->
+ getAdapter().deleteActivity(this, conversationReference);
+
+ return deleteActivityInternal(conversationReference, onDeleteActivity.iterator(), actuallyDeleteStuff);
+ }
+
private CompletableFuture deleteActivityInternal(ConversationReference cr,
Iterator deleteHandlers,
Supplier> callAtBottom) {
BotAssert.conversationReferenceNotNull(cr);
- if (deleteHandlers == null)
+ if (deleteHandlers == null) {
throw new IllegalArgumentException("deleteHandlers");
+ }
- if (!deleteHandlers.hasNext()) { // No middleware to run.
+ // No middleware to run.
+ if (!deleteHandlers.hasNext()) {
if (callAtBottom != null) {
return callAtBottom.get();
}
@@ -531,10 +458,9 @@ public class TurnContextImpl implements TurnContext, AutoCloseable {
Supplier> 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())
+ if (deleteHandlers.hasNext()) {
deleteHandlers.next();
+ }
return deleteActivityInternal(cr, deleteHandlers, callAtBottom);
};
@@ -544,6 +470,16 @@ public class TurnContextImpl implements TurnContext, AutoCloseable {
return toCall.invoke(this, cr, next);
}
+ @Override
+ public void finalize() {
+ try {
+ close();
+ } catch (Exception e) {
+
+ }
+ }
+
+ @Override
public void close() throws Exception {
turnState.close();
}
diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextStateCollection.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextStateCollection.java
index 1c3c8d9b..b5649d7d 100644
--- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextStateCollection.java
+++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextStateCollection.java
@@ -3,6 +3,8 @@
package com.microsoft.bot.builder;
+import com.microsoft.bot.connector.ConnectorClient;
+
import java.util.HashMap;
import java.util.Map;
@@ -71,6 +73,9 @@ public class TurnContextStateCollection extends HashMap implemen
public void close() throws Exception {
for (Map.Entry entry : entrySet()) {
if (entry.getValue() instanceof AutoCloseable) {
+ if (entry.getValue() instanceof ConnectorClient) {
+ continue;
+ }
((AutoCloseable) entry.getValue()).close();
}
}
diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java
index 04469225..c4a6baae 100644
--- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java
+++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java
@@ -279,7 +279,7 @@ public class ActivityHandlerTests {
private static class NotImplementedAdapter extends BotAdapter {
@Override
- public CompletableFuture sendActivities(TurnContext context, Activity[] activities) {
+ public CompletableFuture sendActivities(TurnContext context, List activities) {
throw new NotImplementedException();
}
diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotAdapterTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotAdapterTests.java
index 4a9cfcbe..1e9183a1 100644
--- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotAdapterTests.java
+++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotAdapterTests.java
@@ -9,6 +9,7 @@ import org.apache.commons.lang3.StringUtils;
import org.junit.Assert;
import org.junit.Test;
+import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
@@ -28,7 +29,7 @@ public class BotAdapterTests {
@Test
public void PassResourceResponsesThrough() {
- Consumer validateResponse = (activities) -> {
+ Consumer> validateResponse = (activities) -> {
// no need to do anything.
};
diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/SimpleAdapter.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/SimpleAdapter.java
index e584025b..cdd273bc 100644
--- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/SimpleAdapter.java
+++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/SimpleAdapter.java
@@ -14,20 +14,20 @@ import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
public class SimpleAdapter extends BotAdapter {
- private Consumer callOnSend = null;
+ private Consumer> callOnSend = null;
private Consumer callOnUpdate = null;
private Consumer callOnDelete = null;
// Callback Function but doesn't need to be. Avoid java legacy type erasure
- public SimpleAdapter(Consumer callOnSend) {
+ public SimpleAdapter(Consumer> callOnSend) {
this(callOnSend, null, null);
}
- public SimpleAdapter(Consumer callOnSend, Consumer callOnUpdate) {
+ public SimpleAdapter(Consumer> callOnSend, Consumer callOnUpdate) {
this(callOnSend, callOnUpdate, null);
}
- public SimpleAdapter(Consumer callOnSend, Consumer callOnUpdate, Consumer callOnDelete) {
+ public SimpleAdapter(Consumer> callOnSend, Consumer callOnUpdate, Consumer callOnDelete) {
this.callOnSend = callOnSend;
this.callOnUpdate = callOnUpdate;
this.callOnDelete = callOnDelete;
@@ -39,9 +39,9 @@ public class SimpleAdapter extends BotAdapter {
@Override
- public CompletableFuture sendActivities(TurnContext context, Activity[] activities) {
+ public CompletableFuture sendActivities(TurnContext context, List activities) {
Assert.assertNotNull("SimpleAdapter.deleteActivity: missing reference", activities);
- Assert.assertTrue("SimpleAdapter.sendActivities: empty activities array.", activities.length > 0);
+ Assert.assertTrue("SimpleAdapter.sendActivities: empty activities array.", activities.size() > 0);
if (this.callOnSend != null)
this.callOnSend.accept(activities);
@@ -67,7 +67,7 @@ public class SimpleAdapter extends BotAdapter {
Assert.assertNotNull("SimpleAdapter.deleteActivity: missing reference", reference);
if (callOnDelete != null)
this.callOnDelete.accept(reference);
- return null;
+ return CompletableFuture.completedFuture(null);
}
diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TurnContextTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TurnContextTests.java
index 2a79e582..e449f119 100644
--- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TurnContextTests.java
+++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TurnContextTests.java
@@ -3,510 +3,615 @@
package com.microsoft.bot.builder;
-//[TestClass]
-//[TestCategory("Middleware")]
-//public class TurnContextTests extends BotConnectorTestBase {
+import com.microsoft.azure.AzureClient;
+import com.microsoft.bot.builder.adapters.TestAdapter;
+import com.microsoft.bot.builder.adapters.TestFlow;
+import com.microsoft.bot.connector.Attachments;
+import com.microsoft.bot.connector.ConnectorClient;
+import com.microsoft.bot.connector.Conversations;
+import com.microsoft.bot.schema.Activity;
+import com.microsoft.bot.schema.ActivityTypes;
+import com.microsoft.bot.schema.ConversationAccount;
+import com.microsoft.bot.schema.ConversationReference;
+import com.microsoft.bot.schema.ResourceResponse;
+import com.microsoft.rest.RestClient;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+
public class TurnContextTests {
-/*
- @Test
- public CompletableFuture ConstructorNullAdapter()
- {
- //TurnContext c = new TurnContext(null, new Activity());
- //Assert.Fail("Should Fail due to null Adapter");
+ @Test(expected = IllegalArgumentException.class)
+ public void ConstructorNullAdapter() {
+ new TurnContextImpl(null, new Activity(ActivityTypes.MESSAGE));
+ Assert.fail("Should Fail due to null Adapter");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void ConstructorNullActivity() {
+ new TurnContextImpl(new TestAdapter(), null);
+ Assert.fail("Should Fail due to null Activity");
}
@Test
- public CompletableFuture ConstructorNullActivity()
- {
- //TestAdapter a = new TestAdapter();
- //TurnContext c = new TurnContext(a, null);
- //Assert.Fail("Should Fail due to null Activty");
+ public void Constructor() {
+ new TurnContextImpl(new TestAdapter(), new Activity(ActivityTypes.MESSAGE));
}
- [TestMethod]
- public async Task Constructor()
- {
- TurnContext c = new TurnContext(new TestAdapter(), new Activity());
- Assert.IsNotNull(c);
+ @Test
+ public void CacheValueUsingSetAndGet() {
+ TestAdapter adapter = new TestAdapter();
+ new TestFlow(adapter, (turnContext -> {
+ switch (turnContext.getActivity().getText()) {
+ case "count":
+ return turnContext.sendActivity(turnContext.getActivity().createReply("one"))
+ .thenCompose(resourceResponse -> turnContext.sendActivity(turnContext.getActivity().createReply("two")))
+ .thenCompose(resourceResponse -> turnContext.sendActivity(turnContext.getActivity().createReply("two")))
+ .thenApply(resourceResponse -> null);
+
+ case "ignore":
+ break;
+
+ case "TestResponded":
+ if (turnContext.getResponded()) {
+ throw new RuntimeException("Responded is true");
+ }
+
+ return turnContext.sendActivity(turnContext.getActivity().createReply("one"))
+ .thenApply(resourceResponse -> {
+ if (!turnContext.getResponded()) {
+ throw new RuntimeException("Responded is false");
+ }
+ return null;
+ });
+
+ default:
+ return turnContext.sendActivity(turnContext.getActivity().createReply("echo:" + turnContext.getActivity().getText()))
+ .thenApply(resourceResponse -> null);
+ }
+
+ return CompletableFuture.completedFuture(null);
+ }))
+ .send("TestResponded")
+ .startTest();
}
- [TestMethod]
- public async Task RespondedIsFalse()
- {
- TurnContext c = new TurnContext(new TestAdapter(), new Activity());
- Assert.IsFalse(c.Responded);
+ @Test(expected = IllegalArgumentException.class)
+ public void GetThrowsOnNullKey() {
+ TurnContext c = new TurnContextImpl(new SimpleAdapter(), new Activity(ActivityTypes.MESSAGE));
+ Object o = c.getTurnState().get((String)null);
}
- [TestMethod]
- [ExpectedException(typeof(ArgumentException))]
- public async Task UnableToSetRespondedToFalse()
- {
- TurnContext c = new TurnContext(new TestAdapter(), new Activity())
- {
- Responded = false // should throw
- };
- Assert.Fail("Should have thrown");
+ @Test
+ public void GetReturnsNullOnEmptyKey() {
+ TurnContext c = new TurnContextImpl(new SimpleAdapter(), new Activity(ActivityTypes.MESSAGE));
+ Object service = c.getTurnState().get("");
+ Assert.assertNull("Should not have found a service under an empty key", service);
}
- [TestMethod]
- public async Task CacheValueUsingSetAndGet()
- {
- var adapter = new TestAdapter();
- await new TestFlow(adapter, MyBotLogic)
- .Send("TestResponded")
- .StartTest();
+ @Test
+ public void GetReturnsNullWithUnknownKey() {
+ TurnContext c = new TurnContextImpl(new SimpleAdapter(), new Activity(ActivityTypes.MESSAGE));
+ Object service = c.getTurnState().get("test");
+ Assert.assertNull("Should not have found a service with unknown key", service);
}
- [TestMethod]
- [ExpectedException(typeof(ArgumentNullException))]
- public async Task GetThrowsOnNullKey()
- {
- TurnContext c = new TurnContext(new SimpleAdapter(), new Activity());
- c.Services.Get