This commit is contained in:
Tracy Boehrer 2019-09-18 15:31:51 -05:00
Родитель 7ae65c6a21
Коммит b774350074
15 изменённых файлов: 802 добавлений и 731 удалений

Просмотреть файл

@ -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<ResourceResponse[]> sendActivities(TurnContext context, Activity[] activities);
public abstract CompletableFuture<ResourceResponse[]> sendActivities(TurnContext context, List<Activity> activities);
/**
* When overridden in a derived class, replaces an existing activity in the

Просмотреть файл

@ -279,7 +279,7 @@ public class BotFrameworkAdapter extends BotAdapter {
*/
@SuppressWarnings("checkstyle:EmptyBlock")
@Override
public CompletableFuture<ResourceResponse[]> sendActivities(TurnContext context, Activity[] activities) {
public CompletableFuture<ResourceResponse[]> sendActivities(TurnContext context, List<Activity> 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)) {

Просмотреть файл

@ -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<ResourceResponse> sendActivity(String textReplyToSend, String speak, String inputHint) {
public CompletableFuture<ResourceResponse> 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<ResourceResponse[]> sendActivities(Activity[] activities) {
public CompletableFuture<ResourceResponse[]> sendActivities(List<Activity> activities) {
return innerTurnContext.sendActivities(activities);
}

Просмотреть файл

@ -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.</p>
*/
CompletableFuture<ResourceResponse> sendActivity(String textReplyToSend, String speak, String inputHint);
CompletableFuture<ResourceResponse> 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<ResourceResponse[]> sendActivities(Activity[] activities);
CompletableFuture<ResourceResponse[]> sendActivities(List<Activity> 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 {
* <p>Before calling this, set the ID of the replacement activity to the ID
* of the activity to replace.</p>
*/
CompletableFuture<ResourceResponse> updateActivity(Activity activity);
CompletableFuture<ResourceResponse> 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<Activity>)} methods are called,
* the adapter calls the registered handlers in the order in which they were
* added to the context object.
*/

Просмотреть файл

@ -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<SendActivitiesHandler> onSendActivities = new ArrayList<SendActivitiesHandler>();
private final List<UpdateActivityHandler> onUpdateActivity = new ArrayList<UpdateActivityHandler>();
private final List<DeleteActivityHandler> onDeleteActivity = new ArrayList<DeleteActivityHandler>();
/**
* 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.
* <p>The {@link #sendActivity(Activity)} and {@link #sendActivities(Activity[])}
* methods do this for you.</p>
*/
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<Activity>)} 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.
*
* <p>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.</p>
*
* <p>See the channel's documentation for limits imposed upon the contents of
* {@code textReplyToSend}.</p>
*
* @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.
* <p>See the channel's documentation for limits imposed upon the contents of
* {@code textReplyToSend}.</p>
* <p>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.</p>
*/
@Override
public CompletableFuture<ResourceResponse> sendActivity(String textReplyToSend) {
return sendActivity(textReplyToSend, null, null);
}
/**
* Sends a message activity to the sender of the incoming activity.
*
* <p>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.</p>
*
* <p>See the channel's documentation for limits imposed upon the contents of
* {@code textReplyToSend}.</p>
*
* @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<ResourceResponse> sendActivity(String textReplyToSend, String speak) {
return sendActivity(textReplyToSend, speak, null);
}
/**
* Sends a message activity to the sender of the incoming activity.
*
* <p>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.</p>
*
* <p>See the channel's documentation for limits imposed upon the contents of
* {@code textReplyToSend}.</p>
*
* @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<ResourceResponse> sendActivity(String textReplyToSend, String speak, String inputHint) {
if (StringUtils.isEmpty(textReplyToSend))
public CompletableFuture<ResourceResponse> 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<ResourceResponse> 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<ResourceResponse[]> 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<ResourceResponse[]> sendActivities(List<Activity> activities) {
if (activities == null || activities.size() == 0) {
throw new IllegalArgumentException("activities");
}
// Convert the IActivities to Activities.
List<Activity> 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<Activity> activityList = new ArrayList<Activity>(activityArray);
// Buffer the incoming activities into a List<T> 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<Activity> bufferedActivities = activities.stream()
.map(a -> a.applyConversationReference(cr)).collect(Collectors.toList());
Supplier<CompletableFuture<ResourceResponse[]>> 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<Activity> act_list = new ArrayList<>(activityList);
return sendActivitiesInternal(act_list, onSendActivities.iterator(), actuallySendStuff);
private CompletableFuture<ResourceResponse[]> sendActivitiesThroughAdapter(List<Activity> 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<ResourceResponse[]> sendActivitiesThroughCallbackPipeline(List<Activity> 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<ResourceResponse> updateActivity(Activity activity) {
Supplier<CompletableFuture<ResourceResponse>> 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<Void> deleteActivity(String activityId) {
if (StringUtils.isWhitespace(activityId) || activityId == null) {
throw new IllegalArgumentException("activityId");
}
ConversationReference cr = getConversationReference(getActivity());
cr.setActivityId(activityId);
Supplier<CompletableFuture<Void>> 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<Void> deleteActivity(ConversationReference conversationReference) {
if (conversationReference == null)
throw new IllegalArgumentException("conversationReference");
Supplier<CompletableFuture<Void>> ActuallyDeleteStuff = () ->
getAdapter().deleteActivity(this, conversationReference);
return deleteActivityInternal(conversationReference, onDeleteActivity.iterator(), ActuallyDeleteStuff);
}
private CompletableFuture<ResourceResponse[]> sendActivitiesInternal(
List<Activity> activities,
Iterator<SendActivitiesHandler> sendHandlers,
Supplier<CompletableFuture<ResourceResponse[]>> 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<CompletableFuture<ResourceResponse[]>> 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<SendActivitiesHandler> remaining = sendHandlers.Skip(1);
//Iterator<SendActivitiesHandler> 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<ResourceResponse> UpdateActivityInternal(Activity activity,
// IEnumerable<UpdateActivityHandler> updateHandlers,
// Func<Task<ResourceResponse>> 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<ResourceResponse> 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<UpdateActivityHandler> 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<ResourceResponse> updateActivityInternal(Activity activity,
Iterator<UpdateActivityHandler> updateHandlers,
Supplier<CompletableFuture<ResourceResponse>> callAtBottom) {
public CompletableFuture<ResourceResponse> 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<CompletableFuture<ResourceResponse>> actuallyUpdateStuff =
() -> getAdapter().updateActivity(this, withActivity);
return updateActivityInternal(withActivity, onUpdateActivity.iterator(), actuallyUpdateStuff);
}
private CompletableFuture<ResourceResponse> updateActivityInternal(
Activity activity,
Iterator<UpdateActivityHandler> updateHandlers,
Supplier<CompletableFuture<ResourceResponse>> 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<CompletableFuture<ResourceResponse>> 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<Void> deleteActivity(String activityId) {
if (StringUtils.isWhitespace(activityId) || StringUtils.isEmpty(activityId)) {
throw new IllegalArgumentException("activityId");
}
ConversationReference cr = activity.getConversationReference();
cr.setActivityId(activityId);
Supplier<CompletableFuture<Void>> 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<Void> deleteActivity(ConversationReference conversationReference) {
if (conversationReference == null) {
throw new IllegalArgumentException("conversationReference");
}
Supplier<CompletableFuture<Void>> actuallyDeleteStuff = () ->
getAdapter().deleteActivity(this, conversationReference);
return deleteActivityInternal(conversationReference, onDeleteActivity.iterator(), actuallyDeleteStuff);
}
private CompletableFuture<Void> deleteActivityInternal(ConversationReference cr,
Iterator<DeleteActivityHandler> deleteHandlers,
Supplier<CompletableFuture<Void>> 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<CompletableFuture<Void>> 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<UpdateActivityHandler> 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();
}

Просмотреть файл

@ -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<String, Object> 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();
}
}

Просмотреть файл

@ -279,7 +279,7 @@ public class ActivityHandlerTests {
private static class NotImplementedAdapter extends BotAdapter {
@Override
public CompletableFuture<ResourceResponse[]> sendActivities(TurnContext context, Activity[] activities) {
public CompletableFuture<ResourceResponse[]> sendActivities(TurnContext context, List<Activity> activities) {
throw new NotImplementedException();
}

Просмотреть файл

@ -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<Activity[]> validateResponse = (activities) -> {
Consumer<List<Activity>> validateResponse = (activities) -> {
// no need to do anything.
};

Просмотреть файл

@ -14,20 +14,20 @@ import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
public class SimpleAdapter extends BotAdapter {
private Consumer<Activity[]> callOnSend = null;
private Consumer<List<Activity>> callOnSend = null;
private Consumer<Activity> callOnUpdate = null;
private Consumer<ConversationReference> callOnDelete = null;
// Callback Function but doesn't need to be. Avoid java legacy type erasure
public SimpleAdapter(Consumer<Activity[]> callOnSend) {
public SimpleAdapter(Consumer<List<Activity>> callOnSend) {
this(callOnSend, null, null);
}
public SimpleAdapter(Consumer<Activity[]> callOnSend, Consumer<Activity> callOnUpdate) {
public SimpleAdapter(Consumer<List<Activity>> callOnSend, Consumer<Activity> callOnUpdate) {
this(callOnSend, callOnUpdate, null);
}
public SimpleAdapter(Consumer<Activity[]> callOnSend, Consumer<Activity> callOnUpdate, Consumer<ConversationReference> callOnDelete) {
public SimpleAdapter(Consumer<List<Activity>> callOnSend, Consumer<Activity> callOnUpdate, Consumer<ConversationReference> callOnDelete) {
this.callOnSend = callOnSend;
this.callOnUpdate = callOnUpdate;
this.callOnDelete = callOnDelete;
@ -39,9 +39,9 @@ public class SimpleAdapter extends BotAdapter {
@Override
public CompletableFuture<ResourceResponse[]> sendActivities(TurnContext context, Activity[] activities) {
public CompletableFuture<ResourceResponse[]> sendActivities(TurnContext context, List<Activity> 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);
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -91,7 +91,7 @@ public class TestAdapter extends BotAdapter {
}
@Override
public CompletableFuture<ResourceResponse[]> sendActivities(TurnContext context, Activity[] activities) {
public CompletableFuture<ResourceResponse[]> sendActivities(TurnContext context, List<Activity> activities) {
List<ResourceResponse> responses = new LinkedList<ResourceResponse>();
for (Activity activity : activities) {
@ -104,7 +104,7 @@ public class TestAdapter extends BotAdapter {
responses.add(new ResourceResponse(activity.getId()));
// This is simulating DELAY
System.out.println(String.format("TestAdapter:SendActivities(tid:%s):Count:%s", Thread.currentThread().getId(), activities.length));
System.out.println(String.format("TestAdapter:SendActivities(tid:%s):Count:%s", Thread.currentThread().getId(), activities.size()));
for (Activity act : activities) {
System.out.printf(":--------\n: To:%s\n", act.getRecipient().getName());
System.out.printf(": From:%s\n", (act.getFrom() == null) ? "No from set" : act.getFrom().getName());

Просмотреть файл

@ -16,7 +16,7 @@ import com.microsoft.rest.RestClient;
/**
* The interface for ConnectorClient class.
*/
public interface ConnectorClient {
public interface ConnectorClient extends AutoCloseable {
/**
* Gets the REST client.
*
@ -94,4 +94,9 @@ public interface ConnectorClient {
* @return the Conversations object.
*/
Conversations getConversations();
@Override
default void close() throws Exception {
}
}

Просмотреть файл

@ -9,7 +9,10 @@ package com.microsoft.bot.connector.rest;
import com.microsoft.azure.AzureClient;
import com.microsoft.azure.AzureResponseBuilder;
import com.microsoft.azure.AzureServiceClient;
import com.microsoft.bot.connector.*;
import com.microsoft.bot.connector.Attachments;
import com.microsoft.bot.connector.ConnectorClient;
import com.microsoft.bot.connector.Conversations;
import com.microsoft.bot.connector.UserAgent;
import com.microsoft.rest.credentials.ServiceClientCredentials;
import com.microsoft.rest.RestClient;
import com.microsoft.rest.retry.RetryStrategy;

Просмотреть файл

@ -313,11 +313,21 @@ public class Activity {
/**
* Create a TRACE type Activity.
*
* @param withName Name of the operation
* @param withValueType valueType if helpful to identify the value schema (default is value.GetType().Name)
* @param withValue The content for this trace operation.
* @param withLabel A descriptive label for this trace operation.
* @param withName Name of the operation
*/
public static Activity createTraceActivity(String withName) {
return createTraceActivity(withName, null, null, null);
}
/**
* Create a TRACE type Activity.
*
* @param withName Name of the operation
* @param withValueType valueType if helpful to identify the value schema (default is value.GetType().Name)
* @param withValue The content for this trace operation.
* @param withLabel A descriptive label for this trace operation.
*/
public static Activity createTraceActivity(String withName,
String withValueType,
Object withValue,
@ -325,7 +335,11 @@ public class Activity {
return new Activity(ActivityTypes.TRACE) {{
setName(withName);
setLabel(withLabel);
setValueType((withValueType == null) ? withValue.getClass().getTypeName() : withValueType);
if (withValue != null) {
setValueType((withValueType == null) ? withValue.getClass().getTypeName() : withValueType);
} else {
setValueType(withValueType);
}
setValue(withValue);
}};
}

Просмотреть файл

@ -6,9 +6,6 @@
package com.microsoft.bot.schema;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
/**
* Defines values for ActivityTypes.
*/