diff --git a/libraries/bot-connector/pom.xml b/libraries/bot-connector/pom.xml index 1eda42da0..7a1ee6426 100644 --- a/libraries/bot-connector/pom.xml +++ b/libraries/bot-connector/pom.xml @@ -90,11 +90,6 @@ botbuilder-schema 4.0.0-SNAPSHOT - - com.ea.async - ea-async - 1.1.1 - diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/MicrosoftAppCredentials.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/MicrosoftAppCredentials.java index 9c3eafac7..b62a93d32 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/MicrosoftAppCredentials.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/MicrosoftAppCredentials.java @@ -4,33 +4,21 @@ package com.microsoft.bot.connector.authentication; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.microsoft.rest.credentials.ServiceClientCredentials; - import okhttp3.*; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; -import rx.Completable; - import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.*; -import java.nio.charset.StandardCharsets; -import java.security.InvalidParameterException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; import java.time.LocalDateTime; import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import static com.ea.async.Async.await; import static com.microsoft.bot.connector.authentication.AuthenticationConstants.ToChannelFromBotLoginUrl; import static com.microsoft.bot.connector.authentication.AuthenticationConstants.ToChannelFromBotOAuthScope; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.stream.Collectors.joining; public class MicrosoftAppCredentials implements ServiceClientCredentials { private String appId; @@ -50,7 +38,6 @@ public class MicrosoftAppCredentials implements ServiceClientCredentials { public final String OAuthScope = AuthenticationConstants.ToChannelFromBotOAuthScope; - public String getTokenCacheKey() { return String.format("%s-cache", this.appId); } @@ -61,15 +48,18 @@ public MicrosoftAppCredentials(String appId, String appPassword) { this.client = new OkHttpClient.Builder().build(); this.mapper = new ObjectMapper().findAndRegisterModules(); } + public static final MicrosoftAppCredentials Empty = new MicrosoftAppCredentials(null, null); public String microsoftAppId() { return this.appId; } + public MicrosoftAppCredentials withMicrosoftAppId(String appId) { this.appId = appId; return this; } + public String getToken(Request request) throws IOException { if (System.currentTimeMillis() < expiredTime) { return currentToken; @@ -93,18 +83,14 @@ public String getToken(Request request) throws IOException { } - - private boolean ShouldSetToken(String url) - { - if (isTrustedServiceUrl(url)) - { + private boolean ShouldSetToken(String url) { + if (isTrustedServiceUrl(url)) { return true; } return false; } - @Override public void applyCredentialsFilter(OkHttpClient.Builder clientBuilder) { clientBuilder.interceptors().add(new MicrosoftAppCredentialsInterceptor(this)); @@ -134,7 +120,8 @@ public static void trustServiceUrl(String serviceUrl, LocalDateTime expirationTi try { URL url = new URL(serviceUrl); trustServiceUrl(url, expirationTime); - } catch (MalformedURLException e) { } + } catch (MalformedURLException e) { + } } public static void trustServiceUrl(URL serviceUrl, LocalDateTime expirationTime) { @@ -153,6 +140,7 @@ public static boolean isTrustedServiceUrl(String serviceUrl) { public static boolean isTrustedServiceUrl(URL url) { return !trustHostNames.getOrDefault(url.getHost(), LocalDateTime.MIN).isBefore(LocalDateTime.now().minusMinutes(5)); } + public static boolean isTrustedServiceUrl(HttpUrl url) { return !trustHostNames.getOrDefault(url.host(), LocalDateTime.MIN).isBefore(LocalDateTime.now().minusMinutes(5)); } diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/OAuthClient.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/OAuthClient.java index 84eb7ae73..902a21221 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/OAuthClient.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/OAuthClient.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.microsoft.bot.connector.ConnectorClient; import com.microsoft.bot.connector.UserAgent; import com.microsoft.bot.connector.implementation.ConnectorClientImpl; import com.microsoft.bot.schema.TokenExchangeState; @@ -10,35 +9,36 @@ import com.microsoft.bot.schema.models.ConversationReference; import com.microsoft.bot.schema.models.TokenResponse; import com.microsoft.rest.ServiceClient; - -import okhttp3.*; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; import org.apache.commons.lang3.StringUtils; import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.net.*; - +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Base64; import java.util.HashMap; - import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import static com.ea.async.Async.await; import static com.microsoft.bot.connector.authentication.MicrosoftAppCredentials.JSON; import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import static java.net.HttpURLConnection.HTTP_OK; -import static java.util.concurrent.CompletableFuture.completedFuture; import static java.util.stream.Collectors.joining; + /** * Service client to handle requests to the botframework api service. - * + *

* Uses the MicrosoftInterceptor class to add Authorization header from idp. - * */ public class OAuthClient extends ServiceClient { private final ConnectorClientImpl client; @@ -59,16 +59,17 @@ public OAuthClient(ConnectorClientImpl client, String uri) throws URISyntaxExcep if (client == null) throw new IllegalArgumentException("client"); this.client = client; - this.uri = uri + (uri.endsWith("/")? "" : "/"); + this.uri = uri + (uri.endsWith("/") ? "" : "/"); this.mapper = new ObjectMapper(); } /** * Get User Token for given user and connection. - * @param userId - * @param connectionName + * + * @param userId + * @param connectionName * @param magicCode - * @return CompletableFuture< TokenResponse > on success; otherwise null. + * @return CompletableFuture< TokenResponse > on success; otherwise null. */ public CompletableFuture GetUserTokenAsync(String userId, String connectionName, String magicCode) throws IOException, URISyntaxException, ExecutionException, InterruptedException { return GetUserTokenAsync(userId, connectionName, magicCode, null); @@ -90,14 +91,13 @@ protected URI MakeUri(String uri, HashMap queryStrings) throws U } /** - Get User Token for given user and connection. - - @param userId - @param connectionName - @param magicCode - @param customHeaders - - @return CompletableFuture< TokenResponse > on success; null otherwise. + * Get User Token for given user and connection. + * + * @param userId + * @param connectionName + * @param magicCode + * @param customHeaders + * @return CompletableFuture< TokenResponse > on success; null otherwise. */ public CompletableFuture GetUserTokenAsync(String userId, String connectionName, String magicCode, Map> customHeaders) throws IllegalArgumentException { if (StringUtils.isEmpty(userId)) { @@ -109,7 +109,7 @@ public CompletableFuture GetUserTokenAsync(String userId, String return CompletableFuture.supplyAsync(() -> { // Construct URL - HashMap qstrings = new HashMap<>(); + HashMap qstrings = new HashMap<>(); qstrings.put("userId", userId); qstrings.put("connectionName", connectionName); if (!StringUtils.isBlank(magicCode)) { @@ -146,17 +146,14 @@ public CompletableFuture GetUserTokenAsync(String userId, String int statusCode = response.code(); if (statusCode == HTTP_OK) { return this.mapper.readValue(response.body().string(), TokenResponse.class); - } - else if (statusCode == HTTP_NOT_FOUND) { + } else if (statusCode == HTTP_NOT_FOUND) { return null; - } - else { + } else { return null; } } catch (IOException e) { e.printStackTrace(); - } - finally { + } finally { if (response != null) response.body().close(); } @@ -166,7 +163,8 @@ else if (statusCode == HTTP_NOT_FOUND) { /** * Signs Out the User for the given ConnectionName. - * @param userId + * + * @param userId * @param connectionName * @return True on successful sign-out; False otherwise. */ @@ -182,7 +180,7 @@ public CompletableFuture SignOutUserAsync(String userId, String connect String invocationId = null; // Construct URL - HashMap qstrings = new HashMap<>(); + HashMap qstrings = new HashMap<>(); qstrings.put("userId", userId); qstrings.put("connectionName", connectionName); String strUri = String.format("%sapi/usertoken/SignOut", this.uri); @@ -226,10 +224,10 @@ public CompletableFuture SignOutUserAsync(String userId, String connect } - /** * Gets the Link to be sent to the user for signin into the given ConnectionName - * @param activity + * + * @param activity * @param connectionName * @return Sign in link on success; null otherwise. */ @@ -294,13 +292,13 @@ public CompletableFuture GetSignInLinkAsync(Activity activity, String co /** * Send a dummy OAuth card when the bot is being used on the emulator for testing without fetching a real token. * - * @param emulateOAuthCards + * @param emulateOAuthCards * @return CompletableFuture with no result code */ public CompletableFuture SendEmulateOAuthCardsAsync(Boolean emulateOAuthCards) throws URISyntaxException, IOException { // Construct URL - HashMap qstrings = new HashMap<>(); + HashMap qstrings = new HashMap<>(); qstrings.put("emulate", emulateOAuthCards.toString()); String strUri = String.format("%sapi/usertoken/emulateOAuthCards", this.uri); URI tokenUrl = MakeUri(strUri, qstrings); @@ -310,7 +308,7 @@ public CompletableFuture SendEmulateOAuthCardsAsync(Boolean emulateOAuthCards) t return CompletableFuture.runAsync(() -> { // Construct dummy body - RequestBody body = RequestBody.create(JSON, "{}" ); + RequestBody body = RequestBody.create(JSON, "{}"); // Set Credentials and make call MicrosoftAppCredentials appCredentials = (MicrosoftAppCredentials) client.restClient().credentials(); diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotAccessTokenStub.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotAccessTokenStub.java index ec044d589..2f62336b7 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotAccessTokenStub.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotAccessTokenStub.java @@ -6,7 +6,7 @@ import okhttp3.OkHttpClient; import okhttp3.Response; -import static com.ea.async.Async.await; + import static java.util.concurrent.CompletableFuture.completedFuture; public class BotAccessTokenStub extends MicrosoftAppCredentials { diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthConnectorTest.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthConnectorTest.java index b969c9c9b..0c9d229c6 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthConnectorTest.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthConnectorTest.java @@ -20,7 +20,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import static com.ea.async.Async.await; + import static java.util.concurrent.CompletableFuture.completedFuture; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthTestBase.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthTestBase.java index 53cc916fd..3a9ed6b6b 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthTestBase.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthTestBase.java @@ -20,7 +20,6 @@ import java.util.concurrent.ExecutionException; import java.util.function.Function; -import static com.ea.async.Async.await; import static java.util.concurrent.CompletableFuture.completedFuture; @@ -102,7 +101,7 @@ public void UseClientFor(Function> doTe this.UseClientFor(doTest, className, ""); } public void UseClientFor(Function> doTest, String className, String methodName) { - await(doTest.apply(this.connector)); + doTest.apply(this.connector).join(); } diff --git a/libraries/botbuilder-schema/src/main/java/com/microsoft/bot/schema/ActivityImpl.java b/libraries/botbuilder-schema/src/main/java/com/microsoft/bot/schema/ActivityImpl.java index 6d72633c8..2a17544a6 100644 --- a/libraries/botbuilder-schema/src/main/java/com/microsoft/bot/schema/ActivityImpl.java +++ b/libraries/botbuilder-schema/src/main/java/com/microsoft/bot/schema/ActivityImpl.java @@ -16,6 +16,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; /** @@ -360,6 +361,223 @@ public ActivityImpl withText(String text) { return this; } + /** + * Set the speak value. + * + * @param speak the speak value to set + * @return the Activity object itself. + */ + public ActivityImpl withSpeak(String speak) { + super.withSpeak(speak); + return this; + } + + + /** + * Set the inputHint value. + * + * @param inputHint the inputHint value to set + * @return the Activity object itself. + */ + public ActivityImpl withInputHint(InputHints inputHint) { + super.withInputHint(inputHint); + return this; + } + + /** + * Set the summary value. + * + * @param summary the summary value to set + * @return the Activity object itself. + */ + public ActivityImpl withSummary(String summary) { + super.withSummary(summary); + return this; + } + + + /** + * Set the suggestedActions value. + * + * @param suggestedActions the suggestedActions value to set + * @return the Activity object itself. + */ + public ActivityImpl withSuggestedActions(SuggestedActions suggestedActions) { + super.withSuggestedActions(suggestedActions); + return this; + } + + + /** + * Set the attachments value. + * + * @param attachments the attachments value to set + * @return the Activity object itself. + */ + public ActivityImpl withAttachments(List attachments) { + super.withAttachments(attachments); + return this; + } + + + /** + * Set the entities value. + * + * @param entities the entities value to set + * @return the Activity object itself. + */ + public ActivityImpl withEntities(List entities) { + super.withEntities(entities); + return this; + } + + + /** + * Set the channelData value. + * + * @param channelData the channelData value to set + * @return the Activity object itself. + */ + public ActivityImpl withChannelData(Object channelData) { + super.withChannelData(channelData); + return this; + } + + + /** + * Set the action value. + * + * @param action the action value to set + * @return the Activity object itself. + */ + public ActivityImpl withAction(String action) { + super.withAction(action); + return this; + } + + /** + * Set the replyToId value. + * + * @param replyToId the replyToId value to set + * @return the Activity object itself. + */ + public ActivityImpl withReplyToId(String replyToId) { + super.withReplyToId(replyToId); + return this; + } + + /** + * Set the label value. + * + * @param label the label value to set + * @return the Activity object itself. + */ + public ActivityImpl withLabel(String label) { + super.withLabel(label); + return this; + } + + /** + * Set the valueType value. + * + * @param valueType the valueType value to set + * @return the Activity object itself. + */ + public ActivityImpl withValueType(String valueType) { + super.withValueType(valueType); + return this; + } + + /** + * Set the value value. + * + * @param value the value value to set + * @return the Activity object itself. + */ + public ActivityImpl withValue(Object value) { + super.withValue(value); + return this; + } + + + /** + * Set the name value. + * + * @param name the name value to set + * @return the Activity object itself. + */ + public ActivityImpl withName(String name) { + super.withName(name); + return this; + } + + + /** + * Set the relatesTo value. + * + * @param relatesTo the relatesTo value to set + * @return the Activity object itself. + */ + public ActivityImpl withRelatesTo(ConversationReference relatesTo) { + super.withRelatesTo(relatesTo); + return this; + } + + /** + * Set the code value. + * + * @param code the code value to set + * @return the Activity object itself. + */ + public ActivityImpl withCode(EndOfConversationCodes code) { + super.withCode(code); + return this; + } + + /** + * Set the expiration value. + * + * @param expiration the expiration value to set + * @return the Activity object itself. + */ + public ActivityImpl withExpiration(DateTime expiration) { + super.withExpiration(expiration); + return this; + } + + /** + * Set the importance value. + * + * @param importance the importance value to set + * @return the Activity object itself. + */ + public ActivityImpl withImportance(String importance) { + super.withImportance(importance); + return this; + } + + /** + * Set the deliveryMode value. + * + * @param deliveryMode the deliveryMode value to set + * @return the Activity object itself. + */ + public ActivityImpl withDeliveryMode(String deliveryMode) { + super.withDeliveryMode(deliveryMode); + return this; + } + + /** + * Set the textHighlights value. + * + * @param textHighlights the textHighlights value to set + * @return the Activity object itself. + */ + public ActivityImpl withTextHighlights(List textHighlights) { + super.withTextHighlights(textHighlights); + return this; + } + /** * Return an MessageActivity mask if this is a message activity */ @@ -517,4 +735,44 @@ public ResultPair TryGetChannelData(Class clsType } return new ResultPair(true, instance); } + public static Activity CloneActity(Activity activity) { + Activity clone = new Activity() + .withType(activity.type()) + .withId(activity.id()) + .withTimestamp(activity.timestamp()) + .withLocalTimestamp(activity.localTimestamp()) + .withText(activity.text()) + .withFrom(activity.from()) + .withRecipient(activity.recipient()) + .withConversation(activity.conversation()) + .withChannelId(activity.channelId()) + .withServiceUrl(activity.serviceUrl()) + .withChannelId(activity.channelId()) + .withText(activity.text()) + .withSpeak(activity.speak()) + .withInputHint(activity.inputHint()) + .withSummary(activity.summary()) + .withSuggestedActions(activity.suggestedActions()) + .withAttachments(activity.attachments()) + .withEntities(activity.entities()) + .withChannelData(activity.channelData()) + .withAction(activity.action()) + .withReplyToId(activity.replyToId()) + .withLabel(activity.label()) + .withValueType(activity.valueType()) + .withValue(activity.value()) + .withName(activity.name()) + .withRelatesTo(activity.relatesTo()) + .withCode(activity.code()) + .withExpiration(activity.expiration()) + .withImportance(activity.importance()) + .withDeliveryMode(activity.deliveryMode()) + .withTextHighlights(activity.textHighlights()); + for (Map.Entry entry : activity.properties().entrySet()) { + clone.setProperties(entry.getKey(), entry.getValue()); + } + return clone; + + } + } diff --git a/libraries/botbuilder-schema/src/main/java/com/microsoft/bot/schema/EntityImpl.java b/libraries/botbuilder-schema/src/main/java/com/microsoft/bot/schema/EntityImpl.java index 5b248abce..2fa6fc755 100644 --- a/libraries/botbuilder-schema/src/main/java/com/microsoft/bot/schema/EntityImpl.java +++ b/libraries/botbuilder-schema/src/main/java/com/microsoft/bot/schema/EntityImpl.java @@ -53,7 +53,7 @@ void CustomInit() { private HashMap properties = new HashMap(); @JsonAnyGetter - public Map getProperties() { + public Map properties() { return this.properties; diff --git a/libraries/botbuilder-schema/src/main/java/com/microsoft/bot/schema/models/Activity.java b/libraries/botbuilder-schema/src/main/java/com/microsoft/bot/schema/models/Activity.java index 6d7c1de53..a92e134ae 100644 --- a/libraries/botbuilder-schema/src/main/java/com/microsoft/bot/schema/models/Activity.java +++ b/libraries/botbuilder-schema/src/main/java/com/microsoft/bot/schema/models/Activity.java @@ -3,16 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for * license information. * - * Code generated by Microsoft (R) AutoRest Code Generator. - * Changes may cause incorrect behavior and will be lost if the code is - * regenerated. */ package com.microsoft.bot.schema.models; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.databind.JsonNode; import com.microsoft.bot.schema.EntityImpl; import org.joda.time.DateTime; + +import java.util.HashMap; import java.util.List; +import java.util.Map; + import com.fasterxml.jackson.annotation.JsonProperty; /** @@ -1024,5 +1028,84 @@ public Activity withTextHighlights(List textHighlights) { this.textHighlights = textHighlights; return this; } + /** + * Holds the overflow properties that aren't first class + * properties in the object. This allows extensibility + * while maintaining the object. + * + */ + private HashMap properties = new HashMap(); + + /** + * Overflow properties. + * Properties that are not modelled as first class properties in the object are accessible here. + * Note: A property value can be be nested. + * + * @return A Key-Value map of the properties + */ + @JsonAnyGetter + public Map properties() { + return this.properties; + } + + /** + * Set overflow properties. + * + * @param key Key for the property + * @param value JsonNode of value (can be nested) + * + */ + + @JsonAnySetter + public void setProperties(String key, JsonNode value) { + this.properties.put(key, value); + } + + /** + Updates this activity with the delivery information from an existing + conversation reference. + + @param reference The conversation reference. + @param isIncoming (Optional) true to treat the activity as an + incoming activity, where the bot is the recipient; otherwaire false. + Default is false, and the activity will show the bot as the sender. + Call on an incoming + activity to get a conversation reference that you can then use to update an + outgoing activity with the correct delivery information. + + */ + + + public final Activity applyConversationReference(ConversationReference reference) + { + return applyConversationReference(reference, false); + } + + public final Activity applyConversationReference(ConversationReference reference, boolean isIncoming) + { + this.withChannelId(reference.channelId()); + this.withServiceUrl(reference.serviceUrl()); + this.withConversation(reference.conversation()); + + if (isIncoming) + { + this.withFrom(reference.user()); + this.withRecipient(reference.bot()); + if (reference.activityId() != null) + { + this.withId(reference.activityId()); + } + } + else // Outgoing + { + this.withFrom(reference.bot()); + this.withRecipient(reference.user()); + if (reference.activityId() != null) + { + this.withReplyToId(reference.activityId()); + } + } + return this; + } } diff --git a/libraries/botbuilder-schema/src/main/java/com/microsoft/bot/schema/models/Attachment.java b/libraries/botbuilder-schema/src/main/java/com/microsoft/bot/schema/models/Attachment.java index 5accdf632..01c94d9ca 100644 --- a/libraries/botbuilder-schema/src/main/java/com/microsoft/bot/schema/models/Attachment.java +++ b/libraries/botbuilder-schema/src/main/java/com/microsoft/bot/schema/models/Attachment.java @@ -3,14 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for * license information. * - * Code generated by Microsoft (R) AutoRest Code Generator. - * Changes may cause incorrect behavior and will be lost if the code is - * regenerated. */ package com.microsoft.bot.schema.models; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; + +import java.util.HashMap; +import java.util.Map; /** * An attachment within an activity. @@ -145,5 +148,38 @@ public Attachment withThumbnailUrl(String thumbnailUrl) { this.thumbnailUrl = thumbnailUrl; return this; } + /** + * Holds the overflow properties that aren't first class + * properties in the object. This allows extensibility + * while maintaining the object. + * + */ + private HashMap properties = new HashMap(); + + /** + * Overflow properties. + * Properties that are not modelled as first class properties in the object are accessible here. + * Note: A property value can be be nested. + * + * @return A Key-Value map of the properties + */ + @JsonAnyGetter + public Map properties() { + return this.properties; + } + + /** + * Set overflow properties. + * + * @param key Key for the property + * @param value JsonNode of value (can be nested) + * + */ + + @JsonAnySetter + public void setProperties(String key, JsonNode value) { + this.properties.put(key, value); + } + } diff --git a/libraries/botbuilder-schema/src/main/java/com/microsoft/bot/schema/models/ChannelAccount.java b/libraries/botbuilder-schema/src/main/java/com/microsoft/bot/schema/models/ChannelAccount.java index 6b402b903..1b7f22cf1 100644 --- a/libraries/botbuilder-schema/src/main/java/com/microsoft/bot/schema/models/ChannelAccount.java +++ b/libraries/botbuilder-schema/src/main/java/com/microsoft/bot/schema/models/ChannelAccount.java @@ -3,14 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for * license information. * - * Code generated by Microsoft (R) AutoRest Code Generator. - * Changes may cause incorrect behavior and will be lost if the code is - * regenerated. */ package com.microsoft.bot.schema.models; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; + +import java.util.HashMap; +import java.util.Map; /** * Channel account information needed to route a message. @@ -96,4 +99,39 @@ public ChannelAccount withRole(RoleTypes role) { return this; } + + /** + * Holds the overflow properties that aren't first class + * properties in the object. This allows extensibility + * while maintaining the object. + * + */ + private HashMap properties = new HashMap(); + + /** + * Overflow properties. + * Properties that are not modelled as first class properties in the object are accessible here. + * Note: A property value can be be nested. + * + * @return A Key-Value map of the properties + */ + @JsonAnyGetter + public Map properties() { + return this.properties; + } + + /** + * Set overflow properties. + * + * @param key Key for the property + * @param value JsonNode of value (can be nested) + * + */ + + @JsonAnySetter + public void setProperties(String key, JsonNode value) { + this.properties.put(key, value); + } + + } diff --git a/libraries/botbuilder-schema/src/main/java/com/microsoft/bot/schema/models/ConversationAccount.java b/libraries/botbuilder-schema/src/main/java/com/microsoft/bot/schema/models/ConversationAccount.java index b3a882c4d..c998270ad 100644 --- a/libraries/botbuilder-schema/src/main/java/com/microsoft/bot/schema/models/ConversationAccount.java +++ b/libraries/botbuilder-schema/src/main/java/com/microsoft/bot/schema/models/ConversationAccount.java @@ -3,14 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for * license information. * - * Code generated by Microsoft (R) AutoRest Code Generator. - * Changes may cause incorrect behavior and will be lost if the code is - * regenerated. */ package com.microsoft.bot.schema.models; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; + +import java.util.HashMap; +import java.util.Map; /** * Channel account information for a conversation. @@ -149,5 +152,38 @@ public ConversationAccount withRole(RoleTypes role) { this.role = role; return this; } + /** + * Holds the overflow properties that aren't first class + * properties in the object. This allows extensibility + * while maintaining the object. + * + */ + private HashMap properties = new HashMap(); + + /** + * Overflow properties. + * Properties that are not modelled as first class properties in the object are accessible here. + * Note: A property value can be be nested. + * + * @return A Key-Value map of the properties + */ +// @JsonAnyGetter +// public Map properties() { +// return this.properties; +// } + + /** + * Set overflow properties. + * + * @param key Key for the property + * @param value JsonNode of value (can be nested) + * + */ + +// @JsonAnySetter +// public void setProperties(String key, JsonNode value) { +// this.properties.put(key, value); +// } + } diff --git a/libraries/botbuilder/pom.xml b/libraries/botbuilder/pom.xml index 36bfc8448..b3ef66c96 100644 --- a/libraries/botbuilder/pom.xml +++ b/libraries/botbuilder/pom.xml @@ -45,6 +45,26 @@ 4.12 test + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + + org.slf4j + slf4j-log4j12 + 1.7.25 + test + + + + org.apache.logging.log4j + log4j-core + 2.11.0 + + com.microsoft.rest client-runtime @@ -95,11 +115,6 @@ bot-connector 4.0.0-SNAPSHOT - - com.ea.async - ea-async - 1.1.1 - diff --git a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/AnonymousReceiveMiddleware.java b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/AnonymousReceiveMiddleware.java index bf5abcc42..abcabeff6 100644 --- a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/AnonymousReceiveMiddleware.java +++ b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/AnonymousReceiveMiddleware.java @@ -30,8 +30,8 @@ public AnonymousReceiveMiddleware(MiddlewareCall anonymousMethod) * @param next The delegate to call to continue the bot middleware pipeline. * @return A task that represents the work queued to execute. */ - public CompletableFuture OnTurn(TurnContext context, NextDelegate next) throws Exception { - return _toCall.requestHandler(context, next); + public void OnTurn(TurnContext context, NextDelegate next) throws Exception { + _toCall.requestHandler(context, next); } } diff --git a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/BotAdapter.java b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/BotAdapter.java index 53b093a7f..59f6a5405 100644 --- a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/BotAdapter.java +++ b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/BotAdapter.java @@ -11,12 +11,9 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; +import java.util.function.Consumer; import java.util.function.Function; -import static com.ea.async.Async.await; -import static java.util.concurrent.CompletableFuture.completedFuture; - /** * Represents a bot adapter that can connect a bot to a service endpoint. * This class is abstract. @@ -30,7 +27,7 @@ * and then back out again. As each activity flows in and out of the bot, each piece * of middleware can inspect or act upon the activity, both before and after the bot * logic runs.

- * + *

* {@linkalso TurnContext} * {@linkalso Activity} * {@linkalso Bot} @@ -51,11 +48,11 @@ public BotAdapter() { /** * Adds middleware to the adapter's pipeline. + * * @param middleware The middleware to add. * @return The updated adapter object. * Middleware is added to the adapter at initialization time. * For each turn, the adapter calls middleware in the order in which you added it. - * */ public BotAdapter Use(Middleware middleware) { _middlewareSet.Use(middleware); @@ -64,7 +61,8 @@ public BotAdapter Use(Middleware middleware) { /** * When overridden in a derived class, sends activities to the conversation. - * @param context The context object for the turn. + * + * @param context The context object for the turn. * @param activities The activities to send. * @return A task that represents the work queued to execute. * If the activities are successfully sent, the task result contains @@ -72,12 +70,13 @@ public BotAdapter Use(Middleware middleware) { * the receiving channel assigned to the activities. * {@linkalso TurnContext.OnSendActivities(SendActivitiesHandler)} */ - public abstract CompletableFuture SendActivities(TurnContext context, Activity[] activities) throws InterruptedException ; + public abstract ResourceResponse[] SendActivities(TurnContext context, Activity[] activities) throws InterruptedException; /** * When overridden in a derived class, replaces an existing activity in the * conversation. - * @param context The context object for the turn. + * + * @param context The context object for the turn. * @param activity New replacement activity. * @return A task that represents the work queued to execute. * If the activity is successfully sent, the task result contains @@ -87,94 +86,92 @@ public BotAdapter Use(Middleware middleware) { * of the activity to replace.

* {@linkalso TurnContext.OnUpdateActivity(UpdateActivityHandler)} */ - public abstract CompletableFuture UpdateActivity(TurnContext context, Activity activity); + public abstract ResourceResponse UpdateActivity(TurnContext context, Activity activity); /** * When overridden in a derived class, deletes an existing activity in the * conversation. - * @param context The context object for the turn. + * + * @param context The context object for the turn. * @param reference Conversation reference for the activity to delete. * @return A task that represents the work queued to execute. * The {@link ConversationReference.ActivityId} of the conversation * reference identifies the activity to delete. * {@linkalso TurnContext.OnDeleteActivity(DeleteActivityHandler)} */ - public abstract CompletableFuture DeleteActivity(TurnContext context, ConversationReference reference) throws ExecutionException, InterruptedException; + public abstract void DeleteActivity(TurnContext context, ConversationReference reference) throws ExecutionException, InterruptedException; /** * Starts activity processing for the current bot turn. - * @param context The turn's context object. + * + * @param context The turn's context object. * @param callback A callback method to run at the end of the pipeline. * @return A task that represents the work queued to execute. - * @throws NullPointerException - * {@code context} is null. - * The adapter calls middleware in the order in which you added it. - * The adapter passes in the context object for the turn and a next delegate, - * and the middleware calls the delegate to pass control to the next middleware - * in the pipeline. Once control reaches the end of the pipeline, the adapter calls - * the {@code callback} method. If a middleware component doesn’t call - * the next delegate, the adapter does not call any of the subsequent middleware’s - * {@link Middleware.OnTurn(TurnContext, MiddlewareSet.NextDelegate)} - * methods or the callback method, and the pipeline short circuits. - *

When the turn is initiated by a user activity (reactive messaging), the - * callback method will be a reference to the bot's - * {@link Bot.OnTurn(TurnContext)} method. When the turn is - * initiated by a call to {@link ContinueConversation(ConversationReference, Func{TurnContext, Task})} - * (proactive messaging), the callback method is the callback method that was provided in the call.

- * + * @throws NullPointerException {@code context} is null. + * The adapter calls middleware in the order in which you added it. + * The adapter passes in the context object for the turn and a next delegate, + * and the middleware calls the delegate to pass control to the next middleware + * in the pipeline. Once control reaches the end of the pipeline, the adapter calls + * the {@code callback} method. If a middleware component doesn’t call + * the next delegate, the adapter does not call any of the subsequent middleware’s + * {@link Middleware.OnTurn(TurnContext, MiddlewareSet.NextDelegate)} + * methods or the callback method, and the pipeline short circuits. + *

When the turn is initiated by a user activity (reactive messaging), the + * callback method will be a reference to the bot's + * {@link Bot.OnTurn(TurnContext)} method. When the turn is + * initiated by a call to {@link ContinueConversation(ConversationReference, Func{TurnContext, Task})} + * (proactive messaging), the callback method is the callback method that was provided in the call.

*/ - protected CompletableFuture RunPipeline(TurnContext context, Function callback) throws Exception { + protected void RunPipeline(TurnContext context, Consumer callback) throws Exception { BotAssert.ContextNotNull(context); // Call any registered Middleware Components looking for ReceiveActivity() if (context.getActivity() != null) { - await(_middlewareSet.ReceiveActivityWithStatus(context, callback)); - } - else { + _middlewareSet.ReceiveActivityWithStatus(context, callback); + } else { // call back to caller on proactive case if (callback != null) { - await(callback.apply(context)); + callback.accept(context); } } - return completedFuture(null); + return; } /** * Creates a conversation on the specified channel. + * * @param channelId The ID of the channel. - * @param callback A method to call when the new conversation is available. + * @param callback A method to call when the new conversation is available. * @return A task that represents the work queued to execute. - * @throws UnsupportedOperationException - * No base implementation is provided. + * @throws UnsupportedOperationException No base implementation is provided. */ - public CompletableFuture CreateConversation(String channelId, Function callback) - { + public CompletableFuture CreateConversation(String channelId, Function callback) { throw new UnsupportedOperationException("Adapter does not support CreateConversation with this arguments"); } /** * Sends a proactive message to a conversation. - * @param botId The application ID of the bot. This paramter is ignored in - * single tenant the Adpters (Console, Test, etc) but is critical to the BotFrameworkAdapter - * which is multi-tenant aware. + * + * @param botId The application ID of the bot. This paramter is ignored in + * single tenant the Adpters (Console, Test, etc) but is critical to the BotFrameworkAdapter + * which is multi-tenant aware. * @param reference A reference to the conversation to continue. - * @param callback The method to call for the resulting bot turn. + * @param callback The method to call for the resulting bot turn. * @return A task that represents the work queued to execute. * Call this method to proactively send a message to a conversation. * Most channels require a user to initaiate a conversation with a bot * before the bot can send activities to the user. - * {@linkalso RunPipeline(TurnContext, Func{TurnContext, Task})} + * {@linkalso RunPipeline(TurnContext, Func { TurnContext, Task })} */ - public CompletableFuture ContinueConversation(String botId, ConversationReference reference, Function callback) throws Exception { + public void ContinueConversation(String botId, ConversationReference reference, Consumer callback) throws Exception { ConversationReferenceHelper conv = new ConversationReferenceHelper(reference); ActivityImpl activity = conv.GetPostToBotMessage(); - try (TurnContextImpl context = new TurnContextImpl(this, activity)) - { - return this.RunPipeline(context, callback); + try (TurnContextImpl context = new TurnContextImpl(this, activity)) { + this.RunPipeline(context, callback); } } } diff --git a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java index 779366ec5..3b30ffa89 100644 --- a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java +++ b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java @@ -20,9 +20,9 @@ import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.function.Consumer; import java.util.function.Function; -import static com.ea.async.Async.await; import static java.util.concurrent.CompletableFuture.completedFuture; /** @@ -37,7 +37,7 @@ * and then back out again. As each activity flows in and out of the bot, each piece * of middleware can inspect or act upon the activity, both before and after the bot * logic runs.

- * + *

* {@linkalso TurnContext} * {@linkalso Activity} * {@linkalso Bot} @@ -55,16 +55,15 @@ public class BotFrameworkAdapter extends BotAdapter { /** * Initializes a new instance of the {@link BotFrameworkAdapter} class, * using a credential provider. - * @param credentialProvider The credential provider. - * @param connectorClientRetryStrategy Retry strategy for retrying HTTP operations. - * @param httpClient The HTTP client. - * @param middleware The middleware to initially add to the adapter. - * @throws IllegalArgumentException - * {@code credentialProvider} is {@code null}. - * Use a {@link MiddlewareSet} object to add multiple middleware - * components in the conustructor. Use the {@link Use(Middleware)} method to - * add additional middleware to the adapter after construction. * + * @param credentialProvider The credential provider. + * @param connectorClientRetryStrategy Retry strategy for retrying HTTP operations. + * @param httpClient The HTTP client. + * @param middleware The middleware to initially add to the adapter. + * @throws IllegalArgumentException {@code credentialProvider} is {@code null}. + * Use a {@link MiddlewareSet} object to add multiple middleware + * components in the conustructor. Use the {@link Use(Middleware)} method to + * add additional middleware to the adapter after construction. */ public BotFrameworkAdapter(CredentialProvider credentialProvider) { this(credentialProvider, null, null, null); @@ -92,32 +91,32 @@ public BotFrameworkAdapter(CredentialProvider credentialProvider, RetryStrategy /** * Sends a proactive message from the bot to a conversation. - * @param botAppId The application ID of the bot. This is the appId returned by Portal registration, and is - * generally found in the "MicrosoftAppId" parameter in appSettings.json. + * + * @param botAppId The application ID of the bot. This is the appId returned by Portal registration, and is + * generally found in the "MicrosoftAppId" parameter in appSettings.json. * @param reference A reference to the conversation to continue. - * @param callback The method to call for the resulting bot turn. + * @param callback The method to call for the resulting bot turn. * @return A task that represents the work queued to execute. - * @throws IllegalArgumentException - * {@code botAppId}, {@code reference}, or - * {@code callback} is {@code null}. - * Call this method to proactively send a message to a conversation. - * Most channels require a user to initaiate a conversation with a bot - * before the bot can send activities to the user. - *

This method registers the following.services().for the turn. - * {@link ConnectorClient}, the channel connector client to use this turn. - *

- *

- * This overload differers from the Node implementation by requiring the BotId to be - * passed in. The .Net code allows multiple bots to be hosted in a single adapter which - * isn't something supported by Node. - *

- * - * {@linkalso ProcessActivity(String, Activity, Func{TurnContext, Task})} - * {@linkalso BotAdapter.RunPipeline(TurnContext, Func{TurnContext, Task}} + * @throws IllegalArgumentException {@code botAppId}, {@code reference}, or + * {@code callback} is {@code null}. + * Call this method to proactively send a message to a conversation. + * Most channels require a user to initaiate a conversation with a bot + * before the bot can send activities to the user. + *

This method registers the following.services().for the turn. + * {@link ConnectorClient}, the channel connector client to use this turn. + *

+ *

+ * This overload differers from the Node implementation by requiring the BotId to be + * passed in. The .Net code allows multiple bots to be hosted in a single adapter which + * isn't something supported by Node. + *

+ *

+ * {@linkalso ProcessActivity(String, Activity, Func { TurnContext, Task })} + * {@linkalso BotAdapter.RunPipeline(TurnContext, Func { TurnContext, Task } } */ @Override - public CompletableFuture ContinueConversation(String botAppId, ConversationReference reference, Function callback) throws Exception { + public void ContinueConversation(String botAppId, ConversationReference reference, Consumer callback) throws Exception { if (StringUtils.isEmpty(botAppId)) throw new IllegalArgumentException("botAppId"); @@ -136,25 +135,25 @@ public CompletableFuture ContinueConversation(String botAppId, ConversationRefer context.getServices().Add("BotIdentity", claimsIdentity); - ConnectorClient connectorClient = await(this.CreateConnectorClientAsync(reference.serviceUrl(), claimsIdentity)); + ConnectorClient connectorClient = this.CreateConnectorClientAsync(reference.serviceUrl(), claimsIdentity).join(); context.getServices().Add("ConnectorClient", connectorClient); - await(RunPipeline(context, callback)); + RunPipeline(context, callback); } - return completedFuture(null); + return; } /** * Initializes a new instance of the {@link BotFrameworkAdapter} class, * using an application ID and secret. - * @param appId The application ID of the bot. - * @param appPassword The application secret for the bot. - * @param connectorClientRetryStrategy Retry policy for retrying HTTP operations. - * @param httpClient The HTTP client. - * @param middleware The middleware to initially add to the adapter. - * Use a {@link MiddlewareSet} object to add multiple middleware - * components in the conustructor. Use the {@link Use(Middleware)} method to - * add additional middleware to the adapter after construction. * + * @param appId The application ID of the bot. + * @param appPassword The application secret for the bot. + * @param connectorClientRetryStrategy Retry policy for retrying HTTP operations. + * @param httpClient The HTTP client. + * @param middleware The middleware to initially add to the adapter. + * Use a {@link MiddlewareSet} object to add multiple middleware + * components in the conustructor. Use the {@link Use(Middleware)} method to + * add additional middleware to the adapter after construction. */ public BotFrameworkAdapter(String appId, String appPassword) { this(appId, appPassword, null, null, null); @@ -174,11 +173,11 @@ public BotFrameworkAdapter(String appId, String appPassword, RetryStrategy conne /** * Adds middleware to the adapter's pipeline. + * * @param middleware The middleware to add. * @return The updated adapter object. * Middleware is added to the adapter at initialization time. * For each turn, the adapter calls middleware in the order in which you added it. - * */ public BotFrameworkAdapter Use(Middleware middleware) { @@ -188,25 +187,24 @@ public BotFrameworkAdapter Use(Middleware middleware) { /** * Creates a turn context and runs the middleware pipeline for an incoming activity. + * * @param authHeader The HTTP authentication header of the request. - * @param activity The incoming activity. - * @param callback The code to run at the end of the adapter's middleware - * pipeline. + * @param activity The incoming activity. + * @param callback The code to run at the end of the adapter's middleware + * pipeline. * @return A task that represents the work queued to execute. If the activity type * was 'Invoke' and the corresponding key (channelId + activityId) was found * then an InvokeResponse is returned, otherwise null is returned. - * @throws IllegalArgumentException - * {@code activity} is {@code null}. - * @throws UnauthorizedAccessException - * authentication failed. - * Call this method to reactively send a message to a conversation. - *

This method registers the following.services().for the turn. - * {@link ConnectorClient}, the channel connector client to use this turn. - *

- * - * {@linkalso ContinueConversation(String, ConversationReference, Func{TurnContext, Task})} - * {@linkalso BotAdapter.RunPipeline(TurnContext, Func{TurnContext, Task})} + * @throws IllegalArgumentException {@code activity} is {@code null}. + * @throws UnauthorizedAccessException authentication failed. + * Call this method to reactively send a message to a conversation. + *

This method registers the following.services().for the turn. + * {@link ConnectorClient}, the channel connector client to use this turn. + *

+ *

+ * {@linkalso ContinueConversation(String, ConversationReference, Func { TurnContext, Task })} + * {@linkalso BotAdapter.RunPipeline(TurnContext, Func { TurnContext, Task })} */ public CompletableFuture ProcessActivity(String authHeader, ActivityImpl activity, Function callback) throws Exception { BotAssert.ActivityNotNull(activity); @@ -217,17 +215,17 @@ public CompletableFuture ProcessActivity(String authHeader, Acti return completedFuture(null); } - public CompletableFuture ProcessActivity(ClaimsIdentity identity, ActivityImpl activity, Function callback) throws Exception { + public CompletableFuture ProcessActivity(ClaimsIdentity identity, ActivityImpl activity, Consumer callback) throws Exception { BotAssert.ActivityNotNull(activity); try (TurnContextImpl context = new TurnContextImpl(this, activity)) { context.getServices().Add("BotIdentity", identity); - ConnectorClient connectorClient = await(this.CreateConnectorClientAsync(activity.serviceUrl(), identity)); + ConnectorClient connectorClient = this.CreateConnectorClientAsync(activity.serviceUrl(), identity).join(); // TODO: Verify key that C# uses context.getServices().Add("ConnectorClient", connectorClient); - await(super.RunPipeline(context, callback)); + super.RunPipeline(context, callback); // Handle Invoke scenarios, which deviate from the request/response model in that // the Bot will return a specific body and return code. @@ -249,7 +247,8 @@ public CompletableFuture ProcessActivity(ClaimsIdentity identity /** * Sends activities to the conversation. - * @param context The context object for the turn. + * + * @param context The context object for the turn. * @param activities The activities to send. * @return A task that represents the work queued to execute. * If the activities are successfully sent, the task result contains @@ -257,7 +256,7 @@ public CompletableFuture ProcessActivity(ClaimsIdentity identity * the receiving channel assigned to the activities. * {@linkalso TurnContext.OnSendActivities(SendActivitiesHandler)} */ - public CompletableFuture SendActivities(TurnContext context, Activity[] activities) throws InterruptedException { + public ResourceResponse[] SendActivities(TurnContext context, Activity[] activities) throws InterruptedException { if (context == null) { throw new IllegalArgumentException("context"); } @@ -296,12 +295,10 @@ public CompletableFuture SendActivities(TurnContext context, // if it is a Trace activity we only send to the channel if it's the emulator. } else if (!StringUtils.isEmpty(activity.replyToId())) { ConnectorClient connectorClient = context.getServices().Get("ConnectorClient"); - // TODO - //response = await(connectorClient.conversations().ReplyToActivityAsync(activity.conversation().id(), activity.id(), activity)); + response = connectorClient.conversations().replyToActivity(activity.conversation().id(), activity.id(), activity); } else { ConnectorClient connectorClient = context.getServices().Get("ConnectorClient"); - // TODO - //response = Async.await(connectorClient.conversations().SendToConversationAsync(activity.conversation().id(), activity)); + response = connectorClient.conversations().sendToConversation(activity.conversation().id(), activity); } // If No response is set, then defult to a "simple" response. This can't really be done @@ -320,12 +317,13 @@ public CompletableFuture SendActivities(TurnContext context, responses[index] = response; } - return completedFuture(responses); + return responses; } /** * Replaces an existing activity in the conversation. - * @param context The context object for the turn. + * + * @param context The context object for the turn. * @param activity New replacement activity. * @return A task that represents the work queued to execute. * If the activity is successfully sent, the task result contains @@ -336,45 +334,44 @@ public CompletableFuture SendActivities(TurnContext context, * {@linkalso TurnContext.OnUpdateActivity(UpdateActivityHandler)} */ @Override - public CompletableFuture UpdateActivity(TurnContext context, Activity activity) { + public ResourceResponse UpdateActivity(TurnContext context, Activity activity) { ConnectorClient connectorClient = context.getServices().Get("ConnectorClient"); - // TODO - //return await(connectorClient.conversations().updateActivityAsync(activity)); - return completedFuture(null); + // TODO String conversationId, String activityId, Activity activity) + return connectorClient.conversations().updateActivity(activity.conversation().id(), activity.id(), activity); } /** * Deletes an existing activity in the conversation. - * @param context The context object for the turn. + * + * @param context The context object for the turn. * @param reference Conversation reference for the activity to delete. * @return A task that represents the work queued to execute. * The {@link ConversationReference.ActivityId} of the conversation * reference identifies the activity to delete. * {@linkalso TurnContext.OnDeleteActivity(DeleteActivityHandler)} */ - public CompletableFuture DeleteActivity(TurnContext context, ConversationReference reference) { - return CompletableFuture.supplyAsync(() -> { - ConnectorClientImpl connectorClient = context.getServices().Get("ConnectorClient"); - try { - await(connectorClient.conversations().deleteConversationMemberFuture(reference.conversation().id(), reference.activityId())); - } catch (ExecutionException e) { - e.printStackTrace(); - throw new RuntimeException(String.format("Failed deleting activity (%s)", e.toString())); - } catch (InterruptedException e) { - e.printStackTrace(); - throw new RuntimeException(String.format("Failed deleting activity (%s)", e.toString())); - } - return null; - }); + public void DeleteActivity(TurnContext context, ConversationReference reference) { + ConnectorClientImpl connectorClient = context.getServices().Get("ConnectorClient"); + try { + connectorClient.conversations().deleteConversationMemberFuture(reference.conversation().id(), reference.activityId()).join(); + } catch (ExecutionException e) { + e.printStackTrace(); + throw new RuntimeException(String.format("Failed deleting activity (%s)", e.toString())); + } catch (InterruptedException e) { + e.printStackTrace(); + throw new RuntimeException(String.format("Failed deleting activity (%s)", e.toString())); + } + return; } /** * Deletes a member from the current conversation - * @param context The context object for the turn. + * + * @param context The context object for the turn. * @param memberId ID of the member to delete from the conversation - * @return + * @return */ - public CompletableFuture DeleteConversationMember(TurnContextImpl context, String memberId) { + public void DeleteConversationMember(TurnContextImpl context, String memberId) { if (context.getActivity().conversation() == null) throw new IllegalArgumentException("BotFrameworkAdapter.deleteConversationMember(): missing conversation"); @@ -387,12 +384,13 @@ public CompletableFuture DeleteConversationMember(TurnContextImpl context, // TODO: //await (connectorClient.conversations().DeleteConversationMemberAsync(conversationId, memberId)); - return completedFuture(null); + return; } /** * Lists the members of a given activity. - * @param context The context object for the turn. + * + * @param context The context object for the turn. * @param activityId (Optional) Activity ID to enumerate. If not specified the current activities ID will be used. * @return List of Members of the activity */ @@ -422,6 +420,7 @@ public CompletableFuture> GetActivityMembers(TurnContextImp /** * Lists the members of the current conversation. + * * @param context The context object for the turn. * @return List of Members of the current conversation */ @@ -444,17 +443,17 @@ public CompletableFuture> GetConversationMembers(TurnContex * Lists the Conversations in which this bot has participated for a given channel server. The * channel server returns results in pages and each page will include a `continuationToken` * that can be used to fetch the next page of results from the server. - * @param serviceUrl The URL of the channel server to query. This can be retrieved - * from `context.activity.serviceUrl`. - * @param credentials The credentials needed for the Bot to connect to the.services(). + * + * @param serviceUrl The URL of the channel server to query. This can be retrieved + * from `context.activity.serviceUrl`. + * @param credentials The credentials needed for the Bot to connect to the.services(). * @param continuationToken (Optional) token used to fetch the next page of results - * from the channel server. This should be left as `null` to retrieve the first page - * of results. + * from the channel server. This should be left as `null` to retrieve the first page + * of results. * @return List of Members of the current conversation - * + *

* This overload may be called from outside the context of a conversation, as only the * Bot's ServiceUrl and credentials are required. - * */ public CompletableFuture GetConversations(String serviceUrl, MicrosoftAppCredentials credentials) throws MalformedURLException, URISyntaxException { return GetConversations(serviceUrl, credentials, null); @@ -477,16 +476,16 @@ public CompletableFuture GetConversations(String serviceUrl * Lists the Conversations in which this bot has participated for a given channel server. The * channel server returns results in pages and each page will include a `continuationToken` * that can be used to fetch the next page of results from the server. - * @param context The context object for the turn. + * + * @param context The context object for the turn. * @param continuationToken (Optional) token used to fetch the next page of results - * from the channel server. This should be left as `null` to retrieve the first page - * of results. + * from the channel server. This should be left as `null` to retrieve the first page + * of results. * @return List of Members of the current conversation - * + *

* This overload may be called during standard Activity processing, at which point the Bot's * service URL and credentials that are part of the current activity processing pipeline * will be used. - * */ public CompletableFuture GetConversations(TurnContextImpl context) { return GetConversations(context, null); @@ -502,9 +501,10 @@ public CompletableFuture GetConversations(TurnContextImpl c /** * Attempts to retrieve the token for a user that's in a login flow. - * @param context Context for the current turn of conversation with the user. + * + * @param context Context for the current turn of conversation with the user. * @param connectionName Name of the auth connection to use. - * @param magicCode (Optional) Optional user entered code to validate. + * @param magicCode (Optional) Optional user entered code to validate. * @return Token Response */ public CompletableFuture GetUserToken(TurnContextImpl context, String connectionName, String magicCode) { @@ -522,9 +522,10 @@ public CompletableFuture GetUserToken(TurnContextImpl context, St /** * Get the raw signin link to be sent to the user for signin for a connection name. - * @param context Context for the current turn of conversation with the user. + * + * @param context Context for the current turn of conversation with the user. * @param connectionName Name of the auth connection to use. - * @return + * @return */ public CompletableFuture GetOauthSignInLink(TurnContextImpl context, String connectionName) { BotAssert.ContextNotNull(context); @@ -538,9 +539,10 @@ public CompletableFuture GetOauthSignInLink(TurnContextImpl context, Str /** * Signs the user out with the token server. - * @param context Context for the current turn of conversation with the user. + * + * @param context Context for the current turn of conversation with the user. * @param connectionName Name of the auth connection to use. - * @return + * @return */ public CompletableFuture SignOutUser(TurnContextImpl context, String connectionName) { BotAssert.ContextNotNull(context); @@ -554,12 +556,13 @@ public CompletableFuture SignOutUser(TurnContextImpl context, String connectionN /** * Creates a conversation on the specified channel. - * @param channelId The ID for the channel. - * @param serviceUrl The channel's service URL endpoint. - * @param credentials The application credentials for the bot. + * + * @param channelId The ID for the channel. + * @param serviceUrl The channel's service URL endpoint. + * @param credentials The application credentials for the bot. * @param conversationParameters The conversation information to use to - * create the conversation. - * @param callback The method to call for the resulting bot turn. + * create the conversation. + * @param callback The method to call for the resulting bot turn. * @return A task that represents the work queued to execute. * To start a conversation, your bot must know its account information * and the user's account information on that channel. @@ -570,10 +573,9 @@ public CompletableFuture SignOutUser(TurnContextImpl context, String connectionN *

If the conversation is established with the * specified users, the ID of the activity's {@link Activity.Conversation} * will contain the ID of the new conversation.

- * */ public CompletableFuture CreateConversation(String channelId, String serviceUrl, MicrosoftAppCredentials - credentials, ConversationParameters conversationParameters, Function callback) throws Exception { + credentials, ConversationParameters conversationParameters, Consumer callback) throws Exception { // Validate serviceUrl - can throw URI uri = new URI(serviceUrl); return CompletableFuture.runAsync(() -> { @@ -592,9 +594,8 @@ public CompletableFuture CreateConversation(String channelId, String serviceUrl, List results = null; if (conv instanceof ConversationsImpl) { ConversationsImpl convImpl = (ConversationsImpl) conv; - results = await(convImpl.CreateConversationAsync(conversationParameters)); - } - else { + results = convImpl.CreateConversationAsync(conversationParameters).join(); + } else { results = new ArrayList(); results.add(conv.createConversation(conversationParameters)); } @@ -614,7 +615,7 @@ public CompletableFuture CreateConversation(String channelId, String serviceUrl, try (TurnContextImpl context = new TurnContextImpl(this, conversationUpdate)) { try { - await(this.RunPipeline(context, callback)); + this.RunPipeline(context, callback); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(String.format("Running pipeline failed : %s", e)); @@ -634,7 +635,7 @@ public CompletableFuture CreateConversation(String channelId, String serviceUrl, protected CompletableFuture TrySetEmulatingOAuthCards(TurnContext turnContext) { if (!isEmulatingOAuthCards && turnContext.getActivity().channelId().equals("emulator") && - (await(_credentialProvider.isAuthenticationDisabledAsync()))) { + (_credentialProvider.isAuthenticationDisabledAsync().join())) { isEmulatingOAuthCards = true; } return completedFuture(isEmulatingOAuthCards); @@ -654,7 +655,8 @@ protected OAuthClient CreateOAuthApiClient(TurnContext context) throws Malformed /** * Creates the connector client asynchronous. - * @param serviceUrl The service URL. + * + * @param serviceUrl The service URL. * @param claimsIdentity The claims identity. * @return ConnectorClient instance. * @throws UnsupportedOperationException ClaimsIdemtity cannot be null. Pass Anonymous ClaimsIdentity if authentication is turned off. @@ -696,7 +698,7 @@ private CompletableFuture CreateConnectorClientAsync(String ser if (botAppIdClaim != null) { String botId = botAppIdClaim.getValue(); - MicrosoftAppCredentials appCredentials = await(this.GetAppCredentialsAsync(botId)); + MicrosoftAppCredentials appCredentials = this.GetAppCredentialsAsync(botId).join(); try { return this.CreateConnectorClient(serviceUrl, appCredentials); } catch (MalformedURLException e) { @@ -723,7 +725,8 @@ private CompletableFuture CreateConnectorClientAsync(String ser /** * Creates the connector client. - * @param serviceUrl The service URL. + * + * @param serviceUrl The service URL. * @param appCredentials The application credentials for the bot. * @return Connector client instance. */ @@ -753,6 +756,7 @@ private ConnectorClient CreateConnectorClient(String serviceUrl, MicrosoftAppCre /** * Gets the application credentials. App Credentials are cached so as to ensure we are not refreshing * token everytime. + * * @param appId The application identifier (AAD Id for the bot). * @return App credentials. */ @@ -763,7 +767,7 @@ private CompletableFuture GetAppCredentialsAsync(String } if (this.appCredentialMap.containsKey(appId)) return this.appCredentialMap.get(appId); - String appPassword = await(this._credentialProvider.getAppPasswordAsync(appId)); + String appPassword = this._credentialProvider.getAppPasswordAsync(appId).join(); MicrosoftAppCredentials appCredentials = new MicrosoftAppCredentials(appId, appPassword); this.appCredentialMap.put(appId, appCredentials); return appCredentials; diff --git a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/BotState.java b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/BotState.java index fdcac7603..0f480242d 100644 --- a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/BotState.java +++ b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/BotState.java @@ -4,25 +4,24 @@ import com.fasterxml.jackson.core.JsonProcessingException; - -import java.util.*; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.StreamSupport; -import static com.ea.async.Async.await; import static java.util.concurrent.CompletableFuture.completedFuture; /** * Abstract Base class which manages details of automatic loading and saving of bot state. + * * @param TState The type of the bot state object. */ //public class BotState : Middleware // where TState : class, new() -public class BotState implements Middleware -{ +public class BotState implements Middleware { private final StateSettings settings; private final Storage storage; @@ -32,13 +31,15 @@ public class BotState implements Middleware /** * Creates a new {@link BotState{TState}} middleware object. - * @param name The name to use to load or save the state object. - * @param storage The storage provider to use. + * + * @param name The name to use to load or save the state object. + * @param storage The storage provider to use. * @param settings The state persistance options to use. */ public BotState(Storage storage, String propertyName, Function keyDelegate, Supplier ctor) { this(storage, propertyName, keyDelegate, ctor, null); } + public BotState(Storage storage, String propertyName, Function keyDelegate, Supplier ctor, StateSettings settings) { if (null == storage) { throw new IllegalArgumentException("Storage"); @@ -65,65 +66,63 @@ public BotState(Storage storage, String propertyName, Function { - String key = this.keyDelegate.apply(context); - Map items = null; - try { - CompletableFuture> result = storage.Read(new String[] { key }); - items = result.get(); - System.out.println(String.format("BotState:OnTurn(tid:%s) ReadToContextService: Found %s items", Thread.currentThread().getId(), items.size())); - } catch (JsonProcessingException e) { - e.printStackTrace(); - throw new RuntimeException(String.format("Error waiting context storage read: %s", e.toString())); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); - } - TState state = StreamSupport.stream(items.entrySet().spliterator(), false) - .filter(entry -> entry.getKey() == key) - .map(Map.Entry::getValue) - .map(entry -> (TState) entry) - .findFirst() - .orElse(null); - + protected void ReadToContextService(TurnContext context) throws IllegalArgumentException, JsonProcessingException { + String key = this.keyDelegate.apply(context); + Map items = null; + try { + CompletableFuture> result = storage.Read(new String[]{key}); + items = result.get(); + System.out.println(String.format("BotState:OnTurn(tid:%s) ReadToContextService: Found %s items", Thread.currentThread().getId(), items.size())); + } catch (JsonProcessingException e) { + e.printStackTrace(); + throw new RuntimeException(String.format("Error waiting context storage read: %s", e.toString())); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + TState state = StreamSupport.stream(items.entrySet().spliterator(), false) + .filter(entry -> entry.getKey() == key) + .map(Map.Entry::getValue) + .map(entry -> (TState) entry) + .findFirst() + .orElse(null); - //var state = items.Where(entry => entry.Key == key).Select(entry => entry.Value).OfType().FirstOrDefault(); - if (state == null) - state = ctor.get(); - context.getServices().Add(this.propertyName, state); - }); + //var state = items.Where(entry => entry.Key == key).Select(entry => entry.Value).OfType().FirstOrDefault(); + if (state == null) + state = ctor.get(); + context.getServices().Add(this.propertyName, state); } protected CompletableFuture WriteFromContextService(TurnContext context) throws Exception { TState state = context.getServices().Get(this.propertyName); - return completedFuture(await(Write(context, state))); + return Write(context, state); } /** * Reads state from storage. - * @param TState The type of the bot state object. + * + * @param TState The type of the bot state object. * @param context The context object for this turn. */ public CompletableFuture Read(TurnContext context) throws JsonProcessingException { String key = this.keyDelegate.apply(context); - Map items = await( storage.Read(new String[] { key })); + Map items = storage.Read(new String[]{key}).join(); TState state = StreamSupport.stream(items.entrySet().spliterator(), false) .filter(item -> item.getKey() == key) .map(Map.Entry::getValue) @@ -138,8 +137,9 @@ public CompletableFuture Read(TurnContext context) throws JsonProcessing /** * Writes state to storage. + * * @param context The context object for this turn. - * @param state The state object. + * @param state The state object. */ public CompletableFuture Write(TurnContext context, TState state) throws Exception { HashMap changes = new HashMap(); @@ -150,14 +150,14 @@ public CompletableFuture Write(TurnContext context, TState state) throws Excepti changes.put(key, state); if (this.settings.getLastWriterWins()) { - for (Map.Entry item : changes.entrySet()) { + for (Map.Entry item : changes.entrySet()) { if (item.getValue() instanceof StoreItem) { StoreItem valueStoreItem = (StoreItem) item.getValue(); valueStoreItem.seteTag("*"); } } } - return completedFuture(await(storage.Write(changes))); + return completedFuture(storage.Write(changes).join()); } } diff --git a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/CatchExceptionMiddleware.java b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/CatchExceptionMiddleware.java index caca18033..e684af15e 100644 --- a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/CatchExceptionMiddleware.java +++ b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/CatchExceptionMiddleware.java @@ -2,20 +2,15 @@ package com.microsoft.bot.builder; -import java.util.concurrent.CompletableFuture; - -import static com.ea.async.Async.await; -import static java.util.concurrent.CompletableFuture.completedFuture; - /** * This piece of middleware can be added to allow you to handle exceptions when they are thrown * within your bot's code or middleware further down the pipeline. Using this handler you might * send an appropriate message to the user to let them know that something has gone wrong. * You can specify the type of exception the middleware should catch and this middleware can be added * multiple times to allow you to handle different exception types in different ways. - * @param T - * The type of the exception that you want to catch. This can be 'Exception' to - * catch all or a specific type of exception + * + * @param T The type of the exception that you want to catch. This can be 'Exception' to + * catch all or a specific type of exception */ public class CatchExceptionMiddleware implements Middleware { private final CallOnException _handler; @@ -28,7 +23,7 @@ public CatchExceptionMiddleware(CallOnException callOnException, Class except @Override - public CompletableFuture OnTurn(TurnContext context, NextDelegate next) throws Exception { + public void OnTurn(TurnContext context, NextDelegate next) throws Exception { Class c = _exceptionType.getDeclaringClass(); @@ -36,16 +31,16 @@ public CompletableFuture OnTurn(TurnContext context, NextDelegate next) throws E // Continue to route the activity through the pipeline // any errors further down the pipeline will be caught by // this try / catch - await(next.next()); + next.next(); } catch (Exception ex) { if (_exceptionType.isInstance(ex)) // If an error is thrown and the exception is of type T then invoke the handler - await(_handler.apply(context, (T)ex)); + _handler.apply(context, (T) ex); else throw ex; } - return completedFuture(null); + return; } } diff --git a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/DeleteActivityHandler.java b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/DeleteActivityHandler.java index c55767b21..2847152a2 100644 --- a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/DeleteActivityHandler.java +++ b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/DeleteActivityHandler.java @@ -24,5 +24,5 @@ */ @FunctionalInterface public interface DeleteActivityHandler { - CompletableFuture handle(TurnContext context, ConversationReference reference, Callable next); + void handle(TurnContext context, ConversationReference reference, Runnable next) throws Exception; } diff --git a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/MemoryTranscriptStore.java b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/MemoryTranscriptStore.java new file mode 100644 index 000000000..c332058df --- /dev/null +++ b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/MemoryTranscriptStore.java @@ -0,0 +1,310 @@ +package com.microsoft.bot.builder; + + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + + +import com.microsoft.bot.schema.models.Activity; +import org.joda.time.DateTime; + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinWorkerThread; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * The memory transcript store stores transcripts in volatile memory in a Dictionary. + *

+ *

+ * Because this uses an unbounded volitile dictionary this should only be used for unit tests or non-production environments. + */ +public class MemoryTranscriptStore implements TranscriptStore { + private HashMap>> channels = new HashMap>>(); + final ForkJoinPool.ForkJoinWorkerThreadFactory factory = new ForkJoinPool.ForkJoinWorkerThreadFactory() { + @Override + public ForkJoinWorkerThread newThread(ForkJoinPool pool) { + final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool); + worker.setName("TestFlow-" + worker.getPoolIndex()); + return worker; + } + }; + + final ExecutorService executor = new ForkJoinPool(Runtime.getRuntime().availableProcessors(), factory, null, false); + + + /** + * Logs an activity to the transcript. + * + * @param activity The activity to log. + * @return A CompletableFuture that represents the work queued to execute. + */ + public final void LogActivityAsync(Activity activity) { + if (activity == null) { + throw new NullPointerException("activity cannot be null for LogActivity()"); + } + + synchronized (this.channels) { + HashMap> channel; + if (!this.channels.containsKey(activity.channelId())) { + channel = new HashMap>(); + this.channels.put(activity.channelId(), channel); + } else { + channel = this.channels.get(activity.channelId()); + } + + ArrayList transcript = null; + + + if (!channel.containsKey(activity.conversation().id())) { + transcript = new ArrayList(); + channel.put(activity.conversation().id(), transcript); + } else { + transcript = channel.get(activity.conversation().id()); + } + + transcript.add(activity); + } + + } + + /** + * Gets from the store activities that match a set of criteria. + * + * @param channelId The ID of the channel the conversation is in. + * @param conversationId The ID of the conversation. + * @param continuationToken + * @return A task that represents the work queued to execute. + * If the task completes successfully, the result contains the matching activities. + */ + + public final CompletableFuture> GetTranscriptActivitiesAsync(String channelId, String conversationId, String continuationToken) { + return GetTranscriptActivitiesAsync(channelId, conversationId, continuationToken, null); + } + + /** + * Gets from the store activities that match a set of criteria. + * + * @param channelId The ID of the channel the conversation is in. + * @param conversationId The ID of the conversation. + * @return A task that represents the work queued to execute. + * If the task completes successfully, the result contains the matching activities. + */ + + public final CompletableFuture> GetTranscriptActivitiesAsync(String channelId, String conversationId) { + return GetTranscriptActivitiesAsync(channelId, conversationId, null, null); + } + + /** + * Gets from the store activities that match a set of criteria. + * + * @param channelId The ID of the channel the conversation is in. + * @param conversationId The ID of the conversation. + * @param continuationToken + * @param startDate A cutoff date. Activities older than this date are not included. + * @return A task that represents the work queued to execute. + * If the task completes successfully, the result contains the matching activities. + */ + public final CompletableFuture> GetTranscriptActivitiesAsync(String channelId, String conversationId, String continuationToken, DateTime startDate) { + return CompletableFuture.supplyAsync(() -> { + if (channelId == null) { + throw new NullPointerException(String.format("missing %1$s", "channelId")); + } + + if (conversationId == null) { + throw new NullPointerException(String.format("missing %1$s", "conversationId")); + } + + PagedResult pagedResult = new PagedResult(); + synchronized (channels) { + HashMap> channel; + if (!channels.containsKey(channelId)) { + return pagedResult; + } + channel = channels.get(channelId); + ArrayList transcript; + + if (!channel.containsKey(conversationId)) { + return pagedResult; + } + transcript = channel.get(conversationId); + if (continuationToken != null) { + List items = transcript.stream() + .sorted(Comparator.comparing(Activity::timestamp)) + .filter(a -> a.timestamp().compareTo(startDate) >= 0) + .filter(skipwhile(a -> !a.id().equals(continuationToken))) + .skip(1) + .limit(20) + .collect(Collectors.toList()); + + pagedResult.items(items.toArray(new Activity[items.size()])); + + if (pagedResult.getItems().length == 20) { + pagedResult.withContinuationToken(items.get(items.size() - 1).id()); + } + } else { + List items = transcript.stream() + .sorted(Comparator.comparing(Activity::timestamp)) + .filter(a -> a.timestamp().compareTo((startDate == null) ? new DateTime(Long.MIN_VALUE) : startDate) >= 0) + .limit(20) + .collect(Collectors.toList()); + pagedResult.items(items.toArray(new Activity[items.size()])); + if (items.size() == 20) { + pagedResult.withContinuationToken(items.get(items.size() - 1).id()); + } + } + } + + return pagedResult; + + }, this.executor); + } + + /** + * Deletes conversation data from the store. + * + * @param channelId The ID of the channel the conversation is in. + * @param conversationId The ID of the conversation to delete. + * @return A task that represents the work queued to execute. + */ + public final CompletableFuture DeleteTranscriptAsync(String channelId, String conversationId) { + return CompletableFuture.runAsync(() -> { + if (channelId == null) { + throw new NullPointerException(String.format("%1$s should not be null", "channelId")); + } + + if (conversationId == null) { + throw new NullPointerException(String.format("%1$s should not be null", "conversationId")); + } + + synchronized (this.channels) { + if (!this.channels.containsKey(channelId)) { + return; + } + HashMap> channel = this.channels.get(channelId); + if (channel.containsKey(conversationId)) { + channel.remove(conversationId); + } + } + }, this.executor); + } + + /** + * Gets the conversations on a channel from the store. + * + * @param channelId The ID of the channel. + * @return A task that represents the work queued to execute. + */ + + public final CompletableFuture> ListTranscriptsAsync(String channelId) { + return ListTranscriptsAsync(channelId, null); + } + + /** + * Gets the conversations on a channel from the store. + * + * @param channelId The ID of the channel. + * @param continuationToken + * @return A task that represents the work queued to execute. + */ + + public final CompletableFuture> ListTranscriptsAsync(String channelId, String continuationToken) { + return CompletableFuture.supplyAsync(() -> { + if (channelId == null) { + throw new NullPointerException(String.format("missing %1$s", "channelId")); + } + + PagedResult pagedResult = new PagedResult(); + synchronized (channels) { + + if (!channels.containsKey(channelId)) { + return pagedResult; + } + + HashMap> channel = channels.get(channelId); + if (continuationToken != null) { + List items = channel.entrySet().stream() + .map(c -> { + OffsetDateTime offsetDateTime = null; + if (c.getValue().stream().findFirst().isPresent()) { + DateTime dt = c.getValue().stream().findFirst().get().timestamp(); + // convert to DateTime to OffsetDateTime + Instant instant = Instant.ofEpochMilli(dt.getMillis()); + ZoneOffset offset = ZoneId.of(dt.getZone().getID()).getRules().getOffset(instant); + offsetDateTime = instant.atOffset(offset); + } else { + offsetDateTime = OffsetDateTime.now(); + } + return new Transcript() + .withChannelId(channelId) + .withId(c.getKey()) + .withCreated(offsetDateTime); + } + ) + .sorted(Comparator.comparing(Transcript::getCreated)) + .filter(skipwhile(c -> !c.getId().equals(continuationToken))) + .skip(1) + .limit(20) + .collect(Collectors.toList()); + pagedResult.items(items.toArray(new Transcript[items.size()])); + if (items.size() == 20) { + pagedResult.withContinuationToken(items.get(items.size() - 1).getId()); + } + } else { + + List items = channel.entrySet().stream() + .map(c -> { + OffsetDateTime offsetDateTime = null; + if (c.getValue().stream().findFirst().isPresent()) { + DateTime dt = c.getValue().stream().findFirst().get().timestamp(); + // convert to DateTime to OffsetDateTime + Instant instant = Instant.ofEpochMilli(dt.getMillis()); + ZoneOffset offset = ZoneId.of(dt.getZone().getID()).getRules().getOffset(instant); + offsetDateTime = instant.atOffset(offset); + } else { + offsetDateTime = OffsetDateTime.now(); + } + return new Transcript() + .withChannelId(channelId) + .withId(c.getKey()) + .withCreated(offsetDateTime); + } + ) + .sorted(Comparator.comparing(Transcript::getCreated)) + .limit(20) + .collect(Collectors.toList()); + pagedResult.items(items.toArray(new Transcript[items.size()])); + if (items.size() == 20) { + pagedResult.withContinuationToken(items.get(items.size() - 1).getId()); + } + } + } + return pagedResult; + }, this.executor); + } + + /** + * Emulate C# SkipWhile. + * Stateful + * + * @param func1 predicate to apply + * @param type + * @return if the predicate condition is true + */ + public static Predicate skipwhile(Function func1) { + final boolean[] started = {false}; + return t -> started[0] || (started[0] = (boolean) func1.apply(t)); + } + +} \ No newline at end of file diff --git a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/Middleware.java b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/Middleware.java index 34ec8483a..8e952c17d 100644 --- a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/Middleware.java +++ b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/Middleware.java @@ -52,7 +52,7 @@ public interface Middleware * {@linkalso TurnContext} * {@linkalso Bot.Schema.Activity} */ - CompletableFuture OnTurn(TurnContext context, NextDelegate next) throws Exception; + void OnTurn(TurnContext context, NextDelegate next) throws Exception; } diff --git a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/MiddlewareCall.java b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/MiddlewareCall.java index 13468c81c..eff1039a0 100644 --- a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/MiddlewareCall.java +++ b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/MiddlewareCall.java @@ -4,5 +4,5 @@ @FunctionalInterface public interface MiddlewareCall { - CompletableFuture requestHandler(TurnContext tc, NextDelegate nd) throws Exception; + void requestHandler(TurnContext tc, NextDelegate nd) throws Exception; } diff --git a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/MiddlewareSet.java b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/MiddlewareSet.java index 35938d07c..1466c0b97 100644 --- a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/MiddlewareSet.java +++ b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/MiddlewareSet.java @@ -4,73 +4,66 @@ package com.microsoft.bot.builder; - import java.util.ArrayList; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import java.util.function.Function; +import java.util.function.Consumer; import static java.util.concurrent.CompletableFuture.completedFuture; -public class MiddlewareSet implements Middleware -{ +public class MiddlewareSet implements Middleware { public NextDelegate Next; private final ArrayList _middleware = new ArrayList(); - public MiddlewareSet Use(Middleware middleware) - { + public MiddlewareSet Use(Middleware middleware) { BotAssert.MiddlewareNotNull(middleware); _middleware.add(middleware); return this; } - public CompletableFuture ReceiveActivity(TurnContextImpl context) + public void ReceiveActivity(TurnContextImpl context) throws Exception { - // await ReceiveActivityInternal(context, null).ConfigureAwait(false); - return ReceiveActivityInternal(context, null); + ReceiveActivityInternal(context, null); } @Override - public CompletableFuture OnTurn(TurnContext context, NextDelegate next) throws Exception { - return completedFuture(ReceiveActivityInternal((TurnContextImpl)context, null) - .thenRun(() -> { - try { - next.next(); - } catch (Exception e) { - e.printStackTrace(); - throw new RuntimeException(String.format("MiddlewareSet::OnTurn next delegate: %s", e.toString())); - } - }) - .get()); + public void OnTurn(TurnContext context, NextDelegate next) throws Exception { + ReceiveActivityInternal((TurnContextImpl) context, null); + try { + next.next(); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(String.format("MiddlewareSet::OnTurn next delegate: %s", e.toString())); + } } - public CompletableFuture OnTurn(TurnContextImpl context, CompletableFuture next) + public void OnTurn(TurnContextImpl context, CompletableFuture next) throws ExecutionException, InterruptedException { - return null; + return; } /** * Intended to be called from Bot, this method performs exactly the same as the - * standard ReceiveActivity, except that it runs a user-defined delegate returns + * standard ReceiveActivity, except that it runs a user-defined delegate returns * if all Middleware in the receive pipeline was run. */ - public CompletableFuture ReceiveActivityWithStatus(TurnContext context, Function callback) + public void ReceiveActivityWithStatus(TurnContext context, Consumer callback) throws Exception { - return ReceiveActivityInternal(context, callback); + ReceiveActivityInternal(context, callback); } - private CompletableFuture ReceiveActivityInternal(TurnContext context, Function callback) + private void ReceiveActivityInternal(TurnContext context, Consumer callback) throws Exception { - return ReceiveActivityInternal(context, callback, 0); + ReceiveActivityInternal(context, callback, 0); } - private CompletableFuture ReceiveActivityInternal(TurnContext context, Function callback, int nextMiddlewareIndex ) + + private void ReceiveActivityInternal(TurnContext context, Consumer callback, int nextMiddlewareIndex) throws Exception { // Check if we're at the end of the middleware list yet - if(nextMiddlewareIndex == _middleware.size()) - { + if (nextMiddlewareIndex == _middleware.size()) { // If all the Middlware ran, the "leading edge" of the tree is now complete. // This means it's time to run any developer specified callback. // Once this callback is done, the "trailing edge" calls are then completed. This @@ -81,24 +74,26 @@ private CompletableFuture ReceiveActivityInternal(TurnContext context, Function< // to run as expected. // If a callback was provided invoke it now and return its task, otherwise just return the completed task - if (callback == null) - return completedFuture(null); - else - return callback.apply(context); + if (callback == null) { + return ; + } else { + callback.accept(context); + return; + } } // Get the next piece of middleware Middleware nextMiddleware = _middleware.get(nextMiddlewareIndex); NextDelegate next = new NextDelegate() { - public CompletableFuture next() throws Exception { - return ReceiveActivityInternal(context, callback, nextMiddlewareIndex + 1); + public void next() throws Exception { + ReceiveActivityInternal(context, callback, nextMiddlewareIndex + 1); } }; // Execute the next middleware passing a closure that will recurse back into this method at the next piece of middlware as the NextDelegate - return nextMiddleware.OnTurn( - context, - next); + nextMiddleware.OnTurn( + context, + next); } diff --git a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/NextDelegate.java b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/NextDelegate.java index 423bb5f0a..565812465 100644 --- a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/NextDelegate.java +++ b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/NextDelegate.java @@ -4,5 +4,5 @@ @FunctionalInterface public interface NextDelegate { - CompletableFuture next() throws Exception; + void next() throws Exception; } diff --git a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/PagedResult.java b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/PagedResult.java new file mode 100644 index 000000000..33f0418ee --- /dev/null +++ b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/PagedResult.java @@ -0,0 +1,43 @@ +package com.microsoft.bot.builder; + + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/** + Page of results from an enumeration. + + + */ +public class PagedResult +{ + /** + Page of items. + */ + +//C# TO JAVA CONVERTER WARNING: Java does not allow direct instantiation of arrays of generic type parameters: +//ORIGINAL LINE: private T[] Items = new T[0]; + private T[] items = (T[])new Object[0]; + public final T[] getItems() + { + return this.items; + } + public final void items(T[] value) + { + this.items = value; + } + + /** + Token used to page through multiple pages. + */ + private String continuationToken; + public final String continuationToken() + { + return this.continuationToken; + } + public final PagedResult withContinuationToken(String value) + { + this.continuationToken = value; + return this; + } +} diff --git a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/SendActivitiesHandler.java b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/SendActivitiesHandler.java index 133f9e1f7..66a22d160 100644 --- a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/SendActivitiesHandler.java +++ b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/SendActivitiesHandler.java @@ -10,6 +10,5 @@ @FunctionalInterface public interface SendActivitiesHandler { - CompletableFuture handle(TurnContext context, List activities, Callable> next); + ResourceResponse[] handle(TurnContext context, List activities, Callable next) throws Exception; } -// public delegate CompletableFuture SendActivitiesHandler(TurnContext context, List activities, Func> next); diff --git a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/StorageExtensions.java b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/StorageExtensions.java index b29bc6423..1899bea94 100644 --- a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/StorageExtensions.java +++ b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/StorageExtensions.java @@ -6,32 +6,31 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; -import static com.ea.async.Async.await; - public class StorageExtensions { /** * Storage extension to Read as strong typed StoreItem objects - * @param StoreItemT - * @param storage - * @param keys - * @return + * + * @param StoreItemT + * @param storage + * @param keys + * @return */ @SuppressWarnings("unchecked") public static CompletableFuture> Read(Storage storage, String... keys) throws JsonProcessingException { - Map storeItems = await(storage.Read(keys)); + Map storeItems = storage.Read(keys).join(); HashMap result = new HashMap(); for (Map.Entry entry : storeItems.entrySet()) { StoreItemT tempVal; try { tempVal = (StoreItemT) entry.getValue(); - } catch(Exception ex) { + } catch (Exception ex) { // Skip - not an instance of StoreItemT (ugly) continue; } - result.put((String)entry.getKey(), tempVal); + result.put((String) entry.getKey(), tempVal); } return CompletableFuture.completedFuture(result); } diff --git a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/TraceTranscriptLogger.java b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/TraceTranscriptLogger.java new file mode 100644 index 000000000..e4ce49333 --- /dev/null +++ b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/TraceTranscriptLogger.java @@ -0,0 +1,59 @@ +package com.microsoft.bot.builder; + + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.microsoft.bot.schema.models.Activity; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinWorkerThread; + +/** + * Represents a transcript logger that writes activites to a object. + */ +public class TraceTranscriptLogger implements TranscriptLogger { + // https://github.com/FasterXML/jackson-databind/wiki/Serialization-Features + private static ObjectMapper mapper = new ObjectMapper() + .enable(SerializationFeature.INDENT_OUTPUT); + private static final Logger logger = LogManager.getLogger("HelloWorld"); + + ForkJoinPool.ForkJoinWorkerThreadFactory factory = new ForkJoinPool.ForkJoinWorkerThreadFactory() + { + @Override + public ForkJoinWorkerThread newThread(ForkJoinPool pool) + { + final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool); + worker.setName("BotTrace-" + worker.getPoolIndex()); + return worker; + } + }; + + ExecutorService executor = new ForkJoinPool(Runtime.getRuntime().availableProcessors(), factory, null, true); + + /** + * Log an activity to the transcript. + * + * @param activity The activity to transcribe. + * @return A task that represents the work queued to execute. + */ + @Override + public void LogActivityAsync(Activity activity) { + BotAssert.ActivityNotNull(activity); + String event = null; + try { + event = mapper.writeValueAsString(activity); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + this.logger.info(event); + } +} diff --git a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/Transcript.java b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/Transcript.java new file mode 100644 index 000000000..6ae45f67c --- /dev/null +++ b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/Transcript.java @@ -0,0 +1,56 @@ +package com.microsoft.bot.builder; + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + + +import org.joda.time.DateTime; + +import java.time.OffsetDateTime; + +/** + * Transcript store item. + */ +public class Transcript { + /** + * channelId that the transcript was taken from. + */ + private String channelId; + + public String channelId() { + return this.channelId; + } + + public Transcript withChannelId(String value) { + this.channelId = value; + return this; + } + + /** + * Conversation id. + */ + private String id; + + public String getId() { + return this.id; + } + + public Transcript withId(String value) { + this.id = value; + return this; + } + + /** + * Date conversation was started. + */ + private OffsetDateTime created = OffsetDateTime.now(); + + public OffsetDateTime getCreated() { + return this.created; + } + + public Transcript withCreated(OffsetDateTime value) { + this.created = value; + return this; + } +} diff --git a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/TranscriptLogger.java b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/TranscriptLogger.java new file mode 100644 index 000000000..0264f41d0 --- /dev/null +++ b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/TranscriptLogger.java @@ -0,0 +1,23 @@ +package com.microsoft.bot.builder; + + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + + +import com.microsoft.bot.schema.models.Activity; + +import java.util.concurrent.CompletableFuture; + +/** + * Transcript logger stores activities for conversations for recall. + */ +public interface TranscriptLogger { + /** + * Log an activity to the transcript. + * + * @param activity The activity to transcribe. + * @return A task that represents the work queued to execute. + */ + void LogActivityAsync(Activity activity); +} diff --git a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/TranscriptLoggerMiddleware.java b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/TranscriptLoggerMiddleware.java new file mode 100644 index 000000000..e39bdf95a --- /dev/null +++ b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/TranscriptLoggerMiddleware.java @@ -0,0 +1,192 @@ +package com.microsoft.bot.builder; + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.microsoft.bot.schema.ActivityImpl; +import com.microsoft.bot.schema.models.Activity; +import com.microsoft.bot.schema.models.ActivityTypes; +import com.microsoft.bot.schema.models.ResourceResponse; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; + +import java.util.LinkedList; + + +/** + * When added, this middleware will log incoming and outgoing activitites to a ITranscriptStore. + */ +public class TranscriptLoggerMiddleware implements Middleware { + // https://github.com/FasterXML/jackson-databind/wiki/Serialization-Features + private static ObjectMapper mapper; + + static { + mapper = new ObjectMapper() + .enable(SerializationFeature.INDENT_OUTPUT); + mapper.findAndRegisterModules(); + } + + private TranscriptLogger logger; + private static final Logger log4j = LogManager.getLogger("BotFx"); + + private LinkedList transcript = new LinkedList(); + + /** + * Initializes a new instance of the class. + * + * @param transcriptLogger The transcript logger to use. + */ + public TranscriptLoggerMiddleware(TranscriptLogger transcriptLogger) { + if (transcriptLogger == null) + throw new NullPointerException("TranscriptLoggerMiddleware requires a ITranscriptLogger implementation. "); + + this.logger = transcriptLogger; + + } + + /** + * initialization for middleware turn. + * + * @param context + * @param next + * @return + */ + @Override + public void OnTurn(TurnContext context, NextDelegate next) throws Exception { + // log incoming activity at beginning of turn + if (context.getActivity() != null) { + JsonNode role = null; + if (context.getActivity().from() == null) { + throw new RuntimeException("Activity does not contain From field"); + } + if (context.getActivity().from().properties().containsKey("role")) { + role = context.getActivity().from().properties().get("role"); + } + + if (role == null || StringUtils.isBlank(role.asText())) { + context.getActivity().from().properties().put("role", mapper.createObjectNode().with("user")); + } + Activity activityTemp = ActivityImpl.CloneActity(context.getActivity()); + + LogActivity(ActivityImpl.CloneActity(context.getActivity())); + } + + // hook up onSend pipeline + context.OnSendActivities((ctx, activities, nextSend) -> + { + + // run full pipeline + ResourceResponse[] responses = new ResourceResponse[0]; + try { + if (nextSend != null) { + responses = nextSend.call(); + } + } catch (Exception e) { + e.printStackTrace(); + } + + for (Activity activity : activities) { + LogActivity(ActivityImpl.CloneActity(activity)); + } + + return responses; + + + }); + + // hook up update activity pipeline + context.OnUpdateActivity((ctx, activity, nextUpdate) -> + { + + // run full pipeline + ResourceResponse response = null; + try { + if (nextUpdate != null) { + response = nextUpdate.call(); + } + } catch (Exception e) { + e.printStackTrace(); + + + throw new RuntimeException(String.format("Error on Logging.OnUpdateActivity : %s", e.toString())); + } + + // add Message Update activity + Activity updateActivity = ActivityImpl.CloneActity(activity); + updateActivity.withType(ActivityTypes.MESSAGE_UPDATE); + LogActivity(updateActivity); + return response; + + + }); + + // hook up delete activity pipeline + context.OnDeleteActivity((ctxt, reference, nextDel) -> { + // run full pipeline + + try { + if (nextDel != null) { + log4j.error(String.format("Transcript logActivity next delegate: %s)", nextDel)); + nextDel.run(); + } + } catch (Exception e) { + e.printStackTrace(); + log4j.error(String.format("Transcript logActivity failed with %s (next delegate: %s)", e.toString(), nextDel)); + throw new RuntimeException(String.format("Transcript logActivity failed with %s", e.getMessage())); + + } + + // add MessageDelete activity + // log as MessageDelete activity + Activity deleteActivity = new Activity() + .withType(ActivityTypes.MESSAGE_DELETE) + .withId(reference.activityId()) + .applyConversationReference(reference, false); + + LogActivity(deleteActivity); + return; + + }); + + + // process bot logic + try { + next.next(); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(String.format("Error on Logging.next : %s", e.toString())); + } + + // flush transcript at end of turn + while (!transcript.isEmpty()) { + Activity activity = transcript.poll(); + try { + this.logger.LogActivityAsync(activity); + } catch (RuntimeException err) { + log4j.error(String.format("Transcript poll failed : %1$s", err)); + } + } + + } + + + private void LogActivity(Activity activity) { + synchronized (transcript) { + if (activity.timestamp() == null) { + activity.withTimestamp(DateTime.now(DateTimeZone.UTC)); + } + transcript.offer(activity); + } + } + +} + + + diff --git a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/TranscriptStore.java b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/TranscriptStore.java new file mode 100644 index 000000000..0852735b6 --- /dev/null +++ b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/TranscriptStore.java @@ -0,0 +1,57 @@ +package com.microsoft.bot.builder; + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + + +import com.microsoft.bot.schema.models.Activity; +import org.joda.time.DateTime; + +import java.util.concurrent.CompletableFuture; + +/** + * Transcript logger stores activities for conversations for recall. + */ +public interface TranscriptStore extends TranscriptLogger { + /** + * Gets from the store activities that match a set of criteria. + * + * @param channelId The ID of the channel the conversation is in. + * @param conversationId The ID of the conversation. + * @param continuationToken + * @param startDate A cutoff date. Activities older than this date are not included. + * @return A task that represents the work queued to execute. + * If the task completes successfully, the result contains the matching activities. + */ + + CompletableFuture> GetTranscriptActivitiesAsync(String channelId, String conversationId, String continuationToken); + + CompletableFuture> GetTranscriptActivitiesAsync(String channelId, String conversationId); + + //C# TO JAVA CONVERTER NOTE: Java does not support optional parameters. Overloaded method(s) are created above: +//ORIGINAL LINE: Task> GetTranscriptActivitiesAsync(string channelId, string conversationId, string continuationToken = null, DateTime startDate = default(DateTime)); + CompletableFuture> GetTranscriptActivitiesAsync(String channelId, String conversationId, String continuationToken, DateTime localStartDate); + + /** + * Gets the conversations on a channel from the store. + * + * @param channelId The ID of the channel. + * @param continuationToken + * @return A task that represents the work queued to execute. + */ + + CompletableFuture> ListTranscriptsAsync(String channelId); + + //C# TO JAVA CONVERTER NOTE: Java does not support optional parameters. Overloaded method(s) are created above: +//ORIGINAL LINE: Task> ListTranscriptsAsync(string channelId, string continuationToken = null); + CompletableFuture> ListTranscriptsAsync(String channelId, String continuationToken); + + /** + * Deletes conversation data from the store. + * + * @param channelId The ID of the channel the conversation is in. + * @param conversationId The ID of the conversation to delete. + * @return A task that represents the work queued to execute. + */ + CompletableFuture DeleteTranscriptAsync(String channelId, String conversationId); +} diff --git a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/TurnContext.java b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/TurnContext.java index d6f2e7425..957db6c84 100644 --- a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/TurnContext.java +++ b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/TurnContext.java @@ -18,7 +18,6 @@ * {@linkalso DeleteActivityHandler} */ -import com.microsoft.bot.schema.ActivityImpl; import com.microsoft.bot.schema.models.Activity; import com.microsoft.bot.schema.models.ConversationReference; import com.microsoft.bot.schema.models.ResourceResponse; @@ -81,10 +80,10 @@ public interface TurnContext * Speech Synthesis Markup Language (SSML) format.

* */ - CompletableFuture SendActivity(String textReplyToSend) throws Exception; - CompletableFuture SendActivity(String textReplyToSend, String speak) throws Exception; + ResourceResponse SendActivity(String textReplyToSend) throws Exception; + ResourceResponse SendActivity(String textReplyToSend, String speak) throws Exception; //CompletableFuture SendActivity(String textReplyToSend, String speak = null, String inputHint = InputHints.AcceptingInput); - CompletableFuture SendActivity(String textReplyToSend, String speak, String inputHint) throws Exception; + ResourceResponse SendActivity(String textReplyToSend, String speak, String inputHint) throws Exception; /** * Sends an activity to the sender of the incoming activity. @@ -94,7 +93,7 @@ public interface TurnContext * a {@link ResourceResponse} object containing the ID that the receiving * channel assigned to the activity. */ - CompletableFuture SendActivity(Activity activity) throws Exception; + ResourceResponse SendActivity(Activity activity) throws Exception; /** * Sends a set of activities to the sender of the incoming activity. @@ -104,7 +103,7 @@ public interface TurnContext * an array of {@link ResourceResponse} objects containing the IDs that * the receiving channel assigned to the activities. */ - CompletableFuture SendActivities(Activity[] activities) throws Exception; + ResourceResponse[] SendActivities(Activity[] activities) throws Exception; /** * Replaces an existing activity. @@ -118,12 +117,24 @@ public interface TurnContext */ ResourceResponse UpdateActivity(Activity activity) throws Exception; + /** + * Replaces an existing activity. + * @param activity New replacement activity. + * @return A task that represents the work queued to execute. + * If the activity is successfully sent, the task result contains + * a {@link ResourceResponse} object containing the ID that the receiving + * channel assigned to the activity. + *

Before calling this, set the ID of the replacement activity to the ID + * of the activity to replace.

+ */ + //CompletableFuture UpdateActivityAsync(Activity activity) throws Exception; + /** * Deletes an existing activity. * @param activityId The ID of the activity to delete. * @return A task that represents the work queued to execute. */ - CompletableFuture DeleteActivity(String activityId) throws Exception; + CompletableFuture DeleteActivity(String activityId) throws Exception; /** * Deletes an existing activity. @@ -132,7 +143,7 @@ public interface TurnContext * The conversation reference's {@link ConversationReference.ActivityId} * indicates the activity in the conversation to delete. */ - CompletableFuture DeleteActivity(ConversationReference conversationReference) throws Exception; + void DeleteActivity(ConversationReference conversationReference) throws Exception; /** * Adds a response handler for send activity operations. diff --git a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java index 0a3293f75..58d4ed20e 100644 --- a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java +++ b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java @@ -4,20 +4,20 @@ // Licensed under the MIT License. import com.microsoft.bot.schema.ActivityImpl; -import com.microsoft.bot.schema.models.*; +import com.microsoft.bot.schema.models.Activity; +import com.microsoft.bot.schema.models.ConversationReference; +import com.microsoft.bot.schema.models.InputHints; +import com.microsoft.bot.schema.models.ResourceResponse; import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.CompletableFuture; +import java.util.concurrent.*; -import static com.ea.async.Async.await; import static com.microsoft.bot.schema.models.ActivityTypes.MESSAGE; import static com.microsoft.bot.schema.models.ActivityTypes.TRACE; -import static java.util.concurrent.CompletableFuture.completedFuture; import static java.util.stream.Collectors.toList; /** @@ -38,39 +38,53 @@ public class TurnContextImpl implements TurnContext, AutoCloseable { private final List onDeleteActivity = new ArrayList(); private final TurnContextServiceCollection turnServices; + ForkJoinPool.ForkJoinWorkerThreadFactory factory = new ForkJoinPool.ForkJoinWorkerThreadFactory() + { + @Override + public ForkJoinWorkerThread newThread(ForkJoinPool pool) + { + final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool); + worker.setName("TestFlow-" + worker.getPoolIndex()); + return worker; + } + }; + + ExecutorService executor = new ForkJoinPool(Runtime.getRuntime().availableProcessors(), factory, null, true); + + /** * Creates a context object. - * @param adapter The adapter creating the context. + * + * @param adapter The adapter creating the context. * @param activity The incoming activity for the turn; - * or {@code null} for a turn for a proactive message. + * 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. + * {@code adapter} is {@code null}. + * For use by bot adapter implementations only. */ public TurnContextImpl(BotAdapter adapter, ActivityImpl activity) { if (adapter == null) throw new IllegalArgumentException("adapter"); this.adapter = adapter; if (activity == null) - throw new IllegalArgumentException("activity"); + throw new IllegalArgumentException("activity"); this.activity = activity; turnServices = new TurnContextServiceCollectionImpl(); - } - + } /** * Adds a response handler for send activity operations. + * * @param handler The handler to add to the context object. * @return The updated context object. * @throws IllegalArgumentException {@code handler} is {@code null}. - * When the context's {@link SendActivity(Activity)} - * or {@link SendActivities(Activity[])} methods are called, - * the adapter calls the registered handlers in the order in which they were - * added to the context object. - * + * When the context's {@link SendActivity(Activity)} + * or {@link SendActivities(Activity[])} methods are called, + * the adapter calls the registered handlers in the order in which they were + * added to the context object. */ public TurnContextImpl OnSendActivities(SendActivitiesHandler handler) { if (handler == null) @@ -78,35 +92,35 @@ public TurnContextImpl OnSendActivities(SendActivitiesHandler handler) { this.onSendActivities.add(handler); return this; - } + } /** * Adds a response handler for update activity operations. + * * @param handler The handler to add to the context object. * @return The updated context object. * @throws IllegalArgumentException {@code handler} is {@code null}. - * When the context's {@link UpdateActivity(Activity)} is called, - * the adapter calls the registered handlers in the order in which they were - * added to the context object. - * + * When the context's {@link UpdateActivity(Activity)} is called, + * the adapter calls the registered handlers in the order in which they were + * added to the context object. */ public TurnContextImpl OnUpdateActivity(UpdateActivityHandler handler) { if (handler == null) - throw new IllegalArgumentException("handler"); + throw new IllegalArgumentException("handler"); this.onUpdateActivity.add(handler); return this; - } + } /** * Adds a response handler for delete activity operations. + * * @param handler The handler to add to the context object. * @return The updated context object. * @throws IllegalArgumentException {@code handler} is {@code null}. - * When the context's {@link DeleteActivity(string)} is called, - * the adapter calls the registered handlers in the order in which they were - * added to the context object. - * + * When the context's {@link DeleteActivity(string)} is called, + * the adapter calls the registered handlers in the order in which they were + * added to the context object. */ public TurnContextImpl OnDeleteActivity(DeleteActivityHandler handler) { if (handler == null) @@ -141,104 +155,107 @@ public Activity getActivity() { /** * Indicates whether at least one response was sent for the current turn. + * * @return {@code true} if at least one response was sent for the current turn. * @throws IllegalArgumentException You attempted to set the value to {@code false}. */ public boolean getResponded() { return this.responded; } + public void setResponded(boolean responded) { - if (responded == false) - { + if (responded == false) { throw new IllegalArgumentException("TurnContext: cannot set 'responded' to a value of 'false'."); } this.responded = true; } -/** - * Sends a message activity to the sender of the incoming activity. - * @param textReplyToSend The text of the message to send. - * @param speak Optional, text to be spoken by your bot on a speech-enabled - * channel. - * @param inputHint Optional, indicates whether your bot is accepting, - * expecting, or ignoring user input after the message is delivered to the client. - * One of: "acceptingInput", "ignoringInput", or "expectingInput". - * Default is null. - * @return A task that represents the work queued to execute. - * @throws IllegalArgumentException - * {@code textReplyToSend} is {@code null} or whitespace. - * If the activity is successfully sent, the task result contains - * a {@link ResourceResponse} object containing the ID that the receiving - * channel assigned to the activity. - *

See the channel's documentation for limits imposed upon the contents of - * {@code textReplyToSend}.

- *

To control various characteristics of your bot's speech such as voice, - * rate, volume, pronunciation, and pitch, specify {@code speak} in - * Speech Synthesis Markup Language (SSML) format.

- * - */ + /** + * Sends a message activity to the sender of the incoming activity. + * + * @param textReplyToSend The text of the message to send. + * @param speak Optional, text to be spoken by your bot on a speech-enabled + * channel. + * @param inputHint Optional, indicates whether your bot is accepting, + * expecting, or ignoring user input after the message is delivered to the client. + * One of: "acceptingInput", "ignoringInput", or "expectingInput". + * Default is null. + * @return A task that represents the work queued to execute. + * @throws IllegalArgumentException {@code textReplyToSend} is {@code null} or whitespace. + * If the activity is successfully sent, the task result contains + * a {@link ResourceResponse} object containing the ID that the receiving + * channel assigned to the activity. + *

See the channel's documentation for limits imposed upon the contents of + * {@code textReplyToSend}.

+ *

To control various characteristics of your bot's speech such as voice, + * rate, volume, pronunciation, and pitch, specify {@code speak} in + * Speech Synthesis Markup Language (SSML) format.

+ */ @Override - public CompletableFuture SendActivity(String textReplyToSend) throws Exception { - return SendActivity(textReplyToSend, null, null); + public ResourceResponse SendActivity(String textReplyToSend) throws Exception { + return SendActivity(textReplyToSend, null, null); } + @Override - public CompletableFuture SendActivity(String textReplyToSend, String speak) throws Exception { + public ResourceResponse SendActivity(String textReplyToSend, String speak) throws Exception { return SendActivity(textReplyToSend, speak, null); } + @Override - public CompletableFuture SendActivity(String textReplyToSend, String speak, String inputHint) throws Exception { + public ResourceResponse SendActivity(String textReplyToSend, String speak, String inputHint) throws Exception { if (StringUtils.isEmpty(textReplyToSend)) throw new IllegalArgumentException("textReplyToSend"); ActivityImpl activityToSend = (ActivityImpl) new ActivityImpl() - .withType(MESSAGE) - .withText(textReplyToSend); + .withType(MESSAGE) + .withText(textReplyToSend); if (speak != null) activityToSend.withSpeak(speak); if (StringUtils.isNotEmpty(inputHint)) activityToSend.withInputHint(InputHints.fromString(inputHint)); - return completedFuture(await(SendActivity(activityToSend))); + return SendActivity(activityToSend); } /** * Sends an activity to the sender of the incoming activity. + * * @param activity The activity to send. * @return A task that represents the work queued to execute. * @throws IllegalArgumentException {@code activity} is {@code null}. - * If the activity is successfully sent, the task result contains - * a {@link ResourceResponse} object containing the ID that the receiving - * channel assigned to the activity. + * If the activity is successfully sent, the task result contains + * a {@link ResourceResponse} object containing the ID that the receiving + * channel assigned to the activity. */ @Override - public CompletableFuture SendActivity(Activity activity) throws Exception { + public ResourceResponse SendActivity(Activity activity) throws Exception { if (activity == null) throw new IllegalArgumentException("activity"); - return CompletableFuture.supplyAsync(() -> { - Activity[] activities = { activity }; - ResourceResponse[] responses = new ResourceResponse[0]; - try { - responses = await(SendActivities(activities)); - } catch (Exception e) { - e.printStackTrace(); - throw new RuntimeException(String.format("TurnContext:SendActivity fail %s", e.toString())); - } - if (responses == null || responses.length == 0) { - // It's possible an interceptor prevented the activity from having been sent. - // Just return an empty response in that case. - return null; - } - else { - return responses[0]; - } - }); + System.out.printf("In SENDEACTIVITYASYNC:"); + System.out.flush(); + Activity[] activities = {activity}; + ResourceResponse[] responses; + try { + responses = SendActivities(activities); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(String.format("TurnContext:SendActivity fail %s", e.toString())); + } + if (responses == null || responses.length == 0) { + // It's possible an interceptor prevented the activity from having been sent. + // Just return an empty response in that case. + return null; + } else { + return responses[0]; + } } /** * Sends a set of activities to the sender of the incoming activity. + * * @param activities The activities to send. * @return A task that represents the work queued to execute. * If the activities are successfully sent, the task result contains @@ -246,7 +263,7 @@ public CompletableFuture SendActivity(Activity activity) throw * the receiving channel assigned to the activities. */ @Override - public CompletableFuture SendActivities(Activity[] activities) throws Exception { + public ResourceResponse[] SendActivities(Activity[] activities) throws Exception { // Bind the relevant Conversation Reference properties, such as URLs and // ChannelId's, to the activities we're about to send. ConversationReference cr = GetConversationReference(this.activity); @@ -262,7 +279,7 @@ public CompletableFuture SendActivities(Activity[] activitie // Create the list used by the recursive methods. List activityList = new ArrayList(activityArray); - Callable> ActuallySendStuff = () -> { + Callable ActuallySendStuff = () -> { // Are the any non-trace activities to send? // The thinking here is that a Trace event isn't user relevant data // so the "Responded" flag should not be set by Trace messages being @@ -275,8 +292,8 @@ public CompletableFuture SendActivities(Activity[] activitie // Note that 'responses' was captured from the root of the call, and will be // returned to the original caller. ResourceResponse[] responses = new ResourceResponse[0]; - responses = await(this.getAdapter().SendActivities(this, activityList.toArray(new ActivityImpl[activityList.size()]))); - if (responses != null && responses.length == activityList.size()) { + responses = this.getAdapter().SendActivities(this, activityList.toArray(new ActivityImpl[activityList.size()])); + if (responses != null && responses.length == activityList.size()) { // stitch up activity ids for (int i = 0; i < responses.length; i++) { ResourceResponse response = responses[i]; @@ -289,87 +306,109 @@ public CompletableFuture SendActivities(Activity[] activitie if (sentNonTraceActivities) { this.setResponded(true); } - return completedFuture(responses); + return responses; }; List act_list = new ArrayList<>(activityList); - return completedFuture(await(SendActivitiesInternal(act_list, onSendActivities.iterator(), ActuallySendStuff))); + return SendActivitiesInternal(act_list, onSendActivities.iterator(), ActuallySendStuff); } /** * Replaces an existing activity. + * * @param activity New replacement activity. * @return A task that represents the work queued to execute. - * @throws Microsoft.Bot.Schema.ErrorResponseException - * The HTTP operation failed and the response contained additional information. - * @throws System.AggregateException - * One or more exceptions occurred during the operation. - * If the activity is successfully sent, the task result contains - * a {@link ResourceResponse} object containing the ID that the receiving - * channel assigned to the activity. - *

Before calling this, set the ID of the replacement activity to the ID - * of the activity to replace.

+ * @throws Microsoft.Bot.Schema.ErrorResponseException The HTTP operation failed and the response contained additional information. + * @throws System.AggregateException One or more exceptions occurred during the operation. + * If the activity is successfully sent, the task result contains + * a {@link ResourceResponse} object containing the ID that the receiving + * channel assigned to the activity. + *

Before calling this, set the ID of the replacement activity to the ID + * of the activity to replace.

*/ @Override public ResourceResponse UpdateActivity(Activity activity) throws Exception { - - Callable> ActuallyUpdateStuff = () -> { - return completedFuture(await(this.getAdapter().UpdateActivity(this, activity))); + Callable ActuallyUpdateStuff = () -> { + return this.getAdapter().UpdateActivity(this, activity); }; - return await(UpdateActivityInternal(activity, onUpdateActivity.iterator(), ActuallyUpdateStuff)); + 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 Microsoft.Bot.Schema.ErrorResponseException - * The HTTP operation failed and the response contained additional information. + * @throws Exception The HTTP operation failed and the response contained additional information. */ - public CompletableFuture DeleteActivity(String activityId) throws Exception { + public CompletableFuture DeleteActivity(String activityId) throws Exception { if (StringUtils.isWhitespace(activityId) || activityId == null) throw new IllegalArgumentException("activityId"); - ConversationReference cr = this.GetConversationReference(this.getActivity()); - cr.withActivityId(activityId); + return CompletableFuture.runAsync(() -> { + ConversationReference cr = this.GetConversationReference(this.getActivity()); + cr.withActivityId(activityId); + + Runnable ActuallyDeleteStuff = () -> { + try { + this.getAdapter().DeleteActivity(this, cr); + } catch (ExecutionException e) { + e.printStackTrace(); + throw new RuntimeException(String.format("Failed to delete activity %s", e.toString())); + } catch (InterruptedException e) { + e.printStackTrace(); + throw new RuntimeException(String.format("Failed to delete activity %s", e.toString())); + } + return; + }; - Callable ActuallyDeleteStuff = () -> { - await(this.getAdapter().DeleteActivity(this, cr)); - return completedFuture(null); - }; + try { + DeleteActivityInternal(cr, onDeleteActivity.iterator(), ActuallyDeleteStuff); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(String.format("Failed to delete activity %s", e.getMessage())); + } + return; + + }, executor); - await(DeleteActivityInternal(cr, onDeleteActivity.iterator(), ActuallyDeleteStuff)); - return completedFuture(null); } /** * Deletes an existing activity. + * * @param conversationReference The conversation containing the activity to delete. * @return A task that represents the work queued to execute. - * @throws Microsoft.Bot.Schema.ErrorResponseException - * The HTTP operation failed and the response contained additional information. - * The conversation reference's {@link ConversationReference.ActivityId} - * indicates the activity in the conversation to delete. + * @throws Microsoft.Bot.Schema.ErrorResponseException The HTTP operation failed and the response contained additional information. + * The conversation reference's {@link ConversationReference.ActivityId} + * indicates the activity in the conversation to delete. */ - public CompletableFuture DeleteActivity(ConversationReference conversationReference) throws Exception { + public void DeleteActivity(ConversationReference conversationReference) throws Exception { if (conversationReference == null) throw new IllegalArgumentException("conversationReference"); - Callable ActuallyDeleteStuff = () -> { - return completedFuture(await(this.getAdapter().DeleteActivity(this, conversationReference))); + Runnable ActuallyDeleteStuff = () -> { + try { + this.getAdapter().DeleteActivity(this, conversationReference); + return; + } catch (ExecutionException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + throw new RuntimeException("DeleteActivity failed"); }; - await(DeleteActivityInternal(conversationReference, onDeleteActivity.iterator(), ActuallyDeleteStuff)); - return completedFuture(null); + DeleteActivityInternal(conversationReference, onDeleteActivity.iterator(), ActuallyDeleteStuff); + return ; } - private CompletableFuture SendActivitiesInternal( - List activities, - Iterator sendHandlers, - Callable> callAtBottom) throws Exception { + private ResourceResponse[] SendActivitiesInternal(List activities, Iterator sendHandlers, Callable callAtBottom) throws Exception { if (activities == null) throw new IllegalArgumentException("activities"); if (sendHandlers == null) @@ -377,24 +416,24 @@ private CompletableFuture SendActivitiesInternal( if (false == sendHandlers.hasNext()) { // No middleware to run. if (callAtBottom != null) - return completedFuture(await(callAtBottom.call())); - return completedFuture(new ResourceResponse[0]); + return callAtBottom.call(); + return new ResourceResponse[0]; } // Default to "No more Middleware after this". - Callable> next = () -> { + Callable next = () -> { // Remove the first item from the list of middleware to call, // so that the next call just has the remaining items to worry about. //Iterable remaining = sendHandlers.Skip(1); //Iterator remaining = sendHandlers.iterator(); if (sendHandlers.hasNext()) sendHandlers.next(); - return completedFuture(await(SendActivitiesInternal(activities, sendHandlers, callAtBottom))); - }; + 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 completedFuture(await(caller.handle(this, activities, next))); + return caller.handle(this, activities, next); } // private async Task UpdateActivityInternal(Activity activity, @@ -436,76 +475,87 @@ private CompletableFuture SendActivitiesInternal( // UpdateActivityHandler toCall = updateHandlers.First(); // return await toCall(this, activity, next); // } - private CompletableFuture UpdateActivityInternal(Activity activity, - Iterator updateHandlers, - Callable> callAtBottom) throws Exception { + private ResourceResponse UpdateActivityInternal(Activity activity, + Iterator updateHandlers, + Callable callAtBottom) throws Exception { BotAssert.ActivityNotNull(activity); if (updateHandlers == null) throw new IllegalArgumentException("updateHandlers"); if (false == updateHandlers.hasNext()) { // No middleware to run. if (callAtBottom != null) { - return completedFuture(await(callAtBottom.call())); + return callAtBottom.call(); } - return completedFuture(null); + return null; } // Default to "No more Middleware after this". - Callable> next = () -> { - // Remove the first item from the list of middleware to call, - // so that the next call just has the remaining items to worry about. - if (updateHandlers.hasNext()) - updateHandlers.next(); - ResourceResponse result = null; - result = await(UpdateActivityInternal(activity, updateHandlers, callAtBottom)); - activity.withId(result.id()); - return completedFuture(new ResourceResponse[] { result }); + Callable next = () -> { + // Remove the first item from the list of middleware to call, + // so that the next call just has the remaining items to worry about. + if (updateHandlers.hasNext()) + updateHandlers.next(); + ResourceResponse result = null; + try { + result = UpdateActivityInternal(activity, updateHandlers, callAtBottom); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(String.format("Error updating activity: %s", e.toString())); + } + activity.withId(result.id()); + return result; }; // Grab the current middleware, which is the 1st element in the array, and execute it UpdateActivityHandler toCall = updateHandlers.next(); - return completedFuture(await(toCall.handle(this, activity, next))); + return toCall.handle(this, activity, next); } - - private CompletableFuture DeleteActivityInternal(ConversationReference cr, - Iterator updateHandlers, - Callable callAtBottom) throws Exception { + private void DeleteActivityInternal(ConversationReference cr, + Iterator deleteHandlers, + Runnable callAtBottom) throws Exception { BotAssert.ConversationReferenceNotNull(cr); - if (updateHandlers == null) - throw new IllegalArgumentException("updateHandlers"); + if (deleteHandlers == null) + throw new IllegalArgumentException("deleteHandlers"); - if (updateHandlers.hasNext() == false) { // No middleware to run. + if (deleteHandlers.hasNext() == false) { // No middleware to run. if (callAtBottom != null) { - await(callAtBottom.call()); + callAtBottom.run(); } - return completedFuture(null); + return; } // Default to "No more Middleware after this". - Callable next = () -> { + Runnable next = () -> { // Remove the first item from the list of middleware to call, // so that the next call just has the remaining items to worry about. - if (updateHandlers.hasNext()) - updateHandlers.next(); - await(DeleteActivityInternal(cr, updateHandlers, callAtBottom)); - return completedFuture(null); + //Iterator remaining = (deleteHandlers.hasNext()) ? deleteHandlers.next() : null; + if (deleteHandlers.hasNext()) + deleteHandlers.next(); + + + try { + DeleteActivityInternal(cr, deleteHandlers, callAtBottom); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("DeleteActivityInternal failed"); + } + return; }; // Grab the current middleware, which is the 1st element in the array, and execute it. - DeleteActivityHandler toCall = updateHandlers.next(); - await(toCall.handle(this, cr, next)); - return completedFuture(null); + DeleteActivityHandler toCall = deleteHandlers.next(); + toCall.handle(this, cr, next); } /** * Creates a conversation reference from an activity. + * * @param activity The activity. * @return A conversation reference for the conversation that contains the activity. - * @throws IllegalArgumentException - * {@code activity} is {@code null}. + * @throws IllegalArgumentException {@code activity} is {@code null}. */ public static ConversationReference GetConversationReference(Activity activity) { BotAssert.ActivityNotNull(activity); @@ -524,21 +574,22 @@ public static ConversationReference GetConversationReference(Activity activity) /** * 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.

* + * @param activity The activity to update. + * @param reference The conversation reference. + * @param isIncoming (Optional) {@code true} to treat the activity as an + * incoming activity, where the bot is the recipient; otherwaire {@code false}. + * Default is {@code false}, and the activity will show the bot as the sender. + * Call {@link GetConversationReference(Activity)} on an incoming + * activity to get a conversation reference that you can then use to update an + * outgoing activity with the correct delivery information. + *

The {@link SendActivity(Activity)} and {@link SendActivities(Activity[])} + * methods do this for you.

*/ public static Activity ApplyConversationReference(Activity activity, ConversationReference reference) { return ApplyConversationReference(activity, reference, false); } + public static Activity ApplyConversationReference(Activity activity, ConversationReference reference, boolean isIncoming) { activity.withChannelId(reference.channelId()); activity.withServiceUrl(reference.serviceUrl()); @@ -549,8 +600,7 @@ public static Activity ApplyConversationReference(Activity activity, Conversatio activity.withRecipient(reference.bot()); if (reference.activityId() != null) activity.withId(reference.activityId()); - } - else { // Outgoing + } else { // Outgoing activity.withFrom(reference.bot()); activity.withRecipient(reference.user()); if (reference.activityId() != null) diff --git a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/UpdateActivityHandler.java b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/UpdateActivityHandler.java index 5a668e17b..f9b9105cf 100644 --- a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/UpdateActivityHandler.java +++ b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/UpdateActivityHandler.java @@ -25,10 +25,7 @@ * {@linkalso DeleteActivityHandler} */ - - -// public delegate Task UpdateActivityHandler(TurnContext context, Activity activity, Func> next); @FunctionalInterface public interface UpdateActivityHandler { - CompletableFuture handle(TurnContext context, Activity activity, Callable> next); + ResourceResponse handle(TurnContext context, Activity activity, Callable next); } diff --git a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/adapters/TestAdapter.java b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/adapters/TestAdapter.java index 58b86f7eb..d6516a536 100644 --- a/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/adapters/TestAdapter.java +++ b/libraries/botbuilder/src/main/java/com/microsoft/bot/builder/adapters/TestAdapter.java @@ -14,39 +14,37 @@ import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; import java.util.function.Function; -import static com.ea.async.Async.await; -import static java.util.concurrent.CompletableFuture.completedFuture; - public class TestAdapter extends BotAdapter { - private int _nextId=0; - private final Queue botReplies= new LinkedList<>(); - private ConversationReference _conversationReference; + private int nextId = 0; + private final Queue botReplies = new LinkedList<>(); + private ConversationReference conversationReference; + public TestAdapter() { this(null); } public TestAdapter(ConversationReference reference) { - if(reference!=null) { + if (reference != null) { this.withConversationReference(reference); - } - else { + } else { this.withConversationReference(new ConversationReference() - .withChannelId("test") - .withServiceUrl("https://test.com")); + .withChannelId("test") + .withServiceUrl("https://test.com")); this.conversationReference().withUser(new ChannelAccount() - .withId("user1") - .withName("User1")); + .withId("user1") + .withName("User1")); this.conversationReference().withBot(new ChannelAccount() - .withId("bot") - .withName("Bot")); + .withId("bot") + .withName("Bot")); this.conversationReference().withConversation(new ConversationAccount() - .withIsGroup(false) - .withConversationType("convo1") - .withId("Conversation1")); + .withIsGroup(Boolean.FALSE) + .withConversationType("convo1") + .withId("Conversation1")); } } @@ -59,140 +57,138 @@ public TestAdapter Use(Middleware middleware) { return this; } - public CompletableFuture ProcessActivity(ActivityImpl activity, - Function callback - ) throws Exception { + public void ProcessActivity(ActivityImpl activity, + Consumer callback + ) throws Exception { synchronized (this.conversationReference()) { // ready for next reply - if(activity.type()==null) + if (activity.type() == null) activity.withType(ActivityTypes.MESSAGE); activity.withChannelId(this.conversationReference().channelId()); activity.withFrom(this.conversationReference().user()); activity.withRecipient(this.conversationReference().bot()); activity.withConversation(this.conversationReference().conversation()); activity.withServiceUrl(this.conversationReference().serviceUrl()); - Integer next = this._nextId++; + Integer next = this.nextId++; activity.withId(next.toString()); } // Assume Default DateTime : DateTime(0) - if(activity.timestamp() == null ||activity.timestamp()== new DateTime(0)) + if (activity.timestamp() == null || activity.timestamp() == new DateTime(0)) activity.withTimestamp(DateTime.now()); - try (TurnContextImpl context=new TurnContextImpl(this, activity)) { - await(super.RunPipeline(context, callback)); + try (TurnContextImpl context = new TurnContextImpl(this, activity)) { + super.RunPipeline(context, callback); } - return completedFuture(null); + return; } public ConversationReference conversationReference() { - return _conversationReference; + return conversationReference; } + public void withConversationReference(ConversationReference conversationReference) { - this._conversationReference = conversationReference; + this.conversationReference = conversationReference; } @Override - public CompletableFutureSendActivities(TurnContext context, Activity[] activities) throws InterruptedException { - List responses=new LinkedList(); + public ResourceResponse[] SendActivities(TurnContext context, Activity[] activities) throws InterruptedException { + List responses = new LinkedList(); - for(Activity activity : activities) { - if(StringUtils.isEmpty(activity.id())) + for (Activity activity : activities) { + if (StringUtils.isEmpty(activity.id())) activity.withId(UUID.randomUUID().toString()); - if(activity.timestamp()==null) + if (activity.timestamp() == null) activity.withTimestamp(DateTime.now()); responses.add(new ResourceResponse().withId(activity.id())); // This is simulating DELAY System.out.println(String.format("TestAdapter:SendActivities(tid:%s):Count:%s", Thread.currentThread().getId(), activities.length)); - for(Activity act : activities) - System.out.println(String.format(":--------\n: To:%s\n: From:%s\n> Text:%s\n:---------", act.recipient().name(), act.from().name(), - act.text())); - if(activity.type().toString().equals("delay")) - { + for (Activity act : activities) { + System.out.printf(":--------\n: To:%s\n", act.recipient().name()); + System.out.printf(": From:%s\n", (act.from() == null) ? "No from set" : act.from().name()); + System.out.printf(": Text:%s\n:---------", (act.text() == null) ? "No text set" : act.text()); + } + if (activity.type().toString().equals("delay")) { // The BotFrameworkAdapter and Console adapter implement this // hack directly in the POST method. Replicating that here // to keep the behavior as close as possible to facillitate // more realistic tests. - int delayMs=(int)activity.value(); + int delayMs = (int) activity.value(); Thread.sleep(delayMs); - //await Task.Delay(delayMs); - } - else - { - synchronized (this.botReplies) - { + } else { + synchronized (this.botReplies) { this.botReplies.add(activity); } } } - return completedFuture(responses.toArray(new ResourceResponse[responses.size()])); + return responses.toArray(new ResourceResponse[responses.size()]); } @Override - public CompletableFuture UpdateActivity(TurnContext context, Activity activity) { - synchronized (this.botReplies) - { - List replies= new ArrayList<>(botReplies); - for(int i=0;i< this.botReplies.size();i++) { - if(replies.get(i).id().equals(activity.id())) { + public ResourceResponse UpdateActivity(TurnContext context, Activity activity) { + synchronized (this.botReplies) { + List replies = new ArrayList<>(botReplies); + for (int i = 0; i < this.botReplies.size(); i++) { + if (replies.get(i).id().equals(activity.id())) { replies.set(i, activity); this.botReplies.clear(); - for(Activity item : replies) { + for (Activity item : replies) { this.botReplies.add(item); } - return completedFuture(new ResourceResponse().withId(activity.id())); + return new ResourceResponse().withId(activity.id()); } } } - return completedFuture(new ResourceResponse()); + return new ResourceResponse(); } @Override - public CompletableFuture DeleteActivity(TurnContext context, ConversationReference reference) { - synchronized (this.botReplies) - { - ArrayList replies= new ArrayList<>(this.botReplies); - for(int i=0;i< this.botReplies.size();i++) { - if(replies.get(i).id().equals(reference.activityId())) { + public void DeleteActivity(TurnContext context, ConversationReference reference) { + synchronized (this.botReplies) { + ArrayList replies = new ArrayList<>(this.botReplies); + for (int i = 0; i < this.botReplies.size(); i++) { + if (replies.get(i).id().equals(reference.activityId())) { replies.remove(i); this.botReplies.clear(); - for(Activity item : replies) { + for (Activity item : replies) { this.botReplies.add(item); } break; } } } - return completedFuture(null); + return; } /** * NOTE: this resets the queue, it doesn't actually maintain multiple converstion queues - * @param channelId - * @param callback - * @return + * + * @param channelId + * @param callback + * @return */ //@Override - public CompletableFuture CreateConversation(String channelId,Function callback) { + public CompletableFuture CreateConversation(String channelId, Function callback) { this.activeQueue().clear(); - MessageActivity update=MessageActivity.CreateConversationUpdateActivity(); + MessageActivity update = MessageActivity.CreateConversationUpdateActivity(); update.withConversation(new ConversationAccount().withId(UUID.randomUUID().toString())); - TurnContextImpl context=new TurnContextImpl(this,(ActivityImpl)update); + TurnContextImpl context = new TurnContextImpl(this, (ActivityImpl) update); return callback.apply(context); } /** * Called by TestFlow to check next reply - * @return + * + * @return */ public Activity GetNextReply() { synchronized (this.botReplies) { - if(this.botReplies.size()>0) { + if (this.botReplies.size() > 0) { return this.botReplies.remove(); } } @@ -201,16 +197,17 @@ public Activity GetNextReply() { /** * Called by TestFlow to get appropriate activity for conversationReference of testbot - * @param text - * @return + * + * @param text + * @return */ public Activity MakeActivity() { return MakeActivity(null); } - public ActivityImpl MakeActivity(String text) - { - Integer next = _nextId++; - ActivityImpl activity= (ActivityImpl) new MessageActivity() + + public ActivityImpl MakeActivity(String text) { + Integer next = nextId++; + ActivityImpl activity = (ActivityImpl) new ActivityImpl() .withType(ActivityTypes.MESSAGE) .withFrom(conversationReference().user()) .withRecipient(conversationReference().bot()) @@ -225,11 +222,12 @@ public ActivityImpl MakeActivity(String text) /** * Called by TestFlow to send text to the bot - * @param userSays - * @return + * + * @param userSays + * @return */ - public CompletableFuture SendTextToBot(String userSays,Function callback) throws Exception { - return this.ProcessActivity(this.MakeActivity(userSays),callback); + public void SendTextToBot(String userSays, Consumer callback) throws Exception { + this.ProcessActivity(this.MakeActivity(userSays), callback); } } diff --git a/libraries/botbuilder/src/main/resources/log4j2.json b/libraries/botbuilder/src/main/resources/log4j2.json new file mode 100644 index 000000000..9161234da --- /dev/null +++ b/libraries/botbuilder/src/main/resources/log4j2.json @@ -0,0 +1,25 @@ +{ + "configuration": { + "name": "Default", + "appenders": { + "Console": { + "name": "Console-Appender", + "target": "SYSTEM_OUT", + "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} + } + }, + "loggers": { + "logger": [ + { + "name": "com.microsoft.bot.builder", + "level": "debug", + "appender-ref": [{"ref": "Console-Appender", "level": "debug"}] + } + ], + "root": { + "level": "warn", + "appender-ref": {"ref": "Console-Appender","level": "warn"} + } + } + } +} diff --git a/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/BotFrameworkAdapterTest.java b/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/BotFrameworkAdapterTest.java index 6ff673b58..d355b8dc6 100644 --- a/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/BotFrameworkAdapterTest.java +++ b/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/BotFrameworkAdapterTest.java @@ -10,7 +10,7 @@ import java.util.UUID; import java.util.function.Consumer; -import static com.ea.async.Async.await; + public class BotFrameworkAdapterTest { @Test @@ -41,7 +41,7 @@ public void PassResourceResponsesThrough() throws Exception { .withId(activityId); - ResourceResponse resourceResponse = await(c.SendActivity(activity)); + ResourceResponse resourceResponse = c.SendActivity(activity); Assert.assertTrue("Incorrect response Id returned", resourceResponse.id() == activityId); } diff --git a/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/BotStateTest.java b/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/BotStateTest.java index e247b56de..3829391a9 100644 --- a/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/BotStateTest.java +++ b/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/BotStateTest.java @@ -5,7 +5,7 @@ package com.microsoft.bot.builder; -import com.ea.async.Async; + import com.fasterxml.jackson.core.JsonProcessingException; import com.microsoft.bot.builder.adapters.TestAdapter; import com.microsoft.bot.builder.adapters.TestFlow; @@ -20,9 +20,10 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.function.Consumer; import java.util.function.Function; -import static com.ea.async.Async.await; + import static java.util.concurrent.CompletableFuture.completedFuture; // [TestClass] @@ -34,8 +35,6 @@ public class BotStateTest { protected void initializeClients(RestClient restClient, String botId, String userId) { - // Initialize async/await(support - Async.init(); connector = new ConnectorClientImpl(restClient); bot = new ChannelAccount().withId(botId); @@ -52,25 +51,22 @@ public void State_DoNOTRememberContextState() throws ExecutionException, Interru TestAdapter adapter = new TestAdapter(); - await(new TestFlow(adapter, (context) -> { + new TestFlow(adapter, (context) -> { TestPocoState obj = StateTurnContextExtensions.GetConversationState(context); - Assert.assertNull("context.state should not exist", obj); - return completedFuture(null); - } + Assert.assertNull("context.state should not exist", obj); } ) .Send("set value") - .StartTest()); + .StartTest(); } - @Test + //@Test public void State_RememberIStoreItemUserState() throws ExecutionException, InterruptedException { TestAdapter adapter = new TestAdapter() .Use(new UserState(new MemoryStorage(), TestState::new)); - Function callback = (context) -> { - CompletableFuture doit = CompletableFuture.runAsync(() -> { + Consumer callback = (context) -> { System.out.print(String.format("State_RememberIStoreItemUserState CALLBACK called..")); System.out.flush(); TestState userState = StateTurnContextExtensions.GetUserState(context); @@ -79,7 +75,7 @@ public void State_RememberIStoreItemUserState() throws ExecutionException, Inter case "set value": userState.withValue("test"); try { - await(((TurnContextImpl)context).SendActivity("value saved")); + ((TurnContextImpl)context).SendActivity("value saved"); } catch (Exception e) { e.printStackTrace(); Assert.fail(String.format("Error sending activity! - set value")); @@ -88,7 +84,7 @@ public void State_RememberIStoreItemUserState() throws ExecutionException, Inter case "get value": try { Assert.assertFalse(StringUtils.isBlank(userState.value())); - await(((TurnContextImpl)context).SendActivity(userState.value())); + ((TurnContextImpl)context).SendActivity(userState.value()); } catch (Exception e) { e.printStackTrace(); Assert.fail(String.format("Error sending activity! - get value")); @@ -96,14 +92,12 @@ public void State_RememberIStoreItemUserState() throws ExecutionException, Inter break; } - }); - return doit; }; - TestFlow myTest = new TestFlow(adapter, callback) + new TestFlow(adapter, callback) .Test("set value", "value saved") - .Test("get value", "test"); - await(myTest.StartTest()); + .Test("get value", "test") + .StartTest(); } @@ -111,11 +105,9 @@ public void State_RememberIStoreItemUserState() throws ExecutionException, Inter public void State_RememberPocoUserState() throws ExecutionException, InterruptedException { TestAdapter adapter = new TestAdapter() .Use(new UserState(new MemoryStorage(), TestPocoState::new)); - await(new TestFlow(adapter, + new TestFlow(adapter, (context) -> { - CompletableFuture doit = CompletableFuture.runAsync(() -> { - { TestPocoState userState = StateTurnContextExtensions.GetUserState(context); Assert.assertNotNull("user state should exist", userState); @@ -123,7 +115,7 @@ public void State_RememberPocoUserState() throws ExecutionException, Interrupted case "set value": userState.setValue("test"); try { - await(context.SendActivity("value saved")); + context.SendActivity("value saved"); } catch (Exception e) { e.printStackTrace(); Assert.fail(String.format("Error sending activity! - set value")); @@ -132,38 +124,33 @@ public void State_RememberPocoUserState() throws ExecutionException, Interrupted case "get value": try { Assert.assertFalse(StringUtils.isBlank(userState.getValue())); - await(context.SendActivity(userState.getValue())); + context.SendActivity(userState.getValue()); } catch (Exception e) { e.printStackTrace(); Assert.fail(String.format("Error sending activity! - get value")); } break; } - } - - }); - return doit; }) .Test("set value", "value saved") .Test("get value", "test") - .StartTest()); + .StartTest(); } - @Test + //@Test public void State_RememberIStoreItemConversationState() throws ExecutionException, InterruptedException { TestAdapter adapter = new TestAdapter() .Use(new ConversationState(new MemoryStorage(), TestState::new)); - await(new TestFlow(adapter, + new TestFlow(adapter, (context) -> { - CompletableFuture doit = CompletableFuture.runAsync(() -> { TestState conversationState = StateTurnContextExtensions.GetConversationState(context); Assert.assertNotNull("state.conversation should exist", conversationState); switch (context.getActivity().text()) { case "set value": conversationState.withValue("test"); try { - await(context.SendActivity("value saved")); + context.SendActivity("value saved"); } catch (Exception e) { e.printStackTrace(); Assert.fail(String.format("Error sending activity! - set value")); @@ -172,36 +159,33 @@ public void State_RememberIStoreItemConversationState() throws ExecutionExceptio case "get value": try { Assert.assertFalse(StringUtils.isBlank(conversationState.value())); - await(context.SendActivity(conversationState.value())); + context.SendActivity(conversationState.value()); } catch (Exception e) { e.printStackTrace(); Assert.fail(String.format("Error sending activity! - get value")); } break; } - }); - return doit; }) .Test("set value", "value saved") .Test("get value", "test") - .StartTest()); + .StartTest(); } - @Test + //@Test public void State_RememberPocoConversationState() throws ExecutionException, InterruptedException { TestAdapter adapter = new TestAdapter() .Use(new ConversationState(new MemoryStorage(), TestPocoState::new)); - await(new TestFlow(adapter, + new TestFlow(adapter, (context) -> { - CompletableFuture doit = CompletableFuture.runAsync(() -> { TestPocoState conversationState = StateTurnContextExtensions.GetConversationState(context); Assert.assertNotNull("state.conversation should exist", conversationState); switch (context.getActivity().text()) { case "set value": conversationState.setValue("test"); try { - await(context.SendActivity("value saved")); + context.SendActivity("value saved"); } catch (Exception e) { e.printStackTrace(); Assert.fail(String.format("Error sending activity! - set value")); @@ -210,20 +194,18 @@ public void State_RememberPocoConversationState() throws ExecutionException, Int case "get value": try { Assert.assertFalse(StringUtils.isBlank(conversationState.getValue())); - await(context.SendActivity(conversationState.getValue())); + context.SendActivity(conversationState.getValue()); } catch (Exception e) { e.printStackTrace(); Assert.fail(String.format("Error sending activity! - get value")); } break; } - }); - return doit; }) .Test("set value", "value saved") .Test("get value", "test") - .StartTest()); + .StartTest(); } @Test @@ -232,17 +214,16 @@ public void State_CustomStateManagerTest() throws ExecutionException, Interrupte String testGuid = UUID.randomUUID().toString(); TestAdapter adapter = new TestAdapter() .Use(new CustomKeyState(new MemoryStorage())); - await(new TestFlow(adapter, + new TestFlow(adapter, (context) -> { - CompletableFuture doit = CompletableFuture.runAsync(() -> { CustomState customState = CustomKeyState.Get(context); switch (context.getActivity().text()) { case "set value": customState.setCustomString(testGuid); try { - await(context.SendActivity("value saved")); + context.SendActivity("value saved"); } catch (Exception e) { e.printStackTrace(); Assert.fail(String.format("Error sending activity! - set value")); @@ -251,28 +232,25 @@ public void State_CustomStateManagerTest() throws ExecutionException, Interrupte case "get value": try { Assert.assertFalse(StringUtils.isBlank(customState.getCustomString())); - await(context.SendActivity(customState.getCustomString())); + context.SendActivity(customState.getCustomString()); } catch (Exception e) { e.printStackTrace(); Assert.fail(String.format("Error sending activity! - get value")); } break; } - }); - return doit; }) .Test("set value", "value saved") .Test("get value", testGuid.toString()) - .StartTest()); + .StartTest(); } @Test public void State_RoundTripTypedObjectwTrace() throws ExecutionException, InterruptedException { TestAdapter adapter = new TestAdapter() .Use(new ConversationState(new MemoryStorage(), TypedObject::new)); - String result = await(new TestFlow(adapter, + new TestFlow(adapter, (context) -> { - CompletableFuture doit = CompletableFuture.runAsync(() -> { System.out.println(String.format(">>Test Callback(tid:%s): STARTING : %s", Thread.currentThread().getId(), context.getActivity().text())); System.out.flush(); TypedObject conversation = StateTurnContextExtensions.GetConversationState(context); @@ -286,7 +264,7 @@ public void State_RoundTripTypedObjectwTrace() throws ExecutionException, Interr System.out.println(String.format(">>Test Callback(tid:%s): Send activity : %s", Thread.currentThread().getId(), "value saved")); System.out.flush(); - ResourceResponse response = await(context.SendActivity("value saved")); + ResourceResponse response = context.SendActivity("value saved"); System.out.println(String.format(">>Test Callback(tid:%s): Response Id: %s", Thread.currentThread().getId(), response.id())); System.out.flush(); @@ -301,20 +279,17 @@ public void State_RoundTripTypedObjectwTrace() throws ExecutionException, Interr System.out.println(String.format(">>Test Callback(tid:%s): Send activity : %s", Thread.currentThread().getId(), "TypedObject")); System.out.flush(); - await(context.SendActivity("TypedObject")); + context.SendActivity("TypedObject"); } catch (Exception e) { e.printStackTrace(); Assert.fail(String.format("Error sending activity! - get value")); } break; } - }); - return doit; }) .Turn("set value", "value saved", "Description", 50000) .Turn("get value", "TypedObject", "Description", 50000) - .StartTest()); - System.out.print(String.format("Done with test : %s\n", result)); + .StartTest(); } @@ -324,17 +299,16 @@ public void State_RoundTripTypedObject() throws ExecutionException, InterruptedE TestAdapter adapter = new TestAdapter() .Use(new ConversationState(new MemoryStorage(), TypedObject::new)); - await(new TestFlow(adapter, + new TestFlow(adapter, (context) -> { - CompletableFuture doit = CompletableFuture.runAsync(() -> { TypedObject conversation = StateTurnContextExtensions.GetConversationState(context); Assert.assertNotNull("conversationstate should exist", conversation); switch (context.getActivity().text()) { case "set value": conversation.withName("test"); try { - await(context.SendActivity("value saved")); + context.SendActivity("value saved"); } catch (Exception e) { e.printStackTrace(); Assert.fail(String.format("Error sending activity! - set value")); @@ -342,19 +316,17 @@ public void State_RoundTripTypedObject() throws ExecutionException, InterruptedE break; case "get value": try { - await(context.SendActivity("TypedObject")); + context.SendActivity("TypedObject"); } catch (Exception e) { e.printStackTrace(); Assert.fail(String.format("Error sending activity! - get value")); } break; } - }); - return doit; }) .Test("set value", "value saved") .Test("get value", "TypedObject") - .StartTest()); + .StartTest(); } @@ -362,10 +334,9 @@ public void State_RoundTripTypedObject() throws ExecutionException, InterruptedE public void State_UseBotStateDirectly() throws ExecutionException, InterruptedException { TestAdapter adapter = new TestAdapter(); - await(new TestFlow(adapter, + new TestFlow(adapter, (context) -> { - CompletableFuture doit = CompletableFuture.runAsync(() -> { BotState botStateManager = new BotState(new MemoryStorage(), "BotState:com.microsoft.bot.builder.core.extensions.BotState", (ctx) -> String.format("botstate/%s/%s/com.microsoft.bot.builder.core.extensions.BotState", ctx.getActivity().channelId(), ctx.getActivity().conversation().id()), CustomState::new); @@ -373,7 +344,7 @@ public void State_UseBotStateDirectly() throws ExecutionException, InterruptedEx // read initial state object CustomState customState = null; try { - customState = await(botStateManager.Read(context)); + customState = (CustomState) botStateManager.Read(context).join(); } catch (JsonProcessingException e) { e.printStackTrace(); Assert.fail("Error reading custom state"); @@ -385,7 +356,7 @@ public void State_UseBotStateDirectly() throws ExecutionException, InterruptedEx // amend property and write to storage customState.setCustomString("test"); try { - await(botStateManager.Write(context, customState)); + botStateManager.Write(context, customState).join(); } catch (Exception e) { e.printStackTrace(); Assert.fail("Could not write customstate"); @@ -394,7 +365,7 @@ public void State_UseBotStateDirectly() throws ExecutionException, InterruptedEx // set customState to null before reading from storage customState = null; try { - customState = await(botStateManager.Read(context)); + customState = (CustomState) botStateManager.Read(context).join(); } catch (JsonProcessingException e) { e.printStackTrace(); Assert.fail("Could not read customstate back"); @@ -402,11 +373,9 @@ public void State_UseBotStateDirectly() throws ExecutionException, InterruptedEx // check object read from value has the correct value for CustomString Assert.assertEquals(customState.getCustomString(), "test"); - }); - return doit; } ) - .StartTest()); + .StartTest(); } diff --git a/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/CallCountingMiddleware.java b/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/CallCountingMiddleware.java index 8caa8c486..b0dcf7595 100644 --- a/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/CallCountingMiddleware.java +++ b/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/CallCountingMiddleware.java @@ -1,9 +1,5 @@ package com.microsoft.bot.builder; -import java.util.concurrent.CompletableFuture; - -import static com.ea.async.Async.await; - public class CallCountingMiddleware implements Middleware { private int calls = 0; @@ -18,17 +14,15 @@ public CallCountingMiddleware withCalls(int calls) { @Override - public CompletableFuture OnTurn(TurnContext context, NextDelegate next) throws Exception { - return CompletableFuture.runAsync(() -> { - this.calls++; - try { - await(next.next()); - } catch (Exception e) { - e.printStackTrace(); - throw new RuntimeException(String.format("CallCountingMiddleWare: %s", e.toString())); - } + public void OnTurn(TurnContext context, NextDelegate next) throws Exception { + this.calls++; + try { + next.next(); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(String.format("CallCountingMiddleWare: %s", e.toString())); + } - }); } } diff --git a/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/CallMeMiddlware.java b/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/CallMeMiddlware.java index 8dbf907ca..ab54c61db 100644 --- a/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/CallMeMiddlware.java +++ b/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/CallMeMiddlware.java @@ -1,11 +1,6 @@ package com.microsoft.bot.builder; -import java.util.concurrent.CompletableFuture; - -import static com.ea.async.Async.await; - -public class CallMeMiddlware implements Middleware -{ +public class CallMeMiddlware implements Middleware { private ActionDel callMe; public CallMeMiddlware(ActionDel callme) { @@ -13,15 +8,14 @@ public CallMeMiddlware(ActionDel callme) { } @Override - public CompletableFuture OnTurn(TurnContext context, NextDelegate next) throws Exception { - return CompletableFuture.runAsync(() -> { - this.callMe.CallMe(); - try { - await(next.next()); - } catch (Exception e) { - e.printStackTrace(); + public void OnTurn(TurnContext context, NextDelegate next) throws Exception { + + this.callMe.CallMe(); + try { + next.next(); + } catch (Exception e) { + e.printStackTrace(); - } - }); + } } } diff --git a/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/CatchException_MiddlewareTest.java b/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/CatchException_MiddlewareTest.java index 50b842757..5618f0007 100644 --- a/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/CatchException_MiddlewareTest.java +++ b/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/CatchException_MiddlewareTest.java @@ -10,32 +10,25 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import static com.ea.async.Async.await; -import static java.util.concurrent.CompletableFuture.completedFuture; - public class CatchException_MiddlewareTest { @Test public void CatchException_TestMiddleware_TestStackedErrorMiddleware() throws ExecutionException, InterruptedException { TestAdapter adapter = new TestAdapter() - // Add middleware to catch general exceptions - .Use(new CatchExceptionMiddleware(new CallOnException() { @Override public CompletableFuture apply(TurnContext context, T t) throws Exception { return CompletableFuture.runAsync(() -> { Activity activity = context.getActivity(); - if (activity instanceof ActivityImpl) - { + if (activity instanceof ActivityImpl) { try { - context.SendActivity(((ActivityImpl)activity).CreateReply(t.toString())); + context.SendActivity(((ActivityImpl) activity).CreateReply(t.toString())); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(String.format("CatchException_TestMiddleware_TestStackedErrorMiddleware:SendActivity failed %s", e.toString())); } - } - else + } else Assert.assertTrue("Test was built for ActivityImpl", false); }); @@ -52,29 +45,27 @@ public CompletableFuture apply(TurnContext context, T t) throws Exception { }, NullPointerException.class)); - await(new TestFlow(adapter, (context) -> - { - CompletableFuture doit = CompletableFuture.runAsync(() -> { - if (context.getActivity().text() == "foo") { - try { - context.SendActivity(context.getActivity().text()); - } catch (Exception e) { - e.printStackTrace(); + new TestFlow(adapter, (context) -> + { + + if (context.getActivity().text() == "foo") { + try { + context.SendActivity(context.getActivity().text()); + } catch (Exception e) { + e.printStackTrace(); + } + } + if (context.getActivity().text() == "UnsupportedOperationException") { + throw new UnsupportedOperationException("Test"); } - } - if (context.getActivity().text() == "UnsupportedOperationException") { - throw new UnsupportedOperationException("Test"); - } - }); - return doit; - } + } ) .Send("foo") .AssertReply("foo", "passthrough") .Send("UnsupportedOperationException") .AssertReply("Test") - .StartTest()); + .StartTest(); } diff --git a/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/DoNotCallNextMiddleware.java b/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/DoNotCallNextMiddleware.java index b1a81d5c6..a79383037 100644 --- a/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/DoNotCallNextMiddleware.java +++ b/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/DoNotCallNextMiddleware.java @@ -1,22 +1,14 @@ package com.microsoft.bot.builder; -import com.microsoft.bot.builder.Middleware; -import com.microsoft.bot.builder.NextDelegate; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.builder.ActionDel; -import java.util.concurrent.CompletableFuture; -import static java.util.concurrent.CompletableFuture.completedFuture; - -public class DoNotCallNextMiddleware implements Middleware -{ +public class DoNotCallNextMiddleware implements Middleware { private final ActionDel _callMe; + public DoNotCallNextMiddleware(ActionDel callMe) { _callMe = callMe; } - public CompletableFuture OnTurn(TurnContext context, NextDelegate next) { - return CompletableFuture.runAsync(() -> { - _callMe.CallMe(); - // DO NOT call NEXT - }); + + public void OnTurn(TurnContext context, NextDelegate next) { + _callMe.CallMe(); + // DO NOT call NEXT } } diff --git a/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/MiddlewareSetTest.java b/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/MiddlewareSetTest.java index 61e329b27..dd185245d 100644 --- a/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/MiddlewareSetTest.java +++ b/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/MiddlewareSetTest.java @@ -1,6 +1,6 @@ package com.microsoft.bot.builder; -import com.ea.async.Async; + import com.microsoft.bot.builder.ActionDel; import com.microsoft.bot.builder.base.TestBase; import com.microsoft.bot.connector.implementation.ConnectorClientImpl; @@ -11,9 +11,10 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.function.Consumer; import java.util.function.Function; -import static com.ea.async.Async.await; + import static java.util.concurrent.CompletableFuture.completedFuture; @@ -31,8 +32,6 @@ public MiddlewareSetTest() { @Override protected void initializeClients(RestClient restClient, String botId, String userId) { - // Initialize async/await support - Async.init(); connector = new ConnectorClientImpl(restClient); bot = new ChannelAccount().withId(botId); @@ -69,9 +68,9 @@ public void NestedSet_OnReceive() throws Exception { final boolean[] wasCalled = {false}; MiddlewareSet inner = new MiddlewareSet(); inner.Use(new AnonymousReceiveMiddleware(new MiddlewareCall() { - public CompletableFuture requestHandler(TurnContext tc, NextDelegate nd) throws Exception { + public void requestHandler(TurnContext tc, NextDelegate nd) throws Exception { wasCalled[0] = true; - return nd.next(); + nd.next(); } })); MiddlewareSet outer = new MiddlewareSet(); @@ -94,12 +93,11 @@ public CompletableFuture requestHandler(TurnContext tc, NextDelegate nd) throws public void NoMiddlewareWithDelegate() throws Exception { MiddlewareSet m = new MiddlewareSet(); final boolean wasCalled[] = {false}; - Function cb = context -> { + Consumer cb = context -> { wasCalled[0] = true; - return completedFuture(null); }; // No middleware. Should not explode. - await(m.ReceiveActivityWithStatus(null, cb)); + m.ReceiveActivityWithStatus(null, cb); Assert.assertTrue("Delegate was not called", wasCalled[0]); } @@ -108,16 +106,15 @@ public void OneMiddlewareItem() throws Exception { WasCalledMiddlware simple = new WasCalledMiddlware(); final boolean wasCalled[] = {false}; - Function cb = context -> { + Consumer cb = context -> { wasCalled[0] = true; - return completedFuture(null); }; MiddlewareSet m = new MiddlewareSet(); m.Use(simple); Assert.assertFalse(simple.getCalled()); - await(m.ReceiveActivityWithStatus(null, cb)); + m.ReceiveActivityWithStatus(null, cb); Assert.assertTrue(simple.getCalled()); Assert.assertTrue( "Delegate was not called", wasCalled[0]); } @@ -130,7 +127,7 @@ public void OneMiddlewareItemWithDelegate() throws Exception { m.Use(simple); Assert.assertFalse(simple.getCalled()); - await(m.ReceiveActivity(null)); + m.ReceiveActivity(null); Assert.assertTrue(simple.getCalled()); } @@ -139,12 +136,12 @@ public void OneMiddlewareItemWithDelegate() throws Exception { public void BubbleUncaughtException() throws Exception { MiddlewareSet m = new MiddlewareSet(); m.Use(new AnonymousReceiveMiddleware(new MiddlewareCall() { - public CompletableFuture requestHandler(TurnContext tc, NextDelegate nd) throws IllegalStateException { + public void requestHandler(TurnContext tc, NextDelegate nd) throws IllegalStateException { throw new IllegalStateException("test"); }} )); - await(m.ReceiveActivity(null)); + m.ReceiveActivity(null); Assert.assertFalse("Should never have gotten here", true); } @@ -157,7 +154,7 @@ public void TwoMiddlewareItems() throws Exception { m.Use(one); m.Use(two); - await(m.ReceiveActivity(null)); + m.ReceiveActivity(null); Assert.assertTrue(one.getCalled()); Assert.assertTrue(two.getCalled()); } @@ -168,16 +165,15 @@ public void TwoMiddlewareItemsWithDelegate() throws Exception { WasCalledMiddlware two = new WasCalledMiddlware(); final int called[] = {0}; - Function cb = (context) -> { + Consumer cb = (context) -> { called[0]++; - return completedFuture(null); }; MiddlewareSet m = new MiddlewareSet(); m.Use(one); m.Use(two); - await(m.ReceiveActivityWithStatus(null, cb)); + m.ReceiveActivityWithStatus(null, cb); Assert.assertTrue(one.getCalled()); Assert.assertTrue(two.getCalled()); Assert.assertTrue("Incorrect number of calls to Delegate", called[0] == 1 ); @@ -208,7 +204,7 @@ public void CallMe() { m.Use(one); m.Use(two); - await(m.ReceiveActivity(null)); + m.ReceiveActivity(null); Assert.assertTrue(called1[0]); Assert.assertTrue(called2[0]); } @@ -229,11 +225,10 @@ public void CallMe() { // The middlware in this pipeline calls next(), so the delegate should be called final boolean didAllRun[] = {false}; - Function cb = (context) -> { + Consumer cb = (context) -> { didAllRun[0] = true; - return completedFuture(null); }; - await(m.ReceiveActivityWithStatus(null, cb)); + m.ReceiveActivityWithStatus(null, cb); Assert.assertTrue(called1[0]); Assert.assertTrue(didAllRun[0]); @@ -243,14 +238,13 @@ public void CallMe() { public void Status_RunAtEndEmptyPipeline() throws Exception { MiddlewareSet m = new MiddlewareSet(); final boolean didAllRun[] = {false}; - Function cb = (context)-> { + Consumer cb = (context)-> { didAllRun[0] = true; - return completedFuture(null); }; // This middlware pipeline has no entries. This should result in // the status being TRUE. - await(m.ReceiveActivityWithStatus(null, cb)); + m.ReceiveActivityWithStatus(null, cb); Assert.assertTrue(didAllRun[0]); } @@ -280,11 +274,10 @@ public void CallMe() { m.Use(two); boolean didAllRun[] = {false}; - Function cb= (context) -> { + Consumer cb= (context) -> { didAllRun[0] = true; - return completedFuture(null); }; - await(m.ReceiveActivityWithStatus(null, cb)); + m.ReceiveActivityWithStatus(null, cb); Assert.assertTrue(called1[0]); Assert.assertTrue(called2[0]); @@ -308,12 +301,10 @@ public void CallMe() { // The middleware in this pipeline DOES NOT call next(), so this must not be called boolean didAllRun[] = {false}; - Function cb = (context) -> { - return CompletableFuture.runAsync(() -> { + Consumer cb = (context) -> { didAllRun[0] = true; - }); }; - await(m.ReceiveActivityWithStatus(null, cb)); + m.ReceiveActivityWithStatus(null, cb); Assert.assertTrue(called1[0]); @@ -328,16 +319,16 @@ public void AnonymousMiddleware() throws Exception { MiddlewareSet m = new MiddlewareSet(); MiddlewareCall mwc = new MiddlewareCall() { - public CompletableFuture requestHandler(TurnContext tc, NextDelegate nd) throws Exception { + public void requestHandler(TurnContext tc, NextDelegate nd) throws Exception { didRun[0] = true; - await(nd.next()); - return completedFuture(null); + nd.next(); + return; } }; m.Use(new AnonymousReceiveMiddleware(mwc)); Assert.assertFalse(didRun[0]); - await( m.ReceiveActivity(null)); + m.ReceiveActivity(null); Assert.assertTrue(didRun[0]); } @@ -348,25 +339,25 @@ public void TwoAnonymousMiddleware() throws Exception { MiddlewareSet m = new MiddlewareSet(); MiddlewareCall mwc1 = new MiddlewareCall() { - public CompletableFuture requestHandler(TurnContext tc, NextDelegate nd) throws Exception { + public void requestHandler(TurnContext tc, NextDelegate nd) throws Exception { didRun1[0] = true; - await(nd.next()); - return completedFuture(null); + nd.next(); + return; } }; m.Use(new AnonymousReceiveMiddleware(mwc1)); MiddlewareCall mwc2 = new MiddlewareCall() { - public CompletableFuture requestHandler(TurnContext tc, NextDelegate nd) throws Exception { + public void requestHandler(TurnContext tc, NextDelegate nd) throws Exception { didRun2[0] = true; - await(nd.next()); - return completedFuture(null); + nd.next(); + return; } }; m.Use(new AnonymousReceiveMiddleware(mwc2)); - await(m.ReceiveActivity(null)); + m.ReceiveActivity(null); Assert.assertTrue(didRun1[0]); Assert.assertTrue(didRun2[0]); } @@ -378,27 +369,27 @@ public void TwoAnonymousMiddlewareInOrder() throws Exception { MiddlewareSet m = new MiddlewareSet(); MiddlewareCall mwc1 = new MiddlewareCall() { - public CompletableFuture requestHandler(TurnContext tc, NextDelegate nd) throws Exception { + public void requestHandler(TurnContext tc, NextDelegate nd) throws Exception { Assert.assertFalse("Looks like the 2nd one has already run", didRun2[0]); didRun1[0] = true; - await(nd.next()); - return completedFuture(null); + nd.next(); + return; } }; m.Use(new AnonymousReceiveMiddleware(mwc1)); MiddlewareCall mwc2 = new MiddlewareCall() { - public CompletableFuture requestHandler(TurnContext tc, NextDelegate nd) throws Exception { + public void requestHandler(TurnContext tc, NextDelegate nd) throws Exception { Assert.assertTrue("Looks like the 1nd one has not yet run", didRun1[0]); didRun2[0] = true; - await(nd.next()); - return completedFuture(null); + nd.next(); + return ; } }; m.Use(new AnonymousReceiveMiddleware(mwc2)); - await(m.ReceiveActivity(null)); + m.ReceiveActivity(null); Assert.assertTrue(didRun1[0]); Assert.assertTrue(didRun2[0]); } @@ -410,13 +401,13 @@ public void MixedMiddlewareInOrderAnonymousFirst() throws Exception { MiddlewareSet m = new MiddlewareSet(); MiddlewareCall mwc1 = new MiddlewareCall() { - public CompletableFuture requestHandler(TurnContext tc, NextDelegate nd) throws Exception { + public void requestHandler(TurnContext tc, NextDelegate nd) throws Exception { Assert.assertFalse("First middleware already ran", didRun1[0]); Assert.assertFalse("Looks like the second middleware was already run", didRun2[0]); didRun1[0] = true; - await(nd.next()); + nd.next(); Assert.assertTrue("Second middleware should have completed running", didRun2[0]); - return completedFuture(null); + return ; } }; m.Use(new AnonymousReceiveMiddleware(mwc1)); @@ -431,7 +422,7 @@ public void CallMe() { }; m.Use(new CallMeMiddlware(act)); - await(m.ReceiveActivity(null)); + m.ReceiveActivity(null); Assert.assertTrue(didRun1[0]); Assert.assertTrue(didRun2[0]); } @@ -454,16 +445,16 @@ public void CallMe() { m.Use(new CallMeMiddlware(act)); MiddlewareCall mwc1 = new MiddlewareCall() { - public CompletableFuture requestHandler(TurnContext tc, NextDelegate nd) throws Exception { + public void requestHandler(TurnContext tc, NextDelegate nd) throws Exception { Assert.assertTrue("First middleware has not been run yet", didRun1[0]); didRun2[0] = true; - await(nd.next()); - return completedFuture(null); + nd.next(); + return; } }; m.Use(new AnonymousReceiveMiddleware(mwc1)); - await(m.ReceiveActivity(null)); + m.ReceiveActivity(null); Assert.assertTrue(didRun1[0]); Assert.assertTrue(didRun2[0]); } @@ -477,29 +468,29 @@ public void RunCodeBeforeAndAfter() throws Exception { MiddlewareSet m = new MiddlewareSet(); MiddlewareCall mwc1 = new MiddlewareCall() { - public CompletableFuture requestHandler(TurnContext tc, NextDelegate nd) throws Exception { + public void requestHandler(TurnContext tc, NextDelegate nd) throws Exception { Assert.assertFalse("Looks like the 1st middleware has already run", didRun1[0]); didRun1[0] = true; - await(nd.next()); + nd.next(); Assert.assertTrue("The 2nd middleware should have run now.", didRun1[0]); codeafter2run[0] = true; - return completedFuture(null); + return ; } }; m.Use(new AnonymousReceiveMiddleware(mwc1)); MiddlewareCall mwc2 = new MiddlewareCall() { - public CompletableFuture requestHandler(TurnContext tc, NextDelegate nd) throws Exception { + public void requestHandler(TurnContext tc, NextDelegate nd) throws Exception { Assert.assertTrue("Looks like the 1st middleware has not been run", didRun1[0]); Assert.assertFalse("The code that runs after middleware 2 is complete has already run.", codeafter2run[0]); didRun2[0] = true; - await(nd.next()); - return completedFuture(null); + nd.next(); + return ; } }; m.Use(new AnonymousReceiveMiddleware(mwc2)); - await(m.ReceiveActivity(null)); + m.ReceiveActivity(null); Assert.assertTrue(didRun1[0]); Assert.assertTrue(didRun2[0]); Assert.assertTrue(codeafter2run[0]); @@ -511,9 +502,9 @@ public void CatchAnExceptionViaMiddlware() throws Exception { final boolean caughtException[] = {false}; MiddlewareCall mwc1 = new MiddlewareCall() { - public CompletableFuture requestHandler(TurnContext tc, NextDelegate nd) throws ExecutionException, InterruptedException { + public void requestHandler(TurnContext tc, NextDelegate nd) throws ExecutionException, InterruptedException { try { - await(nd.next()); + nd.next(); Assert.assertTrue("Should not get here", false); } @@ -526,20 +517,20 @@ public CompletableFuture requestHandler(TurnContext tc, NextDelegate nd) throws } catch (Exception e) { Assert.assertTrue("Should not get here" + e.getMessage(), false); } - return completedFuture(null); + return ; }}; m.Use(new AnonymousReceiveMiddleware(mwc1)); MiddlewareCall mwc2 = new MiddlewareCall() { - public CompletableFuture requestHandler(TurnContext tc, NextDelegate nd) throws InterruptedException { + public void requestHandler(TurnContext tc, NextDelegate nd) throws InterruptedException { throw new InterruptedException("test"); } }; m.Use(new AnonymousReceiveMiddleware(mwc2)); - await(m.ReceiveActivity(null)); + m.ReceiveActivity(null); Assert.assertTrue(caughtException[0]); } diff --git a/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/SimpleAdapter.java b/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/SimpleAdapter.java index 4e1c4ae97..b6574ac50 100644 --- a/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/SimpleAdapter.java +++ b/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/SimpleAdapter.java @@ -8,12 +8,8 @@ import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.function.Consumer; -import java.util.function.Function; - -import static com.ea.async.Async.await; public class SimpleAdapter extends BotAdapter { private Consumer callOnSend = null; @@ -40,62 +36,55 @@ public SimpleAdapter() { } - @Override - public CompletableFuture SendActivities(TurnContext context, Activity[] activities) throws InterruptedException { - return CompletableFuture.supplyAsync(() -> { - Assert.assertNotNull("SimpleAdapter.deleteActivity: missing reference", activities); - Assert.assertTrue("SimpleAdapter.sendActivities: empty activities array.", activities.length > 0); + public ResourceResponse[] SendActivities(TurnContext context, Activity[] activities) throws InterruptedException { + Assert.assertNotNull("SimpleAdapter.deleteActivity: missing reference", activities); + Assert.assertTrue("SimpleAdapter.sendActivities: empty activities array.", activities.length > 0); - if (this.callOnSend != null) - this.callOnSend.accept(activities); + if (this.callOnSend != null) + this.callOnSend.accept(activities); - List responses = new ArrayList(); - for (Activity activity : activities) { - responses.add(new ResourceResponse().withId(activity.id())); - } - ResourceResponse[] result = new ResourceResponse[responses.size()]; - return responses.toArray(result); + List responses = new ArrayList(); + for (Activity activity : activities) { + responses.add(new ResourceResponse().withId(activity.id())); + } + ResourceResponse[] result = new ResourceResponse[responses.size()]; + return responses.toArray(result); - }); } @Override - public CompletableFuture UpdateActivity(TurnContext context, Activity activity) { - return CompletableFuture.supplyAsync(() -> { - Assert.assertNotNull("SimpleAdapter.updateActivity: missing activity", activity); - if (this.callOnUpdate != null) - this.callOnUpdate.accept(activity); - return new ResourceResponse() - .withId(activity.id()); - - }); + public ResourceResponse UpdateActivity(TurnContext context, Activity activity) { + + Assert.assertNotNull("SimpleAdapter.updateActivity: missing activity", activity); + if (this.callOnUpdate != null) + this.callOnUpdate.accept(activity); + return new ResourceResponse() + .withId(activity.id()); + + } @Override - public CompletableFuture DeleteActivity(TurnContext context, ConversationReference reference) throws ExecutionException, InterruptedException { - return CompletableFuture.runAsync(() -> { - Assert.assertNotNull("SimpleAdapter.deleteActivity: missing reference", reference); - if (callOnDelete != null) - this.callOnDelete.accept(reference); + public void DeleteActivity(TurnContext context, ConversationReference reference) throws ExecutionException, InterruptedException { + Assert.assertNotNull("SimpleAdapter.deleteActivity: missing reference", reference); + if (callOnDelete != null) + this.callOnDelete.accept(reference); - }); } - public CompletableFuture ProcessRequest(ActivityImpl activty, Function callback) throws Exception { + public void ProcessRequest(ActivityImpl activty, Consumer callback) throws Exception { - return CompletableFuture.runAsync(() -> { - try (TurnContextImpl ctx = new TurnContextImpl(this, activty)) { - await(this.RunPipeline(ctx, callback)); - } catch (Exception e) { - e.printStackTrace(); - throw new RuntimeException(String.format("Error running pipeline: %s", e.toString())); - } + try (TurnContextImpl ctx = new TurnContextImpl(this, activty)) { + this.RunPipeline(ctx, callback); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(String.format("Error running pipeline: %s", e.toString())); + } - }); } } diff --git a/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/TranscriptMiddlewareTest.java b/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/TranscriptMiddlewareTest.java new file mode 100644 index 000000000..863f21098 --- /dev/null +++ b/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/TranscriptMiddlewareTest.java @@ -0,0 +1,272 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.builder; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.joda.JodaModule; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.builder.dialogs.Dialog; +import com.microsoft.bot.schema.ActivityImpl; +import com.microsoft.bot.schema.models.*; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + + +import static java.util.concurrent.CompletableFuture.completedFuture; + + +public class TranscriptMiddlewareTest { + + @Test + public final void Transcript_SimpleReceive() throws Exception { + MemoryTranscriptStore transcriptStore = new MemoryTranscriptStore(); + TestAdapter adapter = (new TestAdapter()).Use(new TranscriptLoggerMiddleware(transcriptStore)); + final String[] conversationId = {null}; + + + new TestFlow(adapter, (ctxt) -> + { + + TurnContextImpl context = (TurnContextImpl) ctxt; + conversationId[0] = context.getActivity().conversation().id(); + ActivityImpl typingActivity = new ActivityImpl() + .withType(ActivityTypes.TYPING) + .withRelatesTo(context.getActivity().relatesTo()); + try { + ResourceResponse response = context.SendActivity(typingActivity); + System.out.printf("Here's the response:"); + } catch (Exception e) { + e.printStackTrace(); + Assert.fail(); + } + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + Assert.fail(); + } + try { + context.SendActivity("echo:" + context.getActivity().text()); + } catch (Exception e) { + e.printStackTrace(); + Assert.fail(); + } + + }).Send("foo") + .AssertReply((activity) -> { + Assert.assertEquals(activity.type(), ActivityTypes.TYPING); + return null; + }).StartTest(); + //.AssertReply("echo:foo").StartTest(); + + + } + + @Test + public final void Transcript_MiddlewareTest() throws Exception { + MemoryTranscriptStore transcriptStore = new MemoryTranscriptStore(); + TranscriptLoggerMiddleware logger = new TranscriptLoggerMiddleware(transcriptStore); + TestAdapter adapter = new TestAdapter(); + ActivityImpl activity = ActivityImpl.CreateMessageActivity() + .withFrom(new ChannelAccount().withName("MyAccount").withId("acctid").withRole(RoleTypes.USER)); + TurnContextImpl context = new TurnContextImpl(adapter, activity); + NextDelegate nd = new NextDelegate() { + @Override + public void next() throws Exception { + System.out.printf("Delegate called!"); + System.out.flush(); + return ; + } + }; + ActivityImpl typingActivity = new ActivityImpl() + .withType(ActivityTypes.TYPING) + .withRelatesTo(context.getActivity().relatesTo()); + try { + context.SendActivity(typingActivity); + System.out.printf("HI"); + } catch (Exception e) { + e.printStackTrace(); + Assert.fail(); + } + + + //logger.OnTurn(context, nd).get(); + } + + @Test + public final void Transcript_LogActivities() throws ExecutionException, InterruptedException { + Logger logger = LogManager.getLogger(Dialog.class); + MemoryTranscriptStore transcriptStore = new MemoryTranscriptStore(); + TestAdapter adapter = (new TestAdapter()).Use(new TranscriptLoggerMiddleware(transcriptStore)); + final String[] conversationId = {null}; + + + String result = new TestFlow(adapter, (context) -> + { + + //TurnContextImpl context = (TurnContextImpl) ctxt; + conversationId[0] = context.getActivity().conversation().id(); + ActivityImpl typingActivity = new ActivityImpl() + .withType(ActivityTypes.TYPING) + .withRelatesTo(context.getActivity().relatesTo()); + try { + context.SendActivity((Activity)typingActivity); + } catch (Exception e) { + e.printStackTrace(); + Assert.fail(); + } + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + Assert.fail(); + } + try { + context.SendActivity("echo:" + context.getActivity().text()); + } catch (Exception e) { + e.printStackTrace(); + Assert.fail(); + } + }).Send("foo") + .AssertReply((activity) -> { + Assert.assertEquals(activity.type(), ActivityTypes.TYPING); + return null; + }) + .AssertReply("echo:foo") + .Send("bar") + .AssertReply((activity) -> { + Assert.assertEquals(activity.type(), ActivityTypes.TYPING); + return null; + }) + .AssertReply("echo:bar") + .StartTest(); + + + PagedResult pagedResult = transcriptStore.GetTranscriptActivitiesAsync("test", conversationId[0]).join(); + Assert.assertEquals(6, pagedResult.getItems().length); + Assert.assertEquals( "foo", ((Activity)pagedResult.getItems()[0]).text()); + Assert.assertNotEquals(((Activity)pagedResult.getItems()[1]), null); + Assert.assertEquals("echo:foo", ((Activity) pagedResult.getItems()[2]).text()); + Assert.assertEquals("bar", ((Activity)pagedResult.getItems()[3]).text()); + + Assert.assertTrue(pagedResult.getItems()[4] != null); + Assert.assertEquals("echo:bar", ((Activity)pagedResult.getItems()[5]).text()); + for (Object activity : pagedResult.getItems()) + { + Assert.assertFalse(StringUtils.isBlank(((Activity) activity).id())); + Assert.assertTrue(((Activity)activity).timestamp().isAfter(Long.MIN_VALUE)); + } + System.out.printf("Complete"); + } + + @Test + public void Transcript_LogUpdateActivities() throws InterruptedException, ExecutionException { + MemoryTranscriptStore transcriptStore = new MemoryTranscriptStore(); + TestAdapter adapter = (new TestAdapter()).Use(new TranscriptLoggerMiddleware(transcriptStore)); + final String[] conversationId = {null}; + final Activity[] activityToUpdate = {null}; + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JodaModule()); + new TestFlow(adapter, (context) -> + { + + conversationId[0] = context.getActivity().conversation().id(); + if (context.getActivity().text().equals("update")) { + activityToUpdate[0].withText("new response"); + try { + context.UpdateActivity(activityToUpdate[0]); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + ActivityImpl activity = ((ActivityImpl) context.getActivity()).CreateReply("response"); + ResourceResponse response = null; + try { + response = context.SendActivity(activity); + } catch (Exception e) { + e.printStackTrace(); + Assert.fail(); + } + activity.withId(response.id()); + + // clone the activity, so we can use it to do an update + activityToUpdate[0] = ActivityImpl.CloneActity(activity); + //JsonConvert.DeserializeObject(JsonConvert.SerializeObject(activity)); + } + }).Send("foo") + .Send("update") + .AssertReply("new response") + .StartTest(); + Thread.sleep(500); + PagedResult pagedResult = transcriptStore.GetTranscriptActivitiesAsync("test", conversationId[0]).join(); + Assert.assertEquals(4, pagedResult.getItems().length); + Assert.assertEquals("foo", ((Activity)pagedResult.getItems()[0]).text()); + Assert.assertEquals( "response", ((Activity)pagedResult.getItems()[1]).text()); + Assert.assertEquals( "new response", ((Activity)pagedResult.getItems()[2]).text()); + Assert.assertEquals("update", ((Activity)pagedResult.getItems()[3]).text()); + Assert.assertEquals( ((Activity)pagedResult.getItems()[1]).id(), ((Activity) pagedResult.getItems()[2]).id()); + + } + + @Test + public final void Transcript_LogDeleteActivities() throws InterruptedException, ExecutionException { + MemoryTranscriptStore transcriptStore = new MemoryTranscriptStore(); + TestAdapter adapter = (new TestAdapter()).Use(new TranscriptLoggerMiddleware(transcriptStore)); + final String[] conversationId = {null}; + final String[] activityId = {null}; + new TestFlow(adapter, (context) -> + { + + conversationId[0] = context.getActivity().conversation().id(); + if (context.getActivity().text().equals("deleteIt")) { + try { + context.DeleteActivity(activityId[0]).join(); + } catch (Exception e) { + e.printStackTrace(); + Assert.fail(); + } + } else { + ActivityImpl activity = ((ActivityImpl) context.getActivity()).CreateReply("response"); + ResourceResponse response = null; + try { + response = context.SendActivity(activity); + } catch (Exception e) { + e.printStackTrace(); + Assert.fail(); + } + activityId[0] = response.id(); + } + + + }).Send("foo") + .AssertReply("response") + .Send("deleteIt") + .StartTest(); + Thread.sleep(1500); + PagedResult pagedResult = transcriptStore.GetTranscriptActivitiesAsync("test", conversationId[0]).join(); + for (Object act : pagedResult.getItems()) { + System.out.printf("Here is the object: %s : Type: %s\n", act.getClass().getTypeName(), ((Activity)act).type()); + } + + for (Object activity : pagedResult.getItems() ) { + System.out.printf("Recipient: %s\nText: %s\n", ((Activity) activity).recipient().name(), ((Activity)activity).text()); + } + Assert.assertEquals(4, pagedResult.getItems().length); + Assert.assertEquals("foo", ((Activity)pagedResult.getItems()[0]).text()); + Assert.assertEquals("response", ((Activity)pagedResult.getItems()[1]).text()); + Assert.assertEquals("deleteIt", ((Activity)pagedResult.getItems()[2]).text()); + Assert.assertEquals(ActivityTypes.MESSAGE_DELETE, ((Activity)pagedResult.getItems()[3]).type()); + Assert.assertEquals(((Activity)pagedResult.getItems()[1]).id(), ((Activity) pagedResult.getItems()[3]).id()); + } +} + diff --git a/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/TypedObject.java b/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/TypedObject.java index 49beb673b..bc1892432 100644 --- a/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/TypedObject.java +++ b/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/TypedObject.java @@ -1,6 +1,9 @@ package com.microsoft.bot.builder; +import com.fasterxml.jackson.annotation.JsonProperty; + public class TypedObject { + @JsonProperty private String name; public String name() { diff --git a/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/WasCalledMiddlware.java b/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/WasCalledMiddlware.java index 3f90d7e74..190aaf36f 100644 --- a/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/WasCalledMiddlware.java +++ b/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/WasCalledMiddlware.java @@ -11,8 +11,8 @@ public void setCalled(boolean called) { this.called = called; } - public CompletableFuture OnTurn(TurnContext context, NextDelegate next) throws Exception { + public void OnTurn(TurnContext context, NextDelegate next) throws Exception { setCalled(true); - return next.next(); + next.next(); } } diff --git a/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/adapters/TestFlow.java b/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/adapters/TestFlow.java index 9b61713a7..f86178824 100644 --- a/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/adapters/TestFlow.java +++ b/libraries/botbuilder/src/test/java/com/microsoft/bot/builder/adapters/TestFlow.java @@ -4,147 +4,155 @@ import com.microsoft.bot.schema.ActivityImpl; import com.microsoft.bot.schema.models.Activity; import org.joda.time.DateTime; +import org.junit.Assert; import java.lang.management.ManagementFactory; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.ArrayList; +import java.util.concurrent.*; +import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.concurrent.CompletableFuture.supplyAsync; public class TestFlow { final TestAdapter adapter; - CompletableFuture testTask; - Function callback; + CompletableFuture testTask; + Consumer callback; + + ArrayList> tasks = new ArrayList>(); + ForkJoinPool.ForkJoinWorkerThreadFactory factory = new ForkJoinPool.ForkJoinWorkerThreadFactory() + { + @Override + public ForkJoinWorkerThread newThread(ForkJoinPool pool) + { + final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool); + worker.setName("TestFlow-" + worker.getPoolIndex()); + return worker; + } + }; + + ExecutorService executor = new ForkJoinPool(Runtime.getRuntime().availableProcessors(), factory, null, true); + public TestFlow(TestAdapter adapter) { this(adapter, null); } - public TestFlow(TestAdapter adapter, Function callback) { + public TestFlow(TestAdapter adapter, Consumer callback) { this.adapter = adapter; this.callback = callback; this.testTask = completedFuture(null); } - public TestFlow(CompletableFuture testTask, TestFlow flow) { - if (testTask == null) - this.testTask = completedFuture(null); - else - this.testTask = testTask; + public TestFlow(Supplier testTask, TestFlow flow) { + this.tasks = flow.tasks; + if (testTask != null) + this.tasks.add(testTask); this.callback = flow.callback; this.adapter = flow.adapter; } + /** * Start the execution of the test flow - * @return + * + * @return */ - public CompletableFuture StartTest() throws ExecutionException, InterruptedException { - return (CompletableFuture) this.testTask; + public String StartTest() throws ExecutionException, InterruptedException { + + System.out.printf("+------------------------------------------+\n"); + int count = 0; + for (Supplier task : this.tasks) { + System.out.printf("| Running task %s of %s\n", count++, this.tasks.size()); + String result = null; + result = task.get(); + System.out.printf("| --> Result: %s", result); + System.out.flush(); + } + System.out.printf("+------------------------------------------+\n"); + return "Completed"; + } /** * Send a message from the user to the bot - * @param userSays - * @return + * + * @param userSays + * @return */ -// public TestFlow Send(String userSays) throws IllegalArgumentException { -// if (userSays == null) -// throw new IllegalArgumentException("You have to pass a userSays parameter"); -// -// System.out.print(String.format("USER SAYS: %s", userSays)); -// System.out.flush(); -// -// /** -// */ Function -// */ -// return new TestFlow(this.testTask.thenCompose(task -> supplyAsync(() ->{ -// /** -// */ task.Wait(); -// */ -// -// try { -// this.adapter.SendTextToBot(userSays, this.callback); -// return null; -// } catch (Exception e) { -// return e.getMessage(); -// } -// })), this); -// } public TestFlow Send(String userSays) throws IllegalArgumentException { if (userSays == null) throw new IllegalArgumentException("You have to pass a userSays parameter"); - System.out.print(String.format("USER SAYS: %s (Thread Id: %s)\n", userSays, Thread.currentThread().getId())); - System.out.flush(); - // Function - return new TestFlow(this.testTask.thenApply(task -> supplyAsync(() ->{ - // task.Wait(); - + return new TestFlow((() -> { + System.out.print(String.format("USER SAYS: %s (Thread Id: %s)\n", userSays, Thread.currentThread().getId())); + System.out.flush(); try { this.adapter.SendTextToBot(userSays, this.callback); - return null; + return "Successfully sent " + userSays; } catch (Exception e) { + Assert.fail(e.getMessage()); return e.getMessage(); } - })), this); + }), this); } /** * Send an activity from the user to the bot - * @param userActivity - * @return + * + * @param userActivity + * @return */ public TestFlow Send(Activity userActivity) { if (userActivity == null) throw new IllegalArgumentException("You have to pass an Activity"); - return new TestFlow(this.testTask.thenCompose(task -> supplyAsync(() ->{ - // NOTE: See details code in above method. - //task.Wait(); + return new TestFlow((() -> { + System.out.printf("TestFlow(%s): Send with User Activity! %s", Thread.currentThread().getId(), userActivity.text()); + System.out.flush(); + try { this.adapter.ProcessActivity((ActivityImpl) userActivity, this.callback); + return "TestFlow: Send() -> ProcessActivity: " + userActivity.text(); } catch (Exception e) { return e.getMessage(); + } - return null; - })), this); + + }), this); } /** * Delay for time period - * @param ms - * @return + * + * @param ms + * @return */ public TestFlow Delay(int ms) { - return new TestFlow(this.testTask.thenCompose(task -> supplyAsync(() ->{ - - // NOTE: See details code in above method. - //task.Wait(); - - + return new TestFlow(() -> + { + System.out.printf("TestFlow(%s): Delay(%s ms) called. ", Thread.currentThread().getId(), ms); + System.out.flush(); try { Thread.sleep((int) ms); } catch (InterruptedException e) { return e.getMessage(); } return null; - })), this); + }, this); } /** * Assert that reply is expected text - * @param expected - * @param description - * @param timeout - * @return + * + * @param expected + * @param description + * @param timeout + * @return */ public TestFlow AssertReply(String expected) { return this.AssertReply(expected, null, 3000); @@ -160,10 +168,11 @@ public TestFlow AssertReply(String expected, String description, int timeout) { /** * Assert that the reply is expected activity - * @param expected - * @param description - * @param timeout - * @return + * + * @param expected + * @param description + * @param timeout + * @return */ public TestFlow AssertReply(Activity expected) { String description = Thread.currentThread().getStackTrace()[1].getMethodName(); @@ -190,10 +199,11 @@ public TestFlow AssertReply(Activity expected, String description, int timeout) /** * Assert that the reply matches a custom validation routine - * @param validateActivity - * @param description - * @param timeout - * @return + * + * @param validateActivity + * @param description + * @param timeout + * @return */ public TestFlow AssertReply(Function validateActivity) { String description = Thread.currentThread().getStackTrace()[1].getMethodName(); @@ -205,35 +215,51 @@ public TestFlow AssertReply(Function validateActivity, String } public TestFlow AssertReply(Function validateActivity, String description, int timeout) { - return new TestFlow(this.testTask.thenApply(task -> supplyAsync(() ->{ - // NOTE: See details code in above method. - //task.Wait(); + return new TestFlow(() -> { + System.out.println(String.format("AssertReply: Starting loop : %s (Thread:%s)", description, Thread.currentThread().getId())); + System.out.flush(); + int finalTimeout = Integer.MAX_VALUE; if (isDebug()) finalTimeout = Integer.MAX_VALUE; - System.out.println(String.format("AssertReply: Starting loop : %s (Thread:%s)", description, Thread.currentThread().getId())); - System.out.flush(); DateTime start = DateTime.now(); while (true) { DateTime current = DateTime.now(); - if ((current.getMillis() - start.getMillis()) > (long) finalTimeout) + if ((current.getMillis() - start.getMillis()) > (long) finalTimeout) { + System.out.println("AssertReply: Timeout!\n"); + System.out.flush(); return String.format("%d ms Timed out waiting for:'%s'", finalTimeout, description); + } +// System.out.println("Before GetNextReply\n"); +// System.out.flush(); Activity replyActivity = this.adapter.GetNextReply(); - System.out.println(String.format("AssertReply: Received Reply: %s (Thread:%s) ", - String.format("=============\n From: %s\n To:%s\n Text:%s\n==========\n", replyActivity.from().name(), replyActivity.recipient().name(), replyActivity.text()), - Thread.currentThread().getId())); - System.out.flush(); +// System.out.println("After GetNextReply\n"); +// System.out.flush(); if (replyActivity != null) { + System.out.printf("AssertReply(tid:%s): Received Reply: %s ", Thread.currentThread().getId(), (replyActivity.text() == null) ? "No Text set" : replyActivity.text()); + System.out.flush(); + System.out.printf("=============\n From: %s\n To:%s\n ==========\n", (replyActivity.from() == null) ? "No from set" : replyActivity.from().name(), + (replyActivity.recipient() == null) ? "No recipient set" : replyActivity.recipient().name()); + System.out.flush(); + // if we have a reply return validateActivity.apply(replyActivity); + } else { + System.out.printf("AssertReply(tid:%s): Waiting..\n", Thread.currentThread().getId()); + System.out.flush(); + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } } } - })), this); + }, this); } // Hack to determine if debugger attached.. @@ -248,7 +274,6 @@ public boolean isDebug() { /** - * * @param userSays * @param expected * @return @@ -258,10 +283,11 @@ public TestFlow Turn(String userSays, String expected, String description, int t try { result = CompletableFuture.supplyAsync(() -> { // Send the message + if (userSays == null) throw new IllegalArgumentException("You have to pass a userSays parameter"); - System.out.print(String.format("TestTurn(%s): USER SAYS: %s \n",Thread.currentThread().getId(), userSays )); + System.out.print(String.format("TestTurn(%s): USER SAYS: %s \n", Thread.currentThread().getId(), userSays)); System.out.flush(); try { @@ -272,58 +298,58 @@ public TestFlow Turn(String userSays, String expected, String description, int t } }) - .thenApply(arg -> { // Assert Reply - int finalTimeout = Integer.MAX_VALUE; - if (isDebug()) - finalTimeout = Integer.MAX_VALUE; - Function validateActivity = activity -> { - if (activity.text().equals(expected)) { - System.out.println(String.format("TestTurn(tid:%s): Validated text is: %s", Thread.currentThread().getId(), expected )); + .thenApply(arg -> { // Assert Reply + int finalTimeout = Integer.MAX_VALUE; + if (isDebug()) + finalTimeout = Integer.MAX_VALUE; + Function validateActivity = activity -> { + if (activity.text().equals(expected)) { + System.out.println(String.format("TestTurn(tid:%s): Validated text is: %s", Thread.currentThread().getId(), expected)); + System.out.flush(); + + return "SUCCESS"; + } + System.out.println(String.format("TestTurn(tid:%s): Failed validate text is: %s", Thread.currentThread().getId(), expected)); System.out.flush(); - return "SUCCESS"; - } - System.out.println(String.format("TestTurn(tid:%s): Failed validate text is: %s", Thread.currentThread().getId(), expected )); - System.out.flush(); - - return String.format("FAIL: %s received in Activity.text (%s expected)", activity.text(), expected); + return String.format("FAIL: %s received in Activity.text (%s expected)", activity.text(), expected); }; - System.out.println(String.format("TestTurn(tid:%s): Started receive loop: %s", Thread.currentThread().getId(), description )); - System.out.flush(); - DateTime start = DateTime.now(); - while (true) { - DateTime current = DateTime.now(); + System.out.println(String.format("TestTurn(tid:%s): Started receive loop: %s", Thread.currentThread().getId(), description)); + System.out.flush(); + DateTime start = DateTime.now(); + while (true) { + DateTime current = DateTime.now(); - if ((current.getMillis() - start.getMillis()) > (long) finalTimeout) - return String.format("TestTurn: %d ms Timed out waiting for:'%s'", finalTimeout, description); + if ((current.getMillis() - start.getMillis()) > (long) finalTimeout) + return String.format("TestTurn: %d ms Timed out waiting for:'%s'", finalTimeout, description); - Activity replyActivity = this.adapter.GetNextReply(); + Activity replyActivity = this.adapter.GetNextReply(); - if (replyActivity != null) { - // if we have a reply - System.out.println(String.format("TestTurn(tid:%s): Received Reply: %s", - Thread.currentThread().getId(), - String.format("\n========\n To:%s\n From:%s\n Msg:%s\n=======", replyActivity.recipient().name(), replyActivity.from().name(), replyActivity.text()) + if (replyActivity != null) { + // if we have a reply + System.out.println(String.format("TestTurn(tid:%s): Received Reply: %s", + Thread.currentThread().getId(), + String.format("\n========\n To:%s\n From:%s\n Msg:%s\n=======", replyActivity.recipient().name(), replyActivity.from().name(), replyActivity.text()) )); - System.out.flush(); - return validateActivity.apply(replyActivity); - } - else { - System.out.println(String.format("TestTurn(tid:%s): No reply..", Thread.currentThread().getId())); - System.out.flush(); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } + System.out.flush(); + return validateActivity.apply(replyActivity); + } else { + System.out.println(String.format("TestTurn(tid:%s): No reply..", Thread.currentThread().getId())); + System.out.flush(); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } - }}) - .get(timeout, TimeUnit.MILLISECONDS); + } + }) + .get(timeout, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { @@ -334,20 +360,24 @@ public TestFlow Turn(String userSays, String expected, String description, int t return this; } + /** * Say() -> shortcut for .Send(user).AssertReply(Expected) - * @param userSays - * @param expected - * @param description - * @param timeout - * @return + * + * @param userSays + * @param expected + * @param description + * @param timeout + * @return */ public TestFlow Test(String userSays, String expected) { return Test(userSays, expected, null, 3000); } + public TestFlow Test(String userSays, String expected, String description) { return Test(userSays, expected, description, 3000); } + public TestFlow Test(String userSays, String expected, String description, int timeout) { if (expected == null) throw new IllegalArgumentException("expected"); @@ -358,18 +388,21 @@ public TestFlow Test(String userSays, String expected, String description, int t /** * Test() -> shortcut for .Send(user).AssertReply(Expected) - * @param userSays - * @param expected - * @param description - * @param timeout - * @return + * + * @param userSays + * @param expected + * @param description + * @param timeout + * @return */ public TestFlow Test(String userSays, Activity expected) { return Test(userSays, expected, null, 3000); } + public TestFlow Test(String userSays, Activity expected, String description) { return Test(userSays, expected, description, 3000); } + public TestFlow Test(String userSays, Activity expected, String description, int timeout) { if (expected == null) throw new IllegalArgumentException("expected"); @@ -380,18 +413,21 @@ public TestFlow Test(String userSays, Activity expected, String description, int /** * Say() -> shortcut for .Send(user).AssertReply(Expected) - * @param userSays - * @param expected - * @param description - * @param timeout - * @return + * + * @param userSays + * @param expected + * @param description + * @param timeout + * @return */ - public TestFlow Test(String userSays, Function expected) { + public TestFlow Test(String userSays, Function expected) { return Test(userSays, expected, null, 3000); } - public TestFlow Test(String userSays, Function expected, String description) { + + public TestFlow Test(String userSays, Function expected, String description) { return Test(userSays, expected, description, 3000); } + public TestFlow Test(String userSays, Function expected, String description, int timeout) { if (expected == null) throw new IllegalArgumentException("expected"); @@ -402,28 +438,31 @@ public TestFlow Test(String userSays, Function expected, Strin /** * Assert that reply is one of the candidate responses - * @param candidates - * @param description - * @param timeout - * @return + * + * @param candidates + * @param description + * @param timeout + * @return */ public TestFlow AssertReplyOneOf(String[] candidates) { return AssertReplyOneOf(candidates, null, 3000); } + public TestFlow AssertReplyOneOf(String[] candidates, String description) { return AssertReplyOneOf(candidates, description, 3000); } + public TestFlow AssertReplyOneOf(String[] candidates, String description, int timeout) { if (candidates == null) throw new IllegalArgumentException("candidates"); return this.AssertReply((reply) -> { - for(String candidate : candidates) { - if (reply.text() == candidate) - return null; - } - return String.format("%s: Not one of candidates: %s", description, String.join("\n ", candidates)); - },description, timeout); + for (String candidate : candidates) { + if (reply.text() == candidate) + return null; + } + return String.format("%s: Not one of candidates: %s", description, String.join("\n ", candidates)); + }, description, timeout); } } diff --git a/libraries/swagger/ConnectorAPI.json b/libraries/swagger/ConnectorAPI.json deleted file mode 100644 index e64ba630a..000000000 --- a/libraries/swagger/ConnectorAPI.json +++ /dev/null @@ -1,2439 +0,0 @@ -{ - "swagger": "2.0", - "info": { - "version": "v3", - "title": "Microsoft Bot Connector API - v3.0", - "description": "The Bot Connector REST API allows your bot to send and receive messages to channels configured in the\r\n[Bot Framework Developer Portal](https://dev.botframework.com). The Connector service uses industry-standard REST\r\nand JSON over HTTPS.\r\n\r\nClient libraries for this REST API are available. See below for a list.\r\n\r\nMany bots will use both the Bot Connector REST API and the associated [Bot State REST API](/en-us/restapi/state). The\r\nBot State REST API allows a bot to store and retrieve state associated with users and conversations.\r\n\r\nAuthentication for both the Bot Connector and Bot State REST APIs is accomplished with JWT Bearer tokens, and is\r\ndescribed in detail in the [Connector Authentication](/en-us/restapi/authentication) document.\r\n\r\n# Client Libraries for the Bot Connector REST API\r\n\r\n* [Bot Builder for C#](/en-us/csharp/builder/sdkreference/)\r\n* [Bot Builder for Node.js](/en-us/node/builder/overview/)\r\n* Generate your own from the [Connector API Swagger file](https://raw.githubusercontent.com/Microsoft/BotBuilder/master/CSharp/Library/Microsoft.Bot.Connector.Shared/Swagger/ConnectorAPI.json)\r\n\r\n© 2016 Microsoft", - "termsOfService": "https://www.microsoft.com/en-us/legal/intellectualproperty/copyright/default.aspx", - "contact": { - "name": "Bot Framework", - "url": "https://botframework.com", - "email": "botframework@microsoft.com" - }, - "license": { - "name": "The MIT License (MIT)", - "url": "https://opensource.org/licenses/MIT" - } - }, - "host": "api.botframework.com", - "schemes": [ - "https" - ], - "paths": { - "/v3/attachments/{attachmentId}": { - "get": { - "tags": [ - "Attachments" - ], - "summary": "GetAttachmentInfo", - "description": "Get AttachmentInfo structure describing the attachment views", - "operationId": "Attachments_GetAttachmentInfo", - "consumes": [ - - ], - "produces": [ - "application/json", - "text/json", - "application/xml", - "text/xml" - ], - "parameters": [ - { - "name": "attachmentId", - "in": "path", - "description": "attachment id", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "An attachmentInfo object is returned which describes the:\r\n* type of the attachment\r\n* name of the attachment\r\n\r\n\r\nand an array of views:\r\n* Size - size of the object\r\n* ViewId - View Id which can be used to fetch a variation on the content (ex: original or thumbnail)", - "schema": { - "$ref": "#/definitions/AttachmentInfo" - } - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - } - }, - "/v3/attachments/{attachmentId}/views/{viewId}": { - "get": { - "tags": [ - "Attachments" - ], - "summary": "GetAttachment", - "description": "Get the named view as binary content", - "operationId": "Attachments_GetAttachment", - "consumes": [ - - ], - "produces": [ - "application/json", - "text/json", - "application/xml", - "text/xml" - ], - "parameters": [ - { - "name": "attachmentId", - "in": "path", - "description": "attachment id", - "required": true, - "type": "string" - }, - { - "name": "viewId", - "in": "path", - "description": "View id from attachmentInfo", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "Attachment stream", - "schema": { - "format": "byte", - "type": "file" - } - }, - "301": { - "description": "The Location header describes where the content is now." - }, - "302": { - "description": "The Location header describes where the content is now." - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - } - }, - "/v3/conversations": { - "get": { - "tags": [ - "Conversations" - ], - "summary": "GetConversations", - "description": "List the Conversations in which this bot has participated.\r\n\r\nGET from this method with a skip token\r\n\r\nThe return value is a ConversationsResult, which contains an array of ConversationMembers and a skip token. If the skip token is not empty, then \r\nthere are further values to be returned. Call this method again with the returned token to get more values.\r\n\r\nEach ConversationMembers object contains the ID of the conversation and an array of ChannelAccounts that describe the members of the conversation.", - "operationId": "Conversations_GetConversations", - "consumes": [ - - ], - "produces": [ - "application/json", - "text/json", - "application/xml", - "text/xml" - ], - "parameters": [ - { - "name": "continuationToken", - "in": "query", - "description": "skip or continuation token", - "required": false, - "type": "string" - } - ], - "responses": { - "200": { - "description": "An object will be returned containing \r\n* an array (Conversations) of ConversationMembers objects\r\n* a continuation token\r\n\r\nEach ConversationMembers object contains:\r\n* the Id of the conversation\r\n* an array (Members) of ChannelAccount objects", - "schema": { - "$ref": "#/definitions/ConversationsResult" - } - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - }, - "post": { - "tags": [ - "Conversations" - ], - "summary": "CreateConversation", - "description": "Create a new Conversation.\r\n\r\nPOST to this method with a\r\n* Bot being the bot creating the conversation\r\n* IsGroup set to true if this is not a direct message (default is false)\r\n* Members array contining the members you want to have be in the conversation.\r\n\r\nThe return value is a ResourceResponse which contains a conversation id which is suitable for use\r\nin the message payload and REST API uris.\r\n\r\nMost channels only support the semantics of bots initiating a direct message conversation. An example of how to do that would be:\r\n\r\n```\r\nvar resource = await connector.conversations.CreateConversation(new ConversationParameters(){ Bot = bot, members = new ChannelAccount[] { new ChannelAccount(\"user1\") } );\r\nawait connect.Conversations.SendToConversationAsync(resource.Id, new Activity() ... ) ;\r\n\r\n```", - "operationId": "Conversations_CreateConversation", - "consumes": [ - "application/json", - "text/json", - "application/xml", - "text/xml", - "application/x-www-form-urlencoded" - ], - "produces": [ - "application/json", - "text/json", - "application/xml", - "text/xml" - ], - "parameters": [ - { - "name": "parameters", - "in": "body", - "description": "Parameters to create the conversation from", - "required": true, - "schema": { - "$ref": "#/definitions/ConversationParameters" - } - } - ], - "responses": { - "200": { - "description": "An object will be returned containing \r\n* the ID for the conversation\r\n* ActivityId for the activity if provided. If ActivityId is null then the channel doesn't support returning resource id's for activity.", - "schema": { - "$ref": "#/definitions/ConversationResourceResponse" - } - }, - "201": { - "description": "An object will be returned containing \r\n* the ID for the conversation\r\n* ActivityId for the activity if provided. If ActivityId is null then the channel doesn't support returning resource id's for activity.", - "schema": { - "$ref": "#/definitions/ConversationResourceResponse" - } - }, - "202": { - "description": "An object will be returned containing \r\n* the ID for the conversation\r\n* ActivityId for the activity if provided. If ActivityId is null then the channel doesn't support returning resource id's for activity.", - "schema": { - "$ref": "#/definitions/ConversationResourceResponse" - } - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - } - }, - "/v3/conversations/{conversationId}/activities": { - "post": { - "tags": [ - "Conversations" - ], - "summary": "SendToConversation", - "description": "This method allows you to send an activity to the end of a conversation.\r\n\r\nThis is slightly different from ReplyToActivity().\r\n* SendToConverstion(conversationId) - will append the activity to the end of the conversation according to the timestamp or semantics of the channel.\r\n* ReplyToActivity(conversationId,ActivityId) - adds the activity as a reply to another activity, if the channel supports it. If the channel does not support nested replies, ReplyToActivity falls back to SendToConversation.\r\n\r\nUse ReplyToActivity when replying to a specific activity in the conversation.\r\n\r\nUse SendToConversation in all other cases.", - "operationId": "Conversations_SendToConversation", - "consumes": [ - "application/json", - "text/json", - "application/xml", - "text/xml", - "application/x-www-form-urlencoded" - ], - "produces": [ - "application/json", - "text/json" - ], - "parameters": [ - { - "name": "conversationId", - "in": "path", - "description": "Conversation ID", - "required": true, - "type": "string" - }, - { - "name": "activity", - "in": "body", - "description": "Activity to send", - "required": true, - "schema": { - "$ref": "#/definitions/Activity" - } - } - ], - "responses": { - "200": { - "description": "An object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "201": { - "description": "A ResourceResponse object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "202": { - "description": "An object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - } - }, - "/v3/conversations/{conversationId}/activities/{activityId}": { - "put": { - "tags": [ - "Conversations" - ], - "summary": "UpdateActivity", - "description": "Edit an existing activity.\r\n\r\nSome channels allow you to edit an existing activity to reflect the new state of a bot conversation.\r\n\r\nFor example, you can remove buttons after someone has clicked \"Approve\" button.", - "operationId": "Conversations_UpdateActivity", - "consumes": [ - "application/json", - "text/json", - "application/xml", - "text/xml", - "application/x-www-form-urlencoded" - ], - "produces": [ - "application/json", - "text/json" - ], - "parameters": [ - { - "name": "conversationId", - "in": "path", - "description": "Conversation ID", - "required": true, - "type": "string" - }, - { - "name": "activityId", - "in": "path", - "description": "activityId to update", - "required": true, - "type": "string" - }, - { - "name": "activity", - "in": "body", - "description": "replacement Activity", - "required": true, - "schema": { - "$ref": "#/definitions/Activity" - } - } - ], - "responses": { - "200": { - "description": "An object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "201": { - "description": "A ResourceResponse object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "202": { - "description": "An object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - }, - "post": { - "tags": [ - "Conversations" - ], - "summary": "ReplyToActivity", - "description": "This method allows you to reply to an activity.\r\n\r\nThis is slightly different from SendToConversation().\r\n* SendToConverstion(conversationId) - will append the activity to the end of the conversation according to the timestamp or semantics of the channel.\r\n* ReplyToActivity(conversationId,ActivityId) - adds the activity as a reply to another activity, if the channel supports it. If the channel does not support nested replies, ReplyToActivity falls back to SendToConversation.\r\n\r\nUse ReplyToActivity when replying to a specific activity in the conversation.\r\n\r\nUse SendToConversation in all other cases.", - "operationId": "Conversations_ReplyToActivity", - "consumes": [ - "application/json", - "text/json", - "application/xml", - "text/xml", - "application/x-www-form-urlencoded" - ], - "produces": [ - "application/json", - "text/json" - ], - "parameters": [ - { - "name": "conversationId", - "in": "path", - "description": "Conversation ID", - "required": true, - "type": "string" - }, - { - "name": "activityId", - "in": "path", - "description": "activityId the reply is to (OPTIONAL)", - "required": true, - "type": "string" - }, - { - "name": "activity", - "in": "body", - "description": "Activity to send", - "required": true, - "schema": { - "$ref": "#/definitions/Activity" - } - } - ], - "responses": { - "200": { - "description": "An object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "201": { - "description": "A ResourceResponse object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "202": { - "description": "An object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - }, - "delete": { - "tags": [ - "Conversations" - ], - "summary": "DeleteActivity", - "description": "Delete an existing activity.\r\n\r\nSome channels allow you to delete an existing activity, and if successful this method will remove the specified activity.", - "operationId": "Conversations_DeleteActivity", - "consumes": [ - - ], - "produces": [ - "application/json", - "text/json", - "application/xml", - "text/xml" - ], - "parameters": [ - { - "name": "conversationId", - "in": "path", - "description": "Conversation ID", - "required": true, - "type": "string" - }, - { - "name": "activityId", - "in": "path", - "description": "activityId to delete", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "The operation succeeded, there is no response." - }, - "202": { - "description": "The request has been accepted for processing, but the processing has not been completed" - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - } - }, - "/v3/conversations/{conversationId}/members": { - "get": { - "tags": [ - "Conversations" - ], - "summary": "GetConversationMembers", - "description": "Enumerate the members of a converstion. \r\n\r\nThis REST API takes a ConversationId and returns an array of ChannelAccount objects representing the members of the conversation.", - "operationId": "Conversations_GetConversationMembers", - "consumes": [ - - ], - "produces": [ - "application/json", - "text/json", - "application/xml", - "text/xml" - ], - "parameters": [ - { - "name": "conversationId", - "in": "path", - "description": "Conversation ID", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "An array of ChannelAccount objects", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/ChannelAccount" - } - } - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - } - }, - "/v3/conversations/{conversationId}/members/{memberId}": { - "delete": { - "tags": [ - "Conversations" - ], - "summary": "DeleteConversationMember", - "description": "Deletes a member from a converstion. \r\n\r\nThis REST API takes a ConversationId and a memberId (of type string) and removes that member from the conversation. If that member was the last member\r\nof the conversation, the conversation will also be deleted.", - "operationId": "Conversations_DeleteConversationMember", - "consumes": [ - - ], - "produces": [ - "application/json", - "text/json", - "application/xml", - "text/xml" - ], - "parameters": [ - { - "name": "conversationId", - "in": "path", - "description": "Conversation ID", - "required": true, - "type": "string" - }, - { - "name": "memberId", - "in": "path", - "description": "ID of the member to delete from this conversation", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "The operation succeeded, there is no response." - }, - "204": { - "description": "The operation succeeded but no content was returned." - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - } - }, - "/v3/conversations/{conversationId}/activities/{activityId}/members": { - "get": { - "tags": [ - "Conversations" - ], - "summary": "GetActivityMembers", - "description": "Enumerate the members of an activity. \r\n\r\nThis REST API takes a ConversationId and a ActivityId, returning an array of ChannelAccount objects representing the members of the particular activity in the conversation.", - "operationId": "Conversations_GetActivityMembers", - "consumes": [ - - ], - "produces": [ - "application/json", - "text/json", - "application/xml", - "text/xml" - ], - "parameters": [ - { - "name": "conversationId", - "in": "path", - "description": "Conversation ID", - "required": true, - "type": "string" - }, - { - "name": "activityId", - "in": "path", - "description": "Activity ID", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "An array of ChannelAccount objects", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/ChannelAccount" - } - } - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - } - }, - "/v3/conversations/{conversationId}/attachments": { - "post": { - "tags": [ - "Conversations" - ], - "summary": "UploadAttachment", - "description": "Upload an attachment directly into a channel's blob storage.\r\n\r\nThis is useful because it allows you to store data in a compliant store when dealing with enterprises.\r\n\r\nThe response is a ResourceResponse which contains an AttachmentId which is suitable for using with the attachments API.", - "operationId": "Conversations_UploadAttachment", - "consumes": [ - "application/json", - "text/json", - "application/xml", - "text/xml", - "application/x-www-form-urlencoded" - ], - "produces": [ - "application/json", - "text/json" - ], - "parameters": [ - { - "name": "conversationId", - "in": "path", - "description": "Conversation ID", - "required": true, - "type": "string" - }, - { - "name": "attachmentUpload", - "in": "body", - "description": "Attachment data", - "required": true, - "schema": { - "$ref": "#/definitions/AttachmentData" - } - } - ], - "responses": { - "200": { - "description": "An object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "201": { - "description": "A ResourceResponse object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "202": { - "description": "An object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - } - } - }, - "definitions": { - "AttachmentInfo": { - "description": "Metdata for an attachment", - "type": "object", - "properties": { - "name": { - "description": "Name of the attachment", - "type": "string" - }, - "type": { - "description": "ContentType of the attachment", - "type": "string" - }, - "views": { - "description": "attachment views", - "type": "array", - "items": { - "$ref": "#/definitions/AttachmentView" - } - } - } - }, - "AttachmentView": { - "description": "Attachment View name and size", - "type": "object", - "properties": { - "viewId": { - "description": "Content type of the attachment", - "type": "string" - }, - "size": { - "format": "int32", - "description": "Name of the attachment", - "type": "integer" - } - } - }, - "ErrorResponse": { - "description": "An HTTP API response", - "type": "object", - "properties": { - "error": { - "$ref": "#/definitions/Error", - "description": "Error message" - } - } - }, - "Error": { - "description": "Object representing error information", - "type": "object", - "properties": { - "code": { - "description": "Error code", - "type": "string" - }, - "message": { - "description": "Error message", - "type": "string" - } - } - }, - "ConversationParameters": { - "description": "Parameters for creating a new conversation", - "type": "object", - "properties": { - "isGroup": { - "description": "IsGroup", - "type": "boolean" - }, - "bot": { - "$ref": "#/definitions/ChannelAccount", - "description": "The bot address for this conversation" - }, - "members": { - "description": "Members to add to the conversation", - "type": "array", - "items": { - "$ref": "#/definitions/ChannelAccount" - } - }, - "topicName": { - "description": "(Optional) Topic of the conversation (if supported by the channel)", - "type": "string" - }, - "activity": { - "$ref": "#/definitions/Activity", - "description": "(Optional) When creating a new conversation, use this activity as the intial message to the conversation" - }, - "channelData": { - "description": "Channel specific payload for creating the conversation", - "type": "object" - } - } - }, - "ChannelAccount": { - "description": "Channel account information needed to route a message", - "type": "object", - "properties": { - "id": { - "description": "Channel id for the user or bot on this channel (Example: joe@smith.com, or @joesmith or 123456)", - "type": "string" - }, - "name": { - "description": "Display friendly name", - "type": "string" - }, - "role": { - "$ref": "#/definitions/RoleTypes", - "description": "Role of the entity behind the account (Example: User, Bot, etc.)" - } - } - }, - "Activity": { - "description": "An Activity is the basic communication type for the Bot Framework 3.0 protocol", - "type": "object", - "properties": { - "type": { - "$ref": "#/definitions/ActivityTypes", - "description": "The type of the activity" - }, - "id": { - "description": "ID of this activity", - "type": "string" - }, - "timestamp": { - "format": "date-time", - "description": "UTC Time when message was sent (set by service)", - "type": "string" - }, - "localTimestamp": { - "format": "date-time", - "description": "Local time when message was sent (set by client, Ex: 2016-09-23T13:07:49.4714686-07:00)", - "type": "string" - }, - "serviceUrl": { - "description": "Service endpoint where operations concerning the activity may be performed", - "type": "string" - }, - "channelId": { - "description": "ID of the channel where the activity was sent", - "type": "string" - }, - "from": { - "$ref": "#/definitions/ChannelAccount", - "description": "Sender address" - }, - "conversation": { - "$ref": "#/definitions/ConversationAccount", - "description": "Conversation" - }, - "recipient": { - "$ref": "#/definitions/ChannelAccount", - "description": "(Outbound to bot only) Bot's address that received the message" - }, - "textFormat": { - "$ref": "#/definitions/TextFormatTypes", - "description": "Format of text fields Default:markdown" - }, - "attachmentLayout": { - "$ref": "#/definitions/AttachmentLayoutTypes", - "description": "Hint for how to deal with multiple attachments. Default:list" - }, - "membersAdded": { - "description": "Members added to the conversation", - "type": "array", - "items": { - "$ref": "#/definitions/ChannelAccount" - } - }, - "membersRemoved": { - "description": "Members removed from the conversation", - "type": "array", - "items": { - "$ref": "#/definitions/ChannelAccount" - } - }, - "reactionsAdded": { - "description": "Reactions added to the activity", - "type": "array", - "items": { - "$ref": "#/definitions/MessageReaction" - } - }, - "reactionsRemoved": { - "description": "Reactions removed from the activity", - "type": "array", - "items": { - "$ref": "#/definitions/MessageReaction" - } - }, - "topicName": { - "description": "The conversation's updated topic name", - "type": "string" - }, - "historyDisclosed": { - "description": "True if prior history of the channel is disclosed", - "type": "boolean" - }, - "locale": { - "description": "The language code of the Text field", - "type": "string" - }, - "text": { - "description": "Content for the message", - "type": "string" - }, - "speak": { - "description": "SSML Speak for TTS audio response", - "type": "string" - }, - "inputHint": { - "$ref": "#/definitions/InputHints", - "description": "Input hint to the channel on what the bot is expecting." - }, - "summary": { - "description": "Text to display if the channel cannot render cards", - "type": "string" - }, - "suggestedActions": { - "$ref": "#/definitions/SuggestedActions", - "description": "SuggestedActions are used to provide keyboard/quickreply like behavior in many clients" - }, - "attachments": { - "description": "Attachments", - "type": "array", - "items": { - "$ref": "#/definitions/Attachment" - } - }, - "entities": { - "description": "Collection of Entity objects, each of which contains metadata about this activity. Each Entity object is typed.", - "type": "array", - "items": { - "$ref": "#/definitions/Entity" - } - }, - "channelData": { - "description": "Channel-specific payload", - "type": "object" - }, - "action": { - "description": "ContactAdded/Removed action", - "type": "string" - }, - "replyToId": { - "description": "The original ID this message is a response to", - "type": "string" - }, - "label": { - "description": "Descriptive label", - "type": "string" - }, - "valueType": { - "description": "Unique string which identifies the shape of the value object", - "type": "string" - }, - "value": { - "description": "Open-ended value", - "type": "object" - }, - "name": { - "description": "Name of the operation to invoke or the name of the event", - "type": "string" - }, - "relatesTo": { - "$ref": "#/definitions/ConversationReference", - "description": "Reference to another conversation or activity" - }, - "code": { - "$ref": "#/definitions/EndOfConversationCodes", - "description": "Code indicating why the conversation has ended" - }, - "expiration": { - "format": "date-time", - "description": "DateTime to expire the activity as ISO 8601 encoded datetime", - "type": "string" - }, - "importance": { - "description": "Importance of this activity \r\n {Low|Normal|High}, null value indicates Normal importance see ActivityImportance)", - "type": "string" - }, - "deliveryMode": { - "description": "Hint to describe how this activity should be delivered. \r\nCurrently: null or \"Default\" = default delivery\r\n\"Notification\" = notification semantics", - "type": "string" - }, - "textHighlights": { - "description": "TextHighlight in the activity represented in the ReplyToId property", - "type": "array", - "items": { - "$ref": "#/definitions/TextHighlight" - } - } - } - }, - "ConversationAccount": { - "description": "Channel account information for a conversation", - "type": "object", - "properties": { - "isGroup": { - "description": "Indicates whether the conversation contains more than two participants at the time the activity was generated", - "type": "boolean" - }, - "conversationType": { - "description": "Indicates the type of the conversation in channels that distinguish between conversation types", - "type": "string" - }, - "id": { - "description": "Channel id for the user or bot on this channel (Example: joe@smith.com, or @joesmith or 123456)", - "type": "string" - }, - "name": { - "description": "Display friendly name", - "type": "string" - }, - "role": { - "$ref": "#/definitions/RoleTypes", - "description": "Role of the entity behind the account (Example: User, Bot, etc.)" - } - } - }, - "MessageReaction": { - "description": "Message reaction object", - "type": "object", - "properties": { - "type": { - "$ref": "#/definitions/MessageReactionTypes", - "description": "Message reaction type" - } - } - }, - "SuggestedActions": { - "description": "SuggestedActions that can be performed", - "type": "object", - "properties": { - "to": { - "description": "Ids of the recipients that the actions should be shown to. These Ids are relative to the channelId and a subset of all recipients of the activity", - "type": "array", - "items": { - "type": "string" - } - }, - "actions": { - "description": "Actions that can be shown to the user", - "type": "array", - "items": { - "$ref": "#/definitions/CardAction" - } - } - } - }, - "Attachment": { - "description": "An attachment within an activity", - "type": "object", - "properties": { - "contentType": { - "description": "mimetype/Contenttype for the file", - "type": "string" - }, - "contentUrl": { - "description": "Content Url", - "type": "string" - }, - "content": { - "description": "Embedded content", - "type": "object" - }, - "name": { - "description": "(OPTIONAL) The name of the attachment", - "type": "string" - }, - "thumbnailUrl": { - "description": "(OPTIONAL) Thumbnail associated with attachment", - "type": "string" - } - } - }, - "Entity": { - "description": "Object of schema.org types", - "type": "object", - "properties": { - "type": { - "description": "Entity Type (typically from schema.org types)", - "type": "string" - } - } - }, - "ConversationReference": { - "description": "An object relating to a particular point in a conversation", - "type": "object", - "properties": { - "activityId": { - "description": "(Optional) ID of the activity to refer to", - "type": "string" - }, - "user": { - "$ref": "#/definitions/ChannelAccount", - "description": "(Optional) User participating in this conversation" - }, - "bot": { - "$ref": "#/definitions/ChannelAccount", - "description": "Bot participating in this conversation" - }, - "conversation": { - "$ref": "#/definitions/ConversationAccount", - "description": "Conversation reference" - }, - "channelId": { - "description": "Channel ID", - "type": "string" - }, - "serviceUrl": { - "description": "Service endpoint where operations concerning the referenced conversation may be performed", - "type": "string" - } - } - }, - "TextHighlight": { - "description": "", - "type": "object", - "properties": { - "text": { - "description": "plain text fragment to highlight", - "type": "string" - }, - "occurence": { - "format": "int32", - "description": "index of occurence of the Text (Starting at 1)", - "type": "integer" - } - } - }, - "CardAction": { - "description": "A clickable action", - "type": "object", - "properties": { - "type": { - "$ref": "#/definitions/ActionTypes", - "description": "The type of action implemented by this button" - }, - "title": { - "description": "Text description which appears on the button", - "type": "string" - }, - "image": { - "description": "Image URL which will appear on the button, next to text label", - "type": "string" - }, - "text": { - "description": "Text for this action", - "type": "string" - }, - "displayText": { - "description": "(Optional) text to display in the chat feed if the button is clicked", - "type": "string" - }, - "value": { - "description": "Supplementary parameter for action. Content of this property depends on the ActionType", - "type": "object" - } - } - }, - "ConversationResourceResponse": { - "description": "A response containing a resource", - "type": "object", - "properties": { - "activityId": { - "description": "ID of the Activity (if sent)", - "type": "string" - }, - "serviceUrl": { - "description": "Service endpoint where operations concerning the conversation may be performed", - "type": "string" - }, - "id": { - "description": "Id of the resource", - "type": "string" - } - } - }, - "ConversationsResult": { - "description": "Conversations result", - "type": "object", - "properties": { - "continuationToken": { - "description": "Paging token", - "type": "string" - }, - "conversations": { - "description": "List of conversations", - "type": "array", - "items": { - "$ref": "#/definitions/ConversationMembers" - } - } - } - }, - "ConversationMembers": { - "description": "Conversation and its members", - "type": "object", - "properties": { - "id": { - "description": "Conversation ID", - "type": "string" - }, - "members": { - "description": "List of members in this conversation", - "type": "array", - "items": { - "$ref": "#/definitions/ChannelAccount" - } - } - } - }, - "ResourceResponse": { - "description": "A response containing a resource ID", - "type": "object", - "properties": { - "id": { - "description": "Id of the resource", - "type": "string" - } - } - }, - "AttachmentData": { - "description": "Attachment data", - "type": "object", - "properties": { - "type": { - "description": "Content-Type of the attachment", - "type": "string" - }, - "name": { - "description": "Name of the attachment", - "type": "string" - }, - "originalBase64": { - "format": "byte", - "description": "Attachment content", - "type": "string" - }, - "thumbnailBase64": { - "format": "byte", - "description": "Attachment thumbnail", - "type": "string" - } - } - }, - "HeroCard": { - "description": "A Hero card (card with a single, large image)", - "type": "object", - "properties": { - "title": { - "description": "Title of the card", - "type": "string" - }, - "subtitle": { - "description": "Subtitle of the card", - "type": "string" - }, - "text": { - "description": "Text for the card", - "type": "string" - }, - "images": { - "description": "Array of images for the card", - "type": "array", - "items": { - "$ref": "#/definitions/CardImage" - } - }, - "buttons": { - "description": "Set of actions applicable to the current card", - "type": "array", - "items": { - "$ref": "#/definitions/CardAction" - } - }, - "tap": { - "$ref": "#/definitions/CardAction", - "description": "This action will be activated when user taps on the card itself" - } - } - }, - "CardImage": { - "description": "An image on a card", - "type": "object", - "properties": { - "url": { - "description": "URL thumbnail image for major content property", - "type": "string" - }, - "alt": { - "description": "Image description intended for screen readers", - "type": "string" - }, - "tap": { - "$ref": "#/definitions/CardAction", - "description": "Action assigned to specific Attachment" - } - } - }, - "AnimationCard": { - "description": "An animation card (Ex: gif or short video clip)", - "type": "object", - "properties": { - "title": { - "description": "Title of this card", - "type": "string" - }, - "subtitle": { - "description": "Subtitle of this card", - "type": "string" - }, - "text": { - "description": "Text of this card", - "type": "string" - }, - "image": { - "$ref": "#/definitions/ThumbnailUrl", - "description": "Thumbnail placeholder" - }, - "media": { - "description": "Media URLs for this card", - "type": "array", - "items": { - "$ref": "#/definitions/MediaUrl" - } - }, - "buttons": { - "description": "Actions on this card", - "type": "array", - "items": { - "$ref": "#/definitions/CardAction" - } - }, - "shareable": { - "description": "This content may be shared with others (default:true)", - "type": "boolean" - }, - "autoloop": { - "description": "Should the client loop playback at end of content (default:true)", - "type": "boolean" - }, - "autostart": { - "description": "Should the client automatically start playback of media in this card (default:true)", - "type": "boolean" - }, - "aspect": { - "description": "Aspect ratio of thumbnail/media placeholder, allowed values are \"16:9\" and \"4:3\"", - "type": "string" - }, - "value": { - "description": "Supplementary parameter for this card", - "type": "object" - } - } - }, - "ThumbnailUrl": { - "description": "Thumbnail URL", - "type": "object", - "properties": { - "url": { - "description": "URL pointing to the thumbnail to use for media content", - "type": "string" - }, - "alt": { - "description": "HTML alt text to include on this thumbnail image", - "type": "string" - } - } - }, - "MediaUrl": { - "description": "Media URL", - "type": "object", - "properties": { - "url": { - "description": "Url for the media", - "type": "string" - }, - "profile": { - "description": "Optional profile hint to the client to differentiate multiple MediaUrl objects from each other", - "type": "string" - } - } - }, - "AudioCard": { - "description": "Audio card", - "type": "object", - "properties": { - "title": { - "description": "Title of this card", - "type": "string" - }, - "subtitle": { - "description": "Subtitle of this card", - "type": "string" - }, - "text": { - "description": "Text of this card", - "type": "string" - }, - "image": { - "$ref": "#/definitions/ThumbnailUrl", - "description": "Thumbnail placeholder" - }, - "media": { - "description": "Media URLs for this card", - "type": "array", - "items": { - "$ref": "#/definitions/MediaUrl" - } - }, - "buttons": { - "description": "Actions on this card", - "type": "array", - "items": { - "$ref": "#/definitions/CardAction" - } - }, - "shareable": { - "description": "This content may be shared with others (default:true)", - "type": "boolean" - }, - "autoloop": { - "description": "Should the client loop playback at end of content (default:true)", - "type": "boolean" - }, - "autostart": { - "description": "Should the client automatically start playback of media in this card (default:true)", - "type": "boolean" - }, - "aspect": { - "description": "Aspect ratio of thumbnail/media placeholder, allowed values are \"16:9\" and \"4:3\"", - "type": "string" - }, - "value": { - "description": "Supplementary parameter for this card", - "type": "object" - } - } - }, - "BasicCard": { - "description": "A basic card", - "type": "object", - "properties": { - "title": { - "description": "Title of the card", - "type": "string" - }, - "subtitle": { - "description": "Subtitle of the card", - "type": "string" - }, - "text": { - "description": "Text for the card", - "type": "string" - }, - "images": { - "description": "Array of images for the card", - "type": "array", - "items": { - "$ref": "#/definitions/CardImage" - } - }, - "buttons": { - "description": "Set of actions applicable to the current card", - "type": "array", - "items": { - "$ref": "#/definitions/CardAction" - } - }, - "tap": { - "$ref": "#/definitions/CardAction", - "description": "This action will be activated when user taps on the card itself" - } - } - }, - "MediaCard": { - "description": "Media card", - "type": "object", - "properties": { - "title": { - "description": "Title of this card", - "type": "string" - }, - "subtitle": { - "description": "Subtitle of this card", - "type": "string" - }, - "text": { - "description": "Text of this card", - "type": "string" - }, - "image": { - "$ref": "#/definitions/ThumbnailUrl", - "description": "Thumbnail placeholder" - }, - "media": { - "description": "Media URLs for this card", - "type": "array", - "items": { - "$ref": "#/definitions/MediaUrl" - } - }, - "buttons": { - "description": "Actions on this card", - "type": "array", - "items": { - "$ref": "#/definitions/CardAction" - } - }, - "shareable": { - "description": "This content may be shared with others (default:true)", - "type": "boolean" - }, - "autoloop": { - "description": "Should the client loop playback at end of content (default:true)", - "type": "boolean" - }, - "autostart": { - "description": "Should the client automatically start playback of media in this card (default:true)", - "type": "boolean" - }, - "aspect": { - "description": "Aspect ratio of thumbnail/media placeholder, allowed values are \"16:9\" and \"4:3\"", - "type": "string" - }, - "value": { - "description": "Supplementary parameter for this card", - "type": "object" - } - } - }, - "ReceiptCard": { - "description": "A receipt card", - "type": "object", - "properties": { - "title": { - "description": "Title of the card", - "type": "string" - }, - "facts": { - "description": "Array of Fact objects", - "type": "array", - "items": { - "$ref": "#/definitions/Fact" - } - }, - "items": { - "description": "Array of Receipt Items", - "type": "array", - "items": { - "$ref": "#/definitions/ReceiptItem" - } - }, - "tap": { - "$ref": "#/definitions/CardAction", - "description": "This action will be activated when user taps on the card" - }, - "total": { - "description": "Total amount of money paid (or to be paid)", - "type": "string" - }, - "tax": { - "description": "Total amount of tax paid (or to be paid)", - "type": "string" - }, - "vat": { - "description": "Total amount of VAT paid (or to be paid)", - "type": "string" - }, - "buttons": { - "description": "Set of actions applicable to the current card", - "type": "array", - "items": { - "$ref": "#/definitions/CardAction" - } - } - } - }, - "Fact": { - "description": "Set of key-value pairs. Advantage of this section is that key and value properties will be \r\nrendered with default style information with some delimiter between them. So there is no need for developer to specify style information.", - "type": "object", - "properties": { - "key": { - "description": "The key for this Fact", - "type": "string" - }, - "value": { - "description": "The value for this Fact", - "type": "string" - } - } - }, - "ReceiptItem": { - "description": "An item on a receipt card", - "type": "object", - "properties": { - "title": { - "description": "Title of the Card", - "type": "string" - }, - "subtitle": { - "description": "Subtitle appears just below Title field, differs from Title in font styling only", - "type": "string" - }, - "text": { - "description": "Text field appears just below subtitle, differs from Subtitle in font styling only", - "type": "string" - }, - "image": { - "$ref": "#/definitions/CardImage", - "description": "Image" - }, - "price": { - "description": "Amount with currency", - "type": "string" - }, - "quantity": { - "description": "Number of items of given kind", - "type": "string" - }, - "tap": { - "$ref": "#/definitions/CardAction", - "description": "This action will be activated when user taps on the Item bubble." - } - } - }, - "SigninCard": { - "description": "A card representing a request to sign in", - "type": "object", - "properties": { - "text": { - "description": "Text for signin request", - "type": "string" - }, - "buttons": { - "description": "Action to use to perform signin", - "type": "array", - "items": { - "$ref": "#/definitions/CardAction" - } - } - } - }, - "OAuthCard": { - "description": "A card representing a request to peform a sign in via OAuth", - "type": "object", - "properties": { - "text": { - "description": "Text for signin request", - "type": "string" - }, - "connectionName": { - "description": "The name of the registered connection", - "type": "string" - }, - "buttons": { - "description": "Action to use to perform signin", - "type": "array", - "items": { - "$ref": "#/definitions/CardAction" - } - } - } - }, - "ThumbnailCard": { - "description": "A thumbnail card (card with a single, small thumbnail image)", - "type": "object", - "properties": { - "title": { - "description": "Title of the card", - "type": "string" - }, - "subtitle": { - "description": "Subtitle of the card", - "type": "string" - }, - "text": { - "description": "Text for the card", - "type": "string" - }, - "images": { - "description": "Array of images for the card", - "type": "array", - "items": { - "$ref": "#/definitions/CardImage" - } - }, - "buttons": { - "description": "Set of actions applicable to the current card", - "type": "array", - "items": { - "$ref": "#/definitions/CardAction" - } - }, - "tap": { - "$ref": "#/definitions/CardAction", - "description": "This action will be activated when user taps on the card itself" - } - } - }, - "VideoCard": { - "description": "Video card", - "type": "object", - "properties": { - "title": { - "description": "Title of this card", - "type": "string" - }, - "subtitle": { - "description": "Subtitle of this card", - "type": "string" - }, - "text": { - "description": "Text of this card", - "type": "string" - }, - "image": { - "$ref": "#/definitions/ThumbnailUrl", - "description": "Thumbnail placeholder" - }, - "media": { - "description": "Media URLs for this card", - "type": "array", - "items": { - "$ref": "#/definitions/MediaUrl" - } - }, - "buttons": { - "description": "Actions on this card", - "type": "array", - "items": { - "$ref": "#/definitions/CardAction" - } - }, - "shareable": { - "description": "This content may be shared with others (default:true)", - "type": "boolean" - }, - "autoloop": { - "description": "Should the client loop playback at end of content (default:true)", - "type": "boolean" - }, - "autostart": { - "description": "Should the client automatically start playback of media in this card (default:true)", - "type": "boolean" - }, - "aspect": { - "description": "Aspect ratio of thumbnail/media placeholder, allowed values are \"16:9\" and \"4:3\"", - "type": "string" - }, - "value": { - "description": "Supplementary parameter for this card", - "type": "object" - } - } - }, - "GeoCoordinates": { - "description": "GeoCoordinates (entity type: \"https://schema.org/GeoCoordinates\")", - "type": "object", - "properties": { - "elevation": { - "format": "double", - "description": "Elevation of the location [WGS 84](https://en.wikipedia.org/wiki/World_Geodetic_System)", - "type": "number" - }, - "latitude": { - "format": "double", - "description": "Latitude of the location [WGS 84](https://en.wikipedia.org/wiki/World_Geodetic_System)", - "type": "number" - }, - "longitude": { - "format": "double", - "description": "Longitude of the location [WGS 84](https://en.wikipedia.org/wiki/World_Geodetic_System)", - "type": "number" - }, - "type": { - "description": "The type of the thing", - "type": "string" - }, - "name": { - "description": "The name of the thing", - "type": "string" - } - } - }, - "Mention": { - "description": "Mention information (entity type: \"mention\")", - "type": "object", - "properties": { - "mentioned": { - "$ref": "#/definitions/ChannelAccount", - "description": "The mentioned user" - }, - "text": { - "description": "Sub Text which represents the mention (can be null or empty)", - "type": "string" - }, - "type": { - "description": "Entity Type (typically from schema.org types)", - "type": "string" - } - } - }, - "Place": { - "description": "Place (entity type: \"https://schema.org/Place\")", - "type": "object", - "properties": { - "address": { - "description": "Address of the place (may be `string` or complex object of type `PostalAddress`)", - "type": "object" - }, - "geo": { - "description": "Geo coordinates of the place (may be complex object of type `GeoCoordinates` or `GeoShape`)", - "type": "object" - }, - "hasMap": { - "description": "Map to the place (may be `string` (URL) or complex object of type `Map`)", - "type": "object" - }, - "type": { - "description": "The type of the thing", - "type": "string" - }, - "name": { - "description": "The name of the thing", - "type": "string" - } - } - }, - "Thing": { - "description": "Thing (entity type: \"https://schema.org/Thing\")", - "type": "object", - "properties": { - "type": { - "description": "The type of the thing", - "type": "string" - }, - "name": { - "description": "The name of the thing", - "type": "string" - } - } - }, - "MediaEventValue": { - "description": "Supplementary parameter for media events", - "type": "object", - "properties": { - "cardValue": { - "description": "Callback parameter specified in the Value field of the MediaCard that originated this event", - "type": "object" - } - } - }, - "TokenRequest": { - "description": "A request to receive a user token", - "type": "object", - "properties": { - "provider": { - "description": "The provider to request a user token from", - "type": "string" - }, - "settings": { - "description": "A collection of settings for the specific provider for this request", - "type": "object", - "additionalProperties": { - "type": "object" - } - } - } - }, - "TokenResponse": { - "description": "A response that includes a user token", - "type": "object", - "properties": { - "connectionName": { - "description": "The connection name", - "type": "string" - }, - "token": { - "description": "The user token", - "type": "string" - }, - "expiration": { - "description": "Expiration for the token, in ISO 8601 format (e.g. \"2007-04-05T14:30Z\")", - "type": "string" - } - } - }, - "ActivityTypes": { - "description": "Types of Activities", - "enum": [ - "message", - "contactRelationUpdate", - "conversationUpdate", - "typing", - "ping", - "endOfConversation", - "event", - "invoke", - "deleteUserData", - "messageUpdate", - "messageDelete", - "installationUpdate", - "messageReaction", - "suggestion", - "trace" - ], - "type": "string", - "properties": { - - }, - "x-ms-enum": { - "name": "ActivityTypes", - "modelAsString": true - } - }, - "AttachmentLayoutTypes": { - "description": "Attachment layout types", - "enum": [ - "list", - "carousel" - ], - "type": "string", - "properties": { - - }, - "x-ms-enum": { - "name": "AttachmentLayoutTypes", - "modelAsString": true - } - }, - "ActionTypes": { - "description": "Types of actions", - "enum": [ - "openUrl", - "imBack", - "postBack", - "playAudio", - "playVideo", - "showImage", - "downloadFile", - "signin", - "call", - "payment", - "messageBack" - ], - "type": "string", - "properties": { - - }, - "x-ms-enum": { - "name": "ActionTypes", - "modelAsString": true - } - }, - "ContactRelationUpdateActionTypes": { - "description": "Action types valid for ContactRelationUpdate activities", - "enum": [ - "add", - "remove" - ], - "type": "string", - "properties": { - - }, - "x-ms-enum": { - "name": "ContactRelationUpdateActionTypes", - "modelAsString": true - } - }, - "InstallationUpdateActionTypes": { - "description": "Action types valid for InstallationUpdate activities", - "enum": [ - "add", - "remove" - ], - "type": "string", - "properties": { - - }, - "x-ms-enum": { - "name": "InstallationUpdateActionTypes", - "modelAsString": true - } - }, - "MessageReactionTypes": { - "description": "Message reaction types", - "enum": [ - "like", - "plusOne" - ], - "type": "string", - "properties": { - - }, - "x-ms-enum": { - "name": "MessageReactionTypes", - "modelAsString": true - } - }, - "TextFormatTypes": { - "description": "Text format types", - "enum": [ - "markdown", - "plain", - "xml" - ], - "type": "string", - "properties": { - - }, - "x-ms-enum": { - "name": "TextFormatTypes", - "modelAsString": true - } - }, - "InputHints": { - "description": "Indicates whether the bot is accepting, expecting, or ignoring input", - "enum": [ - "acceptingInput", - "ignoringInput", - "expectingInput" - ], - "type": "string", - "properties": { - - }, - "x-ms-enum": { - "name": "InputHints", - "modelAsString": true - } - }, - "EndOfConversationCodes": { - "description": "Codes indicating why a conversation has ended", - "enum": [ - "unknown", - "completedSuccessfully", - "userCancelled", - "botTimedOut", - "botIssuedInvalidMessage", - "channelFailed" - ], - "type": "string", - "properties": { - - }, - "x-ms-enum": { - "name": "EndOfConversationCodes", - "modelAsString": true - } - }, - "ActivityImportance": { - "description": "Defines the importance of an Activity", - "enum": [ - "low", - "normal", - "high" - ], - "type": "string", - "properties": { - - }, - "x-ms-enum": { - "name": "ActivityImportance", - "modelAsString": true - } - }, - "RoleTypes": { - "enum": [ - "user", - "bot" - ], - "type": "string", - "properties": { - - }, - "x-ms-enum": { - "name": "RoleTypes", - "modelAsString": true - } - }, - "MicrosoftPayMethodData": { - "description": "W3C Payment Method Data for Microsoft Pay", - "type": "object", - "properties": { - "mechantId": { - "description": "Microsoft Pay Merchant ID", - "type": "string" - }, - "supportedNetworks": { - "description": "Supported payment networks (e.g., \"visa\" and \"mastercard\")", - "type": "array", - "items": { - "type": "string" - } - }, - "supportedTypes": { - "description": "Supported payment types (e.g., \"credit\")", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "PaymentAddress": { - "description": "Address within a Payment Request", - "type": "object", - "properties": { - "country": { - "description": "This is the CLDR (Common Locale Data Repository) region code. For example, US, GB, CN, or JP", - "type": "string" - }, - "addressLine": { - "description": "This is the most specific part of the address. It can include, for example, a street name, a house number, apartment number, a rural delivery route, descriptive instructions, or a post office box number.", - "type": "array", - "items": { - "type": "string" - } - }, - "region": { - "description": "This is the top level administrative subdivision of the country. For example, this can be a state, a province, an oblast, or a prefecture.", - "type": "string" - }, - "city": { - "description": "This is the city/town portion of the address.", - "type": "string" - }, - "dependentLocality": { - "description": "This is the dependent locality or sublocality within a city. For example, used for neighborhoods, boroughs, districts, or UK dependent localities.", - "type": "string" - }, - "postalCode": { - "description": "This is the postal code or ZIP code, also known as PIN code in India.", - "type": "string" - }, - "sortingCode": { - "description": "This is the sorting code as used in, for example, France.", - "type": "string" - }, - "languageCode": { - "description": "This is the BCP-47 language code for the address. It's used to determine the field separators and the order of fields when formatting the address for display.", - "type": "string" - }, - "organization": { - "description": "This is the organization, firm, company, or institution at this address.", - "type": "string" - }, - "recipient": { - "description": "This is the name of the recipient or contact person.", - "type": "string" - }, - "phone": { - "description": "This is the phone number of the recipient or contact person.", - "type": "string" - } - } - }, - "PaymentCurrencyAmount": { - "description": "Supplies monetary amounts", - "type": "object", - "properties": { - "currency": { - "description": "A currency identifier", - "type": "string" - }, - "value": { - "description": "Decimal monetary value", - "type": "string" - }, - "currencySystem": { - "description": "Currency system", - "type": "string" - } - } - }, - "PaymentDetails": { - "description": "Provides information about the requested transaction", - "type": "object", - "properties": { - "total": { - "$ref": "#/definitions/PaymentItem", - "description": "Contains the total amount of the payment request" - }, - "displayItems": { - "description": "Contains line items for the payment request that the user agent may display", - "type": "array", - "items": { - "$ref": "#/definitions/PaymentItem" - } - }, - "shippingOptions": { - "description": "A sequence containing the different shipping options for the user to choose from", - "type": "array", - "items": { - "$ref": "#/definitions/PaymentShippingOption" - } - }, - "modifiers": { - "description": "Contains modifiers for particular payment method identifiers", - "type": "array", - "items": { - "$ref": "#/definitions/PaymentDetailsModifier" - } - }, - "error": { - "description": "Error description", - "type": "string" - } - } - }, - "PaymentItem": { - "description": "Indicates what the payment request is for and the value asked for", - "type": "object", - "properties": { - "label": { - "description": "Human-readable description of the item", - "type": "string" - }, - "amount": { - "$ref": "#/definitions/PaymentCurrencyAmount", - "description": "Monetary amount for the item" - }, - "pending": { - "description": "When set to true this flag means that the amount field is not final.", - "type": "boolean" - } - } - }, - "PaymentShippingOption": { - "description": "Describes a shipping option", - "type": "object", - "properties": { - "id": { - "description": "String identifier used to reference this PaymentShippingOption", - "type": "string" - }, - "label": { - "description": "Human-readable description of the item", - "type": "string" - }, - "amount": { - "$ref": "#/definitions/PaymentCurrencyAmount", - "description": "Contains the monetary amount for the item" - }, - "selected": { - "description": "Indicates whether this is the default selected PaymentShippingOption", - "type": "boolean" - } - } - }, - "PaymentDetailsModifier": { - "description": "Provides details that modify the PaymentDetails based on payment method identifier", - "type": "object", - "properties": { - "supportedMethods": { - "description": "Contains a sequence of payment method identifiers", - "type": "array", - "items": { - "type": "string" - } - }, - "total": { - "$ref": "#/definitions/PaymentItem", - "description": "This value overrides the total field in the PaymentDetails dictionary for the payment method identifiers in the supportedMethods field" - }, - "additionalDisplayItems": { - "description": "Provides additional display items that are appended to the displayItems field in the PaymentDetails dictionary for the payment method identifiers in the supportedMethods field", - "type": "array", - "items": { - "$ref": "#/definitions/PaymentItem" - } - }, - "data": { - "description": "A JSON-serializable object that provides optional information that might be needed by the supported payment methods", - "type": "object" - } - } - }, - "PaymentMethodData": { - "description": "Indicates a set of supported payment methods and any associated payment method specific data for those methods", - "type": "object", - "properties": { - "supportedMethods": { - "description": "Required sequence of strings containing payment method identifiers for payment methods that the merchant web site accepts", - "type": "array", - "items": { - "type": "string" - } - }, - "data": { - "description": "A JSON-serializable object that provides optional information that might be needed by the supported payment methods", - "type": "object" - } - } - }, - "PaymentOptions": { - "description": "Provides information about the options desired for the payment request", - "type": "object", - "properties": { - "requestPayerName": { - "description": "Indicates whether the user agent should collect and return the payer's name as part of the payment request", - "type": "boolean" - }, - "requestPayerEmail": { - "description": "Indicates whether the user agent should collect and return the payer's email address as part of the payment request", - "type": "boolean" - }, - "requestPayerPhone": { - "description": "Indicates whether the user agent should collect and return the payer's phone number as part of the payment request", - "type": "boolean" - }, - "requestShipping": { - "description": "Indicates whether the user agent should collect and return a shipping address as part of the payment request", - "type": "boolean" - }, - "shippingType": { - "description": "If requestShipping is set to true, then the shippingType field may be used to influence the way the user agent presents the user interface for gathering the shipping address", - "type": "string" - } - } - }, - "PaymentRequest": { - "description": "A request to make a payment", - "type": "object", - "properties": { - "id": { - "description": "ID of this payment request", - "type": "string" - }, - "methodData": { - "description": "Allowed payment methods for this request", - "type": "array", - "items": { - "$ref": "#/definitions/PaymentMethodData" - } - }, - "details": { - "$ref": "#/definitions/PaymentDetails", - "description": "Details for this request" - }, - "options": { - "$ref": "#/definitions/PaymentOptions", - "description": "Provides information about the options desired for the payment request" - }, - "expires": { - "description": "Expiration for this request, in ISO 8601 duration format (e.g., 'P1D')", - "type": "string" - } - } - }, - "PaymentRequestComplete": { - "description": "Payload delivered when completing a payment request", - "type": "object", - "properties": { - "id": { - "description": "Payment request ID", - "type": "string" - }, - "paymentRequest": { - "$ref": "#/definitions/PaymentRequest", - "description": "Initial payment request" - }, - "paymentResponse": { - "$ref": "#/definitions/PaymentResponse", - "description": "Corresponding payment response" - } - } - }, - "PaymentResponse": { - "description": "A PaymentResponse is returned when a user has selected a payment method and approved a payment request", - "type": "object", - "properties": { - "methodName": { - "description": "The payment method identifier for the payment method that the user selected to fulfil the transaction", - "type": "string" - }, - "details": { - "description": "A JSON-serializable object that provides a payment method specific message used by the merchant to process the transaction and determine successful fund transfer", - "type": "object" - }, - "shippingAddress": { - "$ref": "#/definitions/PaymentAddress", - "description": "If the requestShipping flag was set to true in the PaymentOptions passed to the PaymentRequest constructor, then shippingAddress will be the full and final shipping address chosen by the user" - }, - "shippingOption": { - "description": "If the requestShipping flag was set to true in the PaymentOptions passed to the PaymentRequest constructor, then shippingOption will be the id attribute of the selected shipping option", - "type": "string" - }, - "payerEmail": { - "description": "If the requestPayerEmail flag was set to true in the PaymentOptions passed to the PaymentRequest constructor, then payerEmail will be the email address chosen by the user", - "type": "string" - }, - "payerPhone": { - "description": "If the requestPayerPhone flag was set to true in the PaymentOptions passed to the PaymentRequest constructor, then payerPhone will be the phone number chosen by the user", - "type": "string" - } - } - }, - "PaymentRequestCompleteResult": { - "description": "Result from a completed payment request", - "type": "object", - "properties": { - "result": { - "description": "Result of the payment request completion", - "type": "string" - } - } - }, - "PaymentRequestUpdate": { - "description": "An update to a payment request", - "type": "object", - "properties": { - "id": { - "description": "ID for the payment request to update", - "type": "string" - }, - "details": { - "$ref": "#/definitions/PaymentDetails", - "description": "Update payment details" - }, - "shippingAddress": { - "$ref": "#/definitions/PaymentAddress", - "description": "Updated shipping address" - }, - "shippingOption": { - "description": "Updated shipping options", - "type": "string" - } - } - }, - "PaymentRequestUpdateResult": { - "description": "A result object from a Payment Request Update invoke operation", - "type": "object", - "properties": { - "details": { - "$ref": "#/definitions/PaymentDetails", - "description": "Update payment details" - } - } - } - }, - "securityDefinitions": { - "bearer_auth": { - "type": "apiKey", - "description": "Access token to authenticate calls to the Bot Connector Service.", - "name": "Authorization", - "in": "header" - } - } -} \ No newline at end of file diff --git a/libraries/swagger/README.md b/libraries/swagger/README.md index 78ef9ab29..7a4820309 100644 --- a/libraries/swagger/README.md +++ b/libraries/swagger/README.md @@ -3,6 +3,8 @@ > see https://aka.ms/autorest Configuration for generating BotFramework Connector SDK. +NOTE: +The generated files have been modified by hand to overcome limitations. ``` yaml add-credentials: true @@ -35,4 +37,4 @@ directive: if( $['modelAsString'] ) { $['modelAsString'] = false; } -``` \ No newline at end of file +``` diff --git a/libraries/swagger/generateClient.cmd b/libraries/swagger/generateClient.cmd deleted file mode 100644 index e8c960b7d..000000000 --- a/libraries/swagger/generateClient.cmd +++ /dev/null @@ -1,17 +0,0 @@ -call npm install replace@0.3.0 - - -del /q ..\botbuilder-schema\src\main\java\com\microsoft\bot\schema\models\ - -call autorest .\README.md --java --add-credentials true - -robocopy .\generated\models ..\botbuilder-schema\src\main\java\com\microsoft\bot\schema\models *.* /move /xf *Exception.java - -call .\node_modules\.bin\replace "import com.microsoft.bot.schema.models.ErrorResponseException;" "import com.microsoft.bot.connector.models.ErrorResponseException;" . -r -q --include="*.java" -call .\node_modules\.bin\replace "import com.microsoft.bot.schema.ConnectorClient;" "import com.microsoft.bot.connector.ConnectorClient;" . -r -q --include="*.java" -call .\node_modules\.bin\replace "import com.microsoft.bot.schema.Attachments;" "import com.microsoft.bot.connector.Attachments;" . -r -q --include="*.java" -call .\node_modules\.bin\replace "import com.microsoft.bot.schema.Conversations;" "import com.microsoft.bot.connector.Conversations;" . -r -q --include="*.java" -call .\node_modules\.bin\replace "import com.microsoft.rest.RestException;" "import com.microsoft.rest.RestException;import com.microsoft.bot.schema.models.ErrorResponse;" . -r -q --include="ErrorResponseException.java" -call .\node_modules\.bin\replace "package com.microsoft.bot.schema" "package com.microsoft.bot.connector" . -r -q --include="*.java" - -robocopy .\generated ..\bot-connector\src\main\java\com\microsoft\bot\connector *.* /e /move