Skip to content
This repository was archived by the owner on Dec 4, 2023. It is now read-only.

Commit 59b5c50

Browse files
authored
Add 'ExpectReplies' as the list of options for Delivery Mode (#937)
1 parent a408ebe commit 59b5c50

File tree

5 files changed

+233
-11
lines changed

5 files changed

+233
-11
lines changed

libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
import com.microsoft.bot.schema.ConversationParameters;
3838
import com.microsoft.bot.schema.ConversationReference;
3939
import com.microsoft.bot.schema.ConversationsResult;
40+
import com.microsoft.bot.schema.DeliveryModes;
41+
import com.microsoft.bot.schema.ExpectedReplies;
4042
import com.microsoft.bot.schema.ResourceResponse;
4143
import com.microsoft.bot.schema.Serialization;
4244
import com.microsoft.bot.schema.TokenExchangeState;
@@ -483,10 +485,20 @@ public CompletableFuture<InvokeResponse> processActivity(
483485
context.getTurnState().add(CONNECTOR_CLIENT_KEY, connectorClient);
484486
return runPipeline(context, callback);
485487
})
486-
487-
// Handle Invoke scenarios, which deviate from the request/response model in
488-
// that the Bot will return a specific body and return code.
489488
.thenCompose(result -> {
489+
// Handle ExpectedReplies scenarios where the all the activities have been
490+
// buffered and sent back at once in an invoke response.
491+
if (DeliveryModes.fromString(
492+
context.getActivity().getDeliveryMode()) == DeliveryModes.EXPECT_REPLIES
493+
) {
494+
return CompletableFuture.completedFuture(new InvokeResponse(
495+
HttpURLConnection.HTTP_OK,
496+
new ExpectedReplies(context.getBufferedReplyActivities())
497+
));
498+
}
499+
500+
// Handle Invoke scenarios, which deviate from the request/response model in
501+
// that the Bot will return a specific body and return code.
490502
if (activity.isType(ActivityTypes.INVOKE)) {
491503
Activity invokeResponse = context.getTurnState().get(INVOKE_RESPONSE_KEY);
492504
if (invokeResponse == null) {
@@ -1581,11 +1593,23 @@ protected Map<String, AppCredentials> getCredentialsCache() {
15811593
}
15821594

15831595
/**
1584-
* Get the ConnectorClient cache. For unit testing.
1596+
* Get the ConnectorClient cache. FOR UNIT TESTING.
15851597
*
15861598
* @return The ConnectorClient cache.
15871599
*/
15881600
protected Map<String, ConnectorClient> getConnectorClientCache() {
15891601
return Collections.unmodifiableMap(connectorClients);
15901602
}
1603+
1604+
/**
1605+
* Inserts a ConnectorClient into the cache. FOR UNIT TESTING ONLY.
1606+
* @param serviceUrl The service url
1607+
* @param appId The app did
1608+
* @param scope The scope
1609+
* @param client The ConnectorClient to insert.
1610+
*/
1611+
protected void addConnectorClientToCache(String serviceUrl, String appId, String scope, ConnectorClient client) {
1612+
String key = BotFrameworkAdapter.keyForConnectorClient(serviceUrl, appId, scope);
1613+
connectorClients.put(key, client);
1614+
}
15911615
}

libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.microsoft.bot.schema.Activity;
88
import com.microsoft.bot.schema.ActivityTypes;
99
import com.microsoft.bot.schema.ConversationReference;
10+
import com.microsoft.bot.schema.DeliveryModes;
1011
import com.microsoft.bot.schema.InputHints;
1112
import com.microsoft.bot.schema.ResourceResponse;
1213
import java.util.Locale;
@@ -39,6 +40,8 @@ public class TurnContextImpl implements TurnContext, AutoCloseable {
3940
*/
4041
private final Activity activity;
4142

43+
private List<Activity> bufferedReplyActivities = new ArrayList<>();
44+
4245
/**
4346
* Response handlers for send activity operations.
4447
*/
@@ -217,6 +220,14 @@ public void setLocale(String withLocale) {
217220
}
218221
}
219222

223+
/**
224+
* Gets a list of activities to send when `context.Activity.DeliveryMode == 'expectReplies'.
225+
* @return A list of activities.
226+
*/
227+
public List<Activity> getBufferedReplyActivities() {
228+
return bufferedReplyActivities;
229+
}
230+
220231
/**
221232
* Sends a message activity to the sender of the incoming activity.
222233
*
@@ -385,21 +396,46 @@ public CompletableFuture<ResourceResponse[]> sendActivities(List<Activity> activ
385396
private CompletableFuture<ResourceResponse[]> sendActivitiesThroughAdapter(
386397
List<Activity> activities
387398
) {
388-
return adapter.sendActivities(this, activities).thenApply(responses -> {
399+
if (DeliveryModes.fromString(getActivity().getDeliveryMode()) == DeliveryModes.EXPECT_REPLIES) {
400+
ResourceResponse[] responses = new ResourceResponse[activities.size()];
389401
boolean sentNonTraceActivity = false;
390402

391403
for (int index = 0; index < responses.length; index++) {
392404
Activity sendActivity = activities.get(index);
393-
sendActivity.setId(responses[index].getId());
405+
bufferedReplyActivities.add(sendActivity);
406+
407+
// Ensure the TurnState has the InvokeResponseKey, since this activity
408+
// is not being sent through the adapter, where it would be added to TurnState.
409+
if (activity.isType(ActivityTypes.INVOKE_RESPONSE)) {
410+
getTurnState().add(BotFrameworkAdapter.INVOKE_RESPONSE_KEY, activity);
411+
}
412+
413+
responses[index] = new ResourceResponse();
394414
sentNonTraceActivity |= !sendActivity.isType(ActivityTypes.TRACE);
395415
}
396416

397417
if (sentNonTraceActivity) {
398418
responded = true;
399419
}
400420

401-
return responses;
402-
});
421+
return CompletableFuture.completedFuture(responses);
422+
} else {
423+
return adapter.sendActivities(this, activities).thenApply(responses -> {
424+
boolean sentNonTraceActivity = false;
425+
426+
for (int index = 0; index < responses.length; index++) {
427+
Activity sendActivity = activities.get(index);
428+
sendActivity.setId(responses[index].getId());
429+
sentNonTraceActivity |= !sendActivity.isType(ActivityTypes.TRACE);
430+
}
431+
432+
if (sentNonTraceActivity) {
433+
responded = true;
434+
}
435+
436+
return responses;
437+
});
438+
}
403439
}
404440

405441
private CompletableFuture<ResourceResponse[]> sendActivitiesThroughCallbackPipeline(

libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotFrameworkAdapterTests.java

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,18 @@
1919
import com.microsoft.bot.connector.authentication.SimpleChannelProvider;
2020
import com.microsoft.bot.connector.authentication.SimpleCredentialProvider;
2121
import com.microsoft.bot.schema.Activity;
22+
import com.microsoft.bot.schema.ActivityTypes;
2223
import com.microsoft.bot.schema.CallerIdConstants;
2324
import com.microsoft.bot.schema.ConversationAccount;
2425
import com.microsoft.bot.schema.ConversationParameters;
2526
import com.microsoft.bot.schema.ConversationReference;
2627
import com.microsoft.bot.schema.ConversationResourceResponse;
28+
import com.microsoft.bot.schema.DeliveryModes;
29+
import com.microsoft.bot.schema.ExpectedReplies;
30+
import com.microsoft.bot.schema.ResourceResponse;
31+
import java.net.HttpURLConnection;
2732
import java.util.HashMap;
33+
import java.util.List;
2834
import java.util.Map;
2935
import org.junit.Assert;
3036
import org.junit.Test;
@@ -364,4 +370,95 @@ private static void getConnectorClientAndAssertValues(
364370
);
365371
Assert.assertEquals("Unexpected base url", expectedUrl, client.baseUrl());
366372
}
373+
374+
@Test
375+
public void DeliveryModeExpectReplies() {
376+
BotFrameworkAdapter adapter = new BotFrameworkAdapter(new SimpleCredentialProvider());
377+
378+
MockConnectorClient mockConnector = new MockConnectorClient("Windows/3.1", new MockAppCredentials("awesome"));
379+
adapter.addConnectorClientToCache("http://tempuri.org/whatever", null, null, mockConnector);
380+
381+
BotCallbackHandler callback = turnContext -> {
382+
turnContext.sendActivity(MessageFactory.text("activity 1")).join();
383+
turnContext.sendActivity(MessageFactory.text("activity 2")).join();
384+
turnContext.sendActivity(MessageFactory.text("activity 3")).join();
385+
return CompletableFuture.completedFuture(null);
386+
};
387+
388+
Activity inboundActivity = new Activity() {{
389+
setType(ActivityTypes.MESSAGE);
390+
setChannelId(Channels.EMULATOR);
391+
setServiceUrl("http://tempuri.org/whatever");
392+
setDeliveryMode(DeliveryModes.EXPECT_REPLIES.toString());
393+
setText("hello world");
394+
}};
395+
396+
InvokeResponse invokeResponse = adapter.processActivity((String) null, inboundActivity, callback).join();
397+
398+
Assert.assertEquals((int) HttpURLConnection.HTTP_OK, invokeResponse.getStatus());
399+
List<Activity> activities = ((ExpectedReplies)invokeResponse.getBody()).getActivities();
400+
Assert.assertEquals(3, activities.size());
401+
Assert.assertEquals("activity 1", activities.get(0).getText());
402+
Assert.assertEquals("activity 2", activities.get(1).getText());
403+
Assert.assertEquals("activity 3", activities.get(2).getText());
404+
Assert.assertEquals(0, ((MemoryConversations) mockConnector.getConversations()).getSentActivities().size());
405+
}
406+
407+
@Test
408+
public void DeliveryModeNormal() {
409+
BotFrameworkAdapter adapter = new BotFrameworkAdapter(new SimpleCredentialProvider());
410+
411+
MockConnectorClient mockConnector = new MockConnectorClient("Windows/3.1", new MockAppCredentials("awesome"));
412+
adapter.addConnectorClientToCache("http://tempuri.org/whatever", null, null, mockConnector);
413+
414+
BotCallbackHandler callback = turnContext -> {
415+
turnContext.sendActivity(MessageFactory.text("activity 1")).join();
416+
turnContext.sendActivity(MessageFactory.text("activity 2")).join();
417+
turnContext.sendActivity(MessageFactory.text("activity 3")).join();
418+
return CompletableFuture.completedFuture(null);
419+
};
420+
421+
Activity inboundActivity = new Activity() {{
422+
setType(ActivityTypes.MESSAGE);
423+
setChannelId(Channels.EMULATOR);
424+
setServiceUrl("http://tempuri.org/whatever");
425+
setDeliveryMode(DeliveryModes.NORMAL.toString());
426+
setText("hello world");
427+
setConversation(new ConversationAccount("conversationId"));
428+
}};
429+
430+
InvokeResponse invokeResponse = adapter.processActivity((String) null, inboundActivity, callback).join();
431+
432+
Assert.assertNull(invokeResponse);
433+
Assert.assertEquals(3, ((MemoryConversations) mockConnector.getConversations()).getSentActivities().size());
434+
}
435+
436+
// should be same as DeliverModes.NORMAL
437+
@Test
438+
public void DeliveryModeNull() {
439+
BotFrameworkAdapter adapter = new BotFrameworkAdapter(new SimpleCredentialProvider());
440+
441+
MockConnectorClient mockConnector = new MockConnectorClient("Windows/3.1", new MockAppCredentials("awesome"));
442+
adapter.addConnectorClientToCache("http://tempuri.org/whatever", null, null, mockConnector);
443+
444+
BotCallbackHandler callback = turnContext -> {
445+
turnContext.sendActivity(MessageFactory.text("activity 1")).join();
446+
turnContext.sendActivity(MessageFactory.text("activity 2")).join();
447+
turnContext.sendActivity(MessageFactory.text("activity 3")).join();
448+
return CompletableFuture.completedFuture(null);
449+
};
450+
451+
Activity inboundActivity = new Activity() {{
452+
setType(ActivityTypes.MESSAGE);
453+
setChannelId(Channels.EMULATOR);
454+
setServiceUrl("http://tempuri.org/whatever");
455+
setText("hello world");
456+
setConversation(new ConversationAccount("conversationId"));
457+
}};
458+
459+
InvokeResponse invokeResponse = adapter.processActivity((String) null, inboundActivity, callback).join();
460+
461+
Assert.assertNull(invokeResponse);
462+
Assert.assertEquals(3, ((MemoryConversations) mockConnector.getConversations()).getSentActivities().size());
463+
}
367464
}

libraries/bot-schema/src/main/java/com/microsoft/bot/schema/DeliveryModes.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,19 @@
1111
*/
1212
public enum DeliveryModes {
1313
/**
14-
* Enum value normal.
14+
* The mode value for normal delivery modes.
1515
*/
1616
NORMAL("normal"),
1717

1818
/**
19-
* Enum value notification.
19+
* The mode value for notification delivery modes.
2020
*/
21-
NOTIFICATION("notification");
21+
NOTIFICATION("notification"),
22+
23+
/**
24+
* The value for expected replies delivery modes.
25+
*/
26+
EXPECT_REPLIES("expectReplies");
2227

2328
/**
2429
* The actual serialized value for a DeliveryModes instance.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.microsoft.bot.schema;
5+
6+
import com.fasterxml.jackson.annotation.JsonInclude;
7+
import com.fasterxml.jackson.annotation.JsonProperty;
8+
import java.util.Arrays;
9+
import java.util.List;
10+
11+
/**
12+
* Replies in response to DeliveryModes.EXPECT_REPLIES.
13+
*/
14+
public class ExpectedReplies {
15+
@JsonProperty(value = "activities")
16+
@JsonInclude(JsonInclude.Include.NON_EMPTY)
17+
private List<Activity> activities;
18+
19+
/**
20+
* Create an instance of ExpectReplies.
21+
*/
22+
public ExpectedReplies() {
23+
24+
}
25+
26+
/**
27+
* Create an instance of ExpectReplies.
28+
* @param withActivities The collection of activities that conforms to the
29+
* ExpectedREplies schema.
30+
*/
31+
public ExpectedReplies(List<Activity> withActivities) {
32+
activities = withActivities;
33+
}
34+
35+
/**
36+
* Create an instance of ExpectReplies.
37+
* @param withActivities The array of activities that conforms to the
38+
* ExpectedREplies schema.
39+
*/
40+
public ExpectedReplies(Activity... withActivities) {
41+
this(Arrays.asList(withActivities));
42+
}
43+
44+
/**
45+
* Gets collection of Activities that conforms to the ExpectedReplies schema.
46+
* @return The collection of activities that conforms to the ExpectedREplies schema.
47+
*/
48+
public List<Activity> getActivities() {
49+
return activities;
50+
}
51+
52+
/**
53+
* Sets collection of Activities that conforms to the ExpectedReplies schema.
54+
* @param withActivities The collection of activities that conforms to the
55+
* ExpectedREplies schema.
56+
*/
57+
public void setActivities(List<Activity> withActivities) {
58+
activities = withActivities;
59+
}
60+
}

0 commit comments

Comments
 (0)