diff --git a/libraries/bot-dialogs/pom.xml b/libraries/bot-dialogs/pom.xml
index 94560c4bf..1357cbf29 100644
--- a/libraries/bot-dialogs/pom.xml
+++ b/libraries/bot-dialogs/pom.xml
@@ -70,7 +70,7 @@
com.microsoft.botbot-builder
- 4.6.0-preview8
+ ${project.version}com.microsoft.bot
diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java
index 6aced56e8..59974a94a 100644
--- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java
+++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java
@@ -7,6 +7,7 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.microsoft.bot.builder.BotTelemetryClient;
import com.microsoft.bot.builder.NullBotTelemetryClient;
+import com.microsoft.bot.builder.StatePropertyAccessor;
import com.microsoft.bot.builder.TurnContext;
import java.util.concurrent.CompletableFuture;
import org.apache.commons.lang3.StringUtils;
@@ -15,11 +16,13 @@
* Base class for all dialogs.
*/
public abstract class Dialog {
+
/**
- * A {@link DialogTurnResult} that indicates that the current dialog is
- * still active and waiting for input from the user next turn.
+ * A {@link DialogTurnResult} that indicates that the current dialog is still active and waiting
+ * for input from the user next turn.
*/
- public static final DialogTurnResult END_OF_TURN = new DialogTurnResult(DialogTurnStatus.WAITING);
+ public static final DialogTurnResult END_OF_TURN = new DialogTurnResult(
+ DialogTurnStatus.WAITING);
@JsonIgnore
private BotTelemetryClient telemetryClient;
@@ -29,6 +32,7 @@ public abstract class Dialog {
/**
* Initializes a new instance of the Dialog class.
+ *
* @param dialogId The ID to assign to this dialog.
*/
public Dialog(String dialogId) {
@@ -38,6 +42,7 @@ public Dialog(String dialogId) {
/**
* Gets id for the dialog.
+ *
* @return Id for the dialog.
*/
public String getId() {
@@ -49,6 +54,7 @@ public String getId() {
/**
* Sets id for the dialog.
+ *
* @param withId Id for the dialog.
*/
public void setId(String withId) {
@@ -57,6 +63,7 @@ public void setId(String withId) {
/**
* Gets the {@link BotTelemetryClient} to use for logging.
+ *
* @return The BotTelemetryClient to use for logging.
*/
public BotTelemetryClient getTelemetryClient() {
@@ -65,6 +72,7 @@ public BotTelemetryClient getTelemetryClient() {
/**
* Sets the {@link BotTelemetryClient} to use for logging.
+ *
* @param withTelemetryClient The BotTelemetryClient to use for logging.
*/
public void setTelemetryClient(BotTelemetryClient withTelemetryClient) {
@@ -74,10 +82,9 @@ public void setTelemetryClient(BotTelemetryClient withTelemetryClient) {
/**
* Called when the dialog is started and pushed onto the dialog stack.
*
- * @param dc The {@link DialogContext} for the current turn of
- * conversation.
- * @return If the task is successful, the result indicates whether the dialog is still
- * active after the turn has been processed by the dialog.
+ * @param dc The {@link DialogContext} for the current turn of conversation.
+ * @return If the task is successful, the result indicates whether the dialog is still active
+ * after the turn has been processed by the dialog.
*/
public CompletableFuture beginDialog(DialogContext dc) {
return beginDialog(dc, null);
@@ -86,25 +93,25 @@ public CompletableFuture beginDialog(DialogContext dc) {
/**
* Called when the dialog is started and pushed onto the dialog stack.
*
- * @param dc The {@link DialogContext} for the current turn of
- * conversation.
+ * @param dc The {@link DialogContext} for the current turn of conversation.
* @param options Initial information to pass to the dialog.
- * @return If the task is successful, the result indicates whether the dialog is still
- * active after the turn has been processed by the dialog.
+ * @return If the task is successful, the result indicates whether the dialog is still active
+ * after the turn has been processed by the dialog.
*/
- public abstract CompletableFuture beginDialog(DialogContext dc, Object options);
+ public abstract CompletableFuture beginDialog(
+ DialogContext dc, Object options
+ );
/**
- * Called when the dialog is _continued_, where it is the active dialog and the
- * user replies with a new activity.
+ * Called when the dialog is _continued_, where it is the active dialog and the user replies
+ * with a new activity.
*
- *
If this method is *not* overridden, the dialog automatically ends when the user replies.
+ *
If this method is *not* overridden, the dialog automatically ends when the user
+ * replies.
*
- * @param dc The {@link DialogContext} for the current turn of
- * conversation.
- * @return If the task is successful, the result indicates whether the dialog is still
- * active after the turn has been processed by the dialog. The result may also contain a
- * return value.
+ * @param dc The {@link DialogContext} for the current turn of conversation.
+ * @return If the task is successful, the result indicates whether the dialog is still active
+ * after the turn has been processed by the dialog. The result may also contain a return value.
*/
public CompletableFuture continueDialog(DialogContext dc) {
// By default just end the current dialog.
@@ -115,16 +122,17 @@ public CompletableFuture continueDialog(DialogContext dc) {
* Called when a child dialog completed this turn, returning control to this dialog.
*
*
Generally, the child dialog was started with a call to
- * {@link #beginDialog(DialogContext, Object)} However, if the
- * {@link DialogContext#replaceDialog(String)} method
- * is called, the logical child dialog may be different than the original.
+ * {@link #beginDialog(DialogContext, Object)} However, if the {@link
+ * DialogContext#replaceDialog(String)} method is called, the logical child dialog may be
+ * different than the original.
*
- *
If this method is *not* overridden, the dialog automatically ends when the user replies.
+ *
If this method is *not* overridden, the dialog automatically ends when the user
+ * replies.
*
- * @param dc The dialog context for the current turn of the conversation.
+ * @param dc The dialog context for the current turn of the conversation.
* @param reason Reason why the dialog resumed.
- * @return If the task is successful, the result indicates whether this dialog is still
- * active after this dialog turn has been processed.
+ * @return If the task is successful, the result indicates whether this dialog is still active
+ * after this dialog turn has been processed.
*/
public CompletableFuture resumeDialog(DialogContext dc, DialogReason reason) {
return resumeDialog(dc, reason, null);
@@ -134,43 +142,53 @@ public CompletableFuture resumeDialog(DialogContext dc, Dialog
* Called when a child dialog completed this turn, returning control to this dialog.
*
*
Generally, the child dialog was started with a call to
- * {@link #beginDialog(DialogContext, Object)} However, if the
- * {@link DialogContext#replaceDialog(String, Object)} method
- * is called, the logical child dialog may be different than the original.
+ * {@link #beginDialog(DialogContext, Object)} However, if the {@link
+ * DialogContext#replaceDialog(String, Object)} method is called, the logical child dialog may
+ * be different than the original.
*
- *
If this method is *not* overridden, the dialog automatically ends when the user replies.
+ *
If this method is *not* overridden, the dialog automatically ends when the user
+ * replies.
*
- * @param dc The dialog context for the current turn of the conversation.
+ * @param dc The dialog context for the current turn of the conversation.
* @param reason Reason why the dialog resumed.
- * @param result Optional, value returned from the dialog that was called. The type of the
- * value returned is dependent on the child dialog.
- * @return If the task is successful, the result indicates whether this dialog is still
- * active after this dialog turn has been processed.
+ * @param result Optional, value returned from the dialog that was called. The type of the value
+ * returned is dependent on the child dialog.
+ * @return If the task is successful, the result indicates whether this dialog is still active
+ * after this dialog turn has been processed.
*/
- public CompletableFuture resumeDialog(DialogContext dc, DialogReason reason, Object result) {
+ public CompletableFuture resumeDialog(
+ DialogContext dc, DialogReason reason, Object result
+ ) {
// By default just end the current dialog and return result to parent.
return dc.endDialog(result);
}
/**
* Called when the dialog should re-prompt the user for input.
+ *
* @param turnContext The context object for this turn.
- * @param instance State information for this dialog.
+ * @param instance State information for this dialog.
* @return A CompletableFuture representing the asynchronous operation.
*/
- public CompletableFuture repromptDialog(TurnContext turnContext, DialogInstance instance) {
+ public CompletableFuture repromptDialog(
+ TurnContext turnContext, DialogInstance instance
+ ) {
// No-op by default
return CompletableFuture.completedFuture(null);
}
/**
* Called when the dialog is ending.
+ *
* @param turnContext The context object for this turn.
- * @param instance State information associated with the instance of this dialog on the dialog stack.
- * @param reason Reason why the dialog ended.
+ * @param instance State information associated with the instance of this dialog on the
+ * dialog stack.
+ * @param reason Reason why the dialog ended.
* @return A CompletableFuture representing the asynchronous operation.
*/
- public CompletableFuture endDialog(TurnContext turnContext, DialogInstance instance, DialogReason reason) {
+ public CompletableFuture endDialog(
+ TurnContext turnContext, DialogInstance instance, DialogReason reason
+ ) {
// No-op by default
return CompletableFuture.completedFuture(null);
}
@@ -178,7 +196,9 @@ public CompletableFuture endDialog(TurnContext turnContext, DialogInstance
/**
* Gets a unique String which represents the version of this dialog. If the version changes
* between turns the dialog system will emit a DialogChanged event.
- * @return Unique String which should only change when dialog has changed in a way that should restart the dialog.
+ *
+ * @return Unique String which should only change when dialog has changed in a way that should
+ * restart the dialog.
*/
@JsonIgnore
public String getVersion() {
@@ -188,8 +208,9 @@ public String getVersion() {
/**
* Called when an event has been raised, using `DialogContext.emitEvent()`, by either the
* current dialog or a dialog that the current dialog started.
+ *
* @param dc The dialog context for the current turn of conversation.
- * @param e The event being raised.
+ * @param e The event being raised.
* @return True if the event is handled by the current dialog and bubbling should stop.
*/
public CompletableFuture onDialogEvent(DialogContext dc, DialogEvent e) {
@@ -222,8 +243,9 @@ public CompletableFuture onDialogEvent(DialogContext dc, DialogEvent e)
* dialogs from performing their default processing.
*
* @param dc The dialog context for the current turn of conversation.
- * @param e The event being raised.
- * @return Whether the event is handled by the current dialog and further processing should stop.
+ * @param e The event being raised.
+ * @return Whether the event is handled by the current dialog and further processing should
+ * stop.
*/
protected CompletableFuture onPreBubbleEvent(DialogContext dc, DialogEvent e) {
return CompletableFuture.completedFuture(false);
@@ -232,12 +254,14 @@ protected CompletableFuture onPreBubbleEvent(DialogContext dc, DialogEv
/**
* Called after an event was bubbled to all parents and wasn't handled.
*
- *
This is a good place to perform default processing logic for an event. Returning `true` will
+ *
This is a good place to perform default processing logic for an event. Returning `true`
+ * will
* prevent any processing of the event by child dialogs.
*
* @param dc The dialog context for the current turn of conversation.
- * @param e The event being raised.
- * @return Whether the event is handled by the current dialog and further processing should stop.
+ * @param e The event being raised.
+ * @return Whether the event is handled by the current dialog and further processing should
+ * stop.
*/
protected CompletableFuture onPostBubbleEvent(DialogContext dc, DialogEvent e) {
return CompletableFuture.completedFuture(false);
@@ -245,9 +269,46 @@ protected CompletableFuture onPostBubbleEvent(DialogContext dc, DialogE
/**
* Computes an id for the Dialog.
+ *
* @return The id.
*/
protected String onComputeId() {
return this.getClass().getName();
}
+
+ /**
+ * Creates a dialog stack and starts a dialog, pushing it onto the stack.
+ *
+ * @param dialog The dialog to start.
+ * @param turnContext The context for the current turn of the conversation.
+ * @param accessor The StatePropertyAccessor accessor with which to manage the state of the
+ * dialog stack.
+ * @return A Task representing the asynchronous operation.
+ */
+ public static CompletableFuture run(
+ Dialog dialog,
+ TurnContext turnContext,
+ StatePropertyAccessor accessor
+ ) {
+ DialogSet dialogSet = new DialogSet(accessor);
+ dialogSet.add(dialog);
+ dialogSet.setTelemetryClient(dialog.getTelemetryClient());
+
+ return dialogSet.createContext(turnContext)
+ .thenCompose(dialogContext -> continueOrStart(dialogContext, dialog))
+ .thenApply(result -> null);
+ }
+
+ private static CompletableFuture continueOrStart(
+ DialogContext dialogContext, Dialog dialog
+ ) {
+ return dialogContext.continueDialog()
+ .thenCompose(result -> {
+ if (result.getStatus() == DialogTurnStatus.EMPTY) {
+ return dialogContext.beginDialog(dialog.getId(), null);
+ }
+
+ return CompletableFuture.completedFuture(result);
+ });
+ }
}
diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java
index 3a0a51306..7416e814b 100644
--- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java
+++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java
@@ -200,7 +200,7 @@ public CompletableFuture beginDialog(String dialogId, Object o
// Look up dialog
Dialog dialog = findDialog(dialogId);
if (dialog == null) {
- Async.completeExceptionally(new Exception(String.format(
+ return Async.completeExceptionally(new Exception(String.format(
"DialogContext.beginDialog(): A dialog with an id of '%s' wasn't found."
+ " The dialog must be included in the current or parent DialogSet."
+ " For example, if subclassing a ComponentDialog you can call AddDialog()"
diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallDialog.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallDialog.java
index aedde17ba..1ac77aef5 100644
--- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallDialog.java
+++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallDialog.java
@@ -4,7 +4,6 @@
package com.microsoft.bot.dialogs;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -38,9 +37,9 @@ public class WaterfallDialog extends Dialog {
* @param dialogId The dialog ID.
* @param actions Optional actions to be defined by the caller.
*/
- public WaterfallDialog(String dialogId, Collection actions) {
+ public WaterfallDialog(String dialogId, List actions) {
super(dialogId);
- steps = actions != null ? new ArrayList(actions) : new ArrayList();
+ steps = actions != null ? actions : new ArrayList();
}
/**
@@ -126,7 +125,7 @@ public CompletableFuture continueDialog(DialogContext dc) {
}
// Don't do anything for non-message activities.
- if (dc.getContext().getActivity().getType() != ActivityTypes.MESSAGE) {
+ if (!dc.getContext().getActivity().isType(ActivityTypes.MESSAGE)) {
return CompletableFuture.completedFuture(END_OF_TURN);
}
diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceFactory.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceFactory.java
index 7e484021e..1971e3d96 100644
--- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceFactory.java
+++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceFactory.java
@@ -11,6 +11,7 @@
import com.microsoft.bot.schema.HeroCard;
import com.microsoft.bot.schema.InputHints;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
@@ -376,6 +377,16 @@ public static List toChoices(List choices) {
return choices == null ? new ArrayList<>() : choices.stream().map(Choice::new).collect(Collectors.toList());
}
+ /**
+ * Returns a list of strings as a list of Choices.
+ *
+ * @param choices The strings to convert.
+ * @return A List of Choices.
+ */
+ public static List toChoices(String... choices) {
+ return toChoices(Arrays.asList(choices));
+ }
+
private static List extractActions(List choices) {
if (choices == null) {
choices = new ArrayList<>();
diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/AttachmentPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/AttachmentPrompt.java
index 13fc79dbb..d7903050f 100644
--- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/AttachmentPrompt.java
+++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/AttachmentPrompt.java
@@ -105,7 +105,7 @@ protected CompletableFuture>> onRecogniz
}
PromptRecognizerResult> result = new PromptRecognizerResult>();
- if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) {
+ if (turnContext.getActivity().isType(ActivityTypes.MESSAGE)) {
Activity message = turnContext.getActivity();
if (message.getAttachments() != null && message.getAttachments().size() > 0) {
result.setSucceeded(true);
diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ChoicePrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ChoicePrompt.java
index 533ce5f58..9b8bb126f 100644
--- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ChoicePrompt.java
+++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ChoicePrompt.java
@@ -39,6 +39,15 @@ public class ChoicePrompt extends Prompt {
private FindChoicesOptions recognizerOptions;
private ChoiceFactoryOptions choiceOptions;
+ /**
+ * Initializes a new instance of the {@link ChoicePrompt} class.
+ *
+ * @param dialogId The ID to assign to this prompt.
+ */
+ public ChoicePrompt(String dialogId) {
+ this(dialogId, null, null);
+ }
+
/**
* Initializes a new instance of the {@link ChoicePrompt} class.
*
@@ -267,7 +276,7 @@ protected CompletableFuture> onRecognize(Tur
List choices = options.getChoices() != null ? options.getChoices() : new ArrayList();
PromptRecognizerResult result = new PromptRecognizerResult();
- if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) {
+ if (turnContext.getActivity().isType(ActivityTypes.MESSAGE)) {
Activity activity = turnContext.getActivity();
String utterance = activity.getText();
if (StringUtils.isEmpty(utterance)) {
diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java
index 2a317d17a..bb7afe798 100644
--- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java
+++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java
@@ -283,7 +283,7 @@ protected CompletableFuture> onRecognize(TurnCon
}
PromptRecognizerResult result = new PromptRecognizerResult();
- if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) {
+ if (turnContext.getActivity().isType(ActivityTypes.MESSAGE)) {
// Recognize utterance
String utterance = turnContext.getActivity().getText();
if (StringUtils.isBlank(utterance)) {
diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimePrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimePrompt.java
index 9e971c486..83cd57306 100644
--- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimePrompt.java
+++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimePrompt.java
@@ -136,7 +136,7 @@ protected CompletableFuture onPrompt(TurnContext turnContext, Map> result =
new PromptRecognizerResult>();
- if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) {
+ if (turnContext.getActivity().isType(ActivityTypes.MESSAGE)) {
String utterance = turnContext.getActivity().getText();
if (StringUtils.isEmpty(utterance)) {
return CompletableFuture.completedFuture(result);
diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/NumberPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/NumberPrompt.java
index 2446328f7..d95dd2be4 100644
--- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/NumberPrompt.java
+++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/NumberPrompt.java
@@ -7,8 +7,6 @@
import java.util.Map;
import java.util.concurrent.CompletableFuture;
-import javax.activation.UnsupportedDataTypeException;
-
import com.microsoft.bot.builder.TurnContext;
import com.microsoft.bot.connector.Async;
import com.microsoft.bot.schema.ActivityTypes;
@@ -37,13 +35,29 @@ public class NumberPrompt extends Prompt {
* {@link DialogSet} or {@link ComponentDialog} .
* @param classOfNumber Type of used to determine within the class what type was created for. This is required
* due to type erasure in Java not allowing checking the type of during runtime.
- * @throws UnsupportedDataTypeException thrown if a type other than int, long, float, or double are used for .
+ * @throws IllegalArgumentException thrown if a type other than int, long, float, or double are used for .
*/
public NumberPrompt(String dialogId, Class classOfNumber)
- throws UnsupportedDataTypeException {
+ throws IllegalArgumentException {
this(dialogId, null, null, classOfNumber);
}
+ /**
+ * Initializes a new instance of the {@link NumberPrompt{T}} class.
+ *
+ * @param dialogId Unique ID of the dialog within its parent
+ * {@link DialogSet} or {@link ComponentDialog} .
+ * @param validator Validator that will be called each time the user
+ * responds to the prompt.
+ * @param classOfNumber Type of used to determine within the class what type was created for. This is required
+ * due to type erasure in Java not allowing checking the type of during runtime.
+ * @throws IllegalArgumentException thrown if a type other than int, long, float, or double are used for .
+ */
+ public NumberPrompt(String dialogId, PromptValidator validator, Class classOfNumber)
+ throws IllegalArgumentException {
+ this(dialogId, validator, null, classOfNumber);
+ }
+
/**
* Initializes a new instance of the {@link NumberPrompt{T}} class.
*
@@ -54,18 +68,17 @@ public NumberPrompt(String dialogId, Class classOfNumber)
* @param defaultLocale Locale to use.
* @param classOfNumber Type of used to determine within the class what type was created for. This is required
* due to type erasure in Java not allowing checking the type of during runtime.
- * @throws UnsupportedDataTypeException thrown if a type other than int, long, float, or double are used for .
+ * @throws IllegalArgumentException thrown if a type other than int, long, float, or double are used for .
*/
public NumberPrompt(String dialogId, PromptValidator validator, String defaultLocale, Class classOfNumber)
- throws UnsupportedDataTypeException {
-
+ throws IllegalArgumentException {
super(dialogId, validator);
this.defaultLocale = defaultLocale;
this.classOfNumber = classOfNumber;
if (!(classOfNumber.getSimpleName().equals("Long") || classOfNumber.getSimpleName().equals("Integer")
|| classOfNumber.getSimpleName().equals("Float") || classOfNumber.getSimpleName().equals("Double"))) {
- throw new UnsupportedDataTypeException(String.format("NumberPrompt: Type argument %s is not supported",
+ throw new IllegalArgumentException(String.format("NumberPrompt: Type argument %s is not supported",
classOfNumber.getSimpleName()));
}
}
@@ -160,7 +173,7 @@ protected CompletableFuture> onRecognize(TurnContext t
}
PromptRecognizerResult result = new PromptRecognizerResult();
- if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) {
+ if (turnContext.getActivity().isType(ActivityTypes.MESSAGE)) {
String utterance = turnContext.getActivity().getText();
if (StringUtils.isEmpty(utterance)) {
return CompletableFuture.completedFuture(result);
diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java
index b4130d560..aced3a4ab 100644
--- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java
+++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java
@@ -151,7 +151,7 @@ public CompletableFuture continueDialog(DialogContext dc) {
}
// Don't do anything for non-message activities
- if (dc.getContext().getActivity().getType() != ActivityTypes.MESSAGE) {
+ if (!dc.getContext().getActivity().isType(ActivityTypes.MESSAGE)) {
return CompletableFuture.completedFuture(Dialog.END_OF_TURN);
}
@@ -248,7 +248,7 @@ public CompletableFuture repromptDialog(TurnContext turnContext, DialogIns
@Override
protected CompletableFuture onPreBubbleEvent(DialogContext dc, DialogEvent e) {
if (e.getName() == DialogEvents.ACTIVITY_RECEIVED
- && dc.getContext().getActivity().getType() == ActivityTypes.MESSAGE) {
+ && dc.getContext().getActivity().isType(ActivityTypes.MESSAGE)) {
// Perform base recognition
Map state = dc.getActiveDialog().getState();
PromptRecognizerResult recognized = onRecognize(dc.getContext(),
diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/TextPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/TextPrompt.java
index a26c9b2e5..20869b2a7 100644
--- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/TextPrompt.java
+++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/TextPrompt.java
@@ -104,7 +104,7 @@ protected CompletableFuture> onRecognize(TurnCont
}
PromptRecognizerResult result = new PromptRecognizerResult();
- if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) {
+ if (turnContext.getActivity().isType(ActivityTypes.MESSAGE)) {
Activity message = turnContext.getActivity();
if (message.getText() != null) {
result.setSucceeded(true);
diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ComponentDialogTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ComponentDialogTests.java
index 2b125f4f3..2a0bb2a98 100644
--- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ComponentDialogTests.java
+++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ComponentDialogTests.java
@@ -114,8 +114,8 @@ public void BasicWaterfallTest() throws UnsupportedDataTypeException {
dialogs.add(createWaterfall());
try {
dialogs.add(new NumberPrompt("number", Integer.class));
- } catch (UnsupportedDataTypeException e) {
- e.printStackTrace();
+ } catch (Throwable t) {
+ t.printStackTrace();
}
DialogContext dc = dialogs.createContext(turnContext).join();
diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/WaterfallTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/WaterfallTests.java
index 188f1a2cb..c9944e5fc 100644
--- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/WaterfallTests.java
+++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/WaterfallTests.java
@@ -220,9 +220,9 @@ public void WaterfallPrompt() throws UnsupportedDataTypeException{
try {
numberPrompt = new NumberPrompt("number", null, PromptCultureModels.ENGLISH_CULTURE,
Integer.class);
- } catch (UnsupportedDataTypeException e) {
+ } catch (Throwable t) {
// TODO Auto-generated catch block
- e.printStackTrace();
+ t.printStackTrace();
}
dialogs.add(numberPrompt);
diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptTests.java
index 3ea7723a0..a8939f2a5 100644
--- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptTests.java
+++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptTests.java
@@ -45,7 +45,7 @@ public void NumberPromptWithNullIdShouldFail() {
@Test
public void NumberPromptWithUnsupportedTypeShouldFail() {
- Assert.assertThrows(UnsupportedDataTypeException.class, () -> new NumberPrompt("prompt", Short.class));
+ Assert.assertThrows(IllegalArgumentException.class, () -> new NumberPrompt("prompt", Short.class));
}
@Test
diff --git a/samples/05.multi-turn-prompt/LICENSE b/samples/05.multi-turn-prompt/LICENSE
new file mode 100644
index 000000000..21071075c
--- /dev/null
+++ b/samples/05.multi-turn-prompt/LICENSE
@@ -0,0 +1,21 @@
+ MIT License
+
+ Copyright (c) Microsoft Corporation. All rights reserved.
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE
diff --git a/samples/05.multi-turn-prompt/README.md b/samples/05.multi-turn-prompt/README.md
new file mode 100644
index 000000000..e4375c425
--- /dev/null
+++ b/samples/05.multi-turn-prompt/README.md
@@ -0,0 +1,90 @@
+# Multi-turn prompt
+
+Bot Framework v4 multi-turn prompt bot sample
+
+This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to use the prompts classes included in `botbuilder-dialogs`. This bot will ask for the user's name and age, then store the responses. It demonstrates a multi-turn dialog flow using a text prompt, a number prompt, and state accessors to store and retrieve values.
+
+## Prerequisites
+
+- Java 1.8+
+- Install [Maven](https://maven.apache.org/)
+- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure.
+
+## To try this sample locally
+- From the root of this project folder:
+ - Build the sample using `mvn package`
+ - Run it by using `java -jar .\target\bot-multiturnprompt-sample.jar`
+
+- Test the bot using Bot Framework Emulator
+
+ [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel.
+
+ - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases)
+
+ - Connect to the bot using Bot Framework Emulator
+
+ - Launch Bot Framework Emulator
+ - File -> Open Bot
+ - Enter a Bot URL of `http://localhost:3978/api/messages`
+
+## Deploy the bot to Azure
+
+As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin.
+
+### 1. Login to Azure
+From a command (or PowerShell) prompt in the root of the bot folder, execute:
+`az login`
+
+### 2. Set the subscription
+`az account set --subscription ""`
+
+If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command.
+
+### 3. Create an App registration
+`az ad app create --display-name "" --password "" --available-to-other-tenants`
+
+Replace `` and `` with your own values.
+
+`` is the unique name of your bot.
+`` is a minimum 16 character password for your bot.
+
+Record the `appid` from the returned JSON
+
+### 4. Create the Azure resources
+Replace the values for ``, ``, ``, and `` in the following commands:
+
+#### To a new Resource Group
+`az deployment create --name "stateBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""`
+
+#### To an existing Resource Group
+`az group deployment create --name "stateBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""`
+
+### 5. Update app id and password
+In src/main/resources/application.properties update
+ - `MicrosoftAppPassword` with the botsecret value
+ - `MicrosoftAppId` with the appid from the first step
+
+### 6. Deploy the code
+- Execute `mvn clean package`
+- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""`
+
+If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot.
+
+After the bot is deployed, you only need to execute #6 if you make changes to the bot.
+
+
+## Further reading
+
+- [Bot Framework Documentation](https://docs.botframework.com)
+- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0)
+- [Dialogs](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0)
+- [Gathering Input Using Prompts](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-prompts?view=azure-bot-service-4.0&tabs=csharp)
+- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0)
+- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0)
+- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0)
+- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest)
+- [Azure Portal](https://portal.azure.com)
+- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0)
+- [Maven Plugin for Azure App Service](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable)
+- [Spring Boot](https://spring.io/projects/spring-boot)
+- [Azure for Java cloud developers](https://docs.microsoft.com/en-us/azure/java/?view=azure-java-stable)
diff --git a/samples/05.multi-turn-prompt/deploymentTemplates/new-rg-parameters.json b/samples/05.multi-turn-prompt/deploymentTemplates/new-rg-parameters.json
new file mode 100644
index 000000000..ead339093
--- /dev/null
+++ b/samples/05.multi-turn-prompt/deploymentTemplates/new-rg-parameters.json
@@ -0,0 +1,42 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "groupLocation": {
+ "value": ""
+ },
+ "groupName": {
+ "value": ""
+ },
+ "appId": {
+ "value": ""
+ },
+ "appSecret": {
+ "value": ""
+ },
+ "botId": {
+ "value": ""
+ },
+ "botSku": {
+ "value": ""
+ },
+ "newAppServicePlanName": {
+ "value": ""
+ },
+ "newAppServicePlanSku": {
+ "value": {
+ "name": "S1",
+ "tier": "Standard",
+ "size": "S1",
+ "family": "S",
+ "capacity": 1
+ }
+ },
+ "newAppServicePlanLocation": {
+ "value": ""
+ },
+ "newWebAppName": {
+ "value": ""
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/05.multi-turn-prompt/deploymentTemplates/preexisting-rg-parameters.json b/samples/05.multi-turn-prompt/deploymentTemplates/preexisting-rg-parameters.json
new file mode 100644
index 000000000..b6f5114fc
--- /dev/null
+++ b/samples/05.multi-turn-prompt/deploymentTemplates/preexisting-rg-parameters.json
@@ -0,0 +1,39 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "appId": {
+ "value": ""
+ },
+ "appSecret": {
+ "value": ""
+ },
+ "botId": {
+ "value": ""
+ },
+ "botSku": {
+ "value": ""
+ },
+ "newAppServicePlanName": {
+ "value": ""
+ },
+ "newAppServicePlanSku": {
+ "value": {
+ "name": "S1",
+ "tier": "Standard",
+ "size": "S1",
+ "family": "S",
+ "capacity": 1
+ }
+ },
+ "appServicePlanLocation": {
+ "value": ""
+ },
+ "existingAppServicePlan": {
+ "value": ""
+ },
+ "newWebAppName": {
+ "value": ""
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/05.multi-turn-prompt/deploymentTemplates/template-with-new-rg.json b/samples/05.multi-turn-prompt/deploymentTemplates/template-with-new-rg.json
new file mode 100644
index 000000000..dcd6260a5
--- /dev/null
+++ b/samples/05.multi-turn-prompt/deploymentTemplates/template-with-new-rg.json
@@ -0,0 +1,191 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "groupLocation": {
+ "defaultValue": "",
+ "type": "string",
+ "metadata": {
+ "description": "Specifies the location of the Resource Group."
+ }
+ },
+ "groupName": {
+ "type": "string",
+ "metadata": {
+ "description": "Specifies the name of the Resource Group."
+ }
+ },
+ "appId": {
+ "type": "string",
+ "metadata": {
+ "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings."
+ }
+ },
+ "appSecret": {
+ "type": "string",
+ "metadata": {
+ "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings."
+ }
+ },
+ "botId": {
+ "type": "string",
+ "metadata": {
+ "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable."
+ }
+ },
+ "botSku": {
+ "defaultValue": "F0",
+ "type": "string",
+ "metadata": {
+ "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1."
+ }
+ },
+ "newAppServicePlanName": {
+ "defaultValue": "",
+ "type": "string",
+ "metadata": {
+ "description": "The name of the App Service Plan."
+ }
+ },
+ "newAppServicePlanSku": {
+ "type": "object",
+ "defaultValue": {
+ "name": "P1v2",
+ "tier": "PremiumV2",
+ "size": "P1v2",
+ "family": "Pv2",
+ "capacity": 1
+ },
+ "metadata": {
+ "description": "The SKU of the App Service Plan. Defaults to Standard values."
+ }
+ },
+ "newAppServicePlanLocation": {
+ "defaultValue": "",
+ "type": "string",
+ "metadata": {
+ "description": "The location of the App Service Plan. Defaults to \"westus\"."
+ }
+ },
+ "newWebAppName": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"."
+ }
+ }
+ },
+ "variables": {
+ "resourcesLocation": "[deployment().location]",
+ "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]",
+ "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]",
+ "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]",
+ "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]",
+ "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]",
+ "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]"
+ },
+ "resources": [
+ {
+ "name": "[parameters('groupName')]",
+ "type": "Microsoft.Resources/resourceGroups",
+ "apiVersion": "2018-05-01",
+ "location": "[variables('effectiveGroupLocation')]",
+ "properties": {
+ }
+ },
+ {
+ "type": "Microsoft.Resources/deployments",
+ "apiVersion": "2018-05-01",
+ "name": "storageDeployment",
+ "resourceGroup": "[parameters('groupName')]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]"
+ ],
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {},
+ "variables": {},
+ "resources": [
+ {
+ "comments": "Create a new App Service Plan",
+ "type": "Microsoft.Web/serverfarms",
+ "name": "[variables('appServicePlanName')]",
+ "apiVersion": "2018-02-01",
+ "location": "[variables('effectivePlanLocation')]",
+ "sku": "[parameters('newAppServicePlanSku')]",
+ "kind": "linux",
+ "properties": {
+ "name": "[variables('appServicePlanName')]",
+ "reserved":true
+ }
+ },
+ {
+ "comments": "Create a Web App using the new App Service Plan",
+ "type": "Microsoft.Web/sites",
+ "apiVersion": "2015-08-01",
+ "location": "[variables('resourcesLocation')]",
+ "kind": "app",
+ "dependsOn": [
+ "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]"
+ ],
+ "name": "[variables('webAppName')]",
+ "properties": {
+ "name": "[variables('webAppName')]",
+ "serverFarmId": "[variables('appServicePlanName')]",
+ "siteConfig": {
+ "appSettings": [
+ {
+ "name": "JAVA_OPTS",
+ "value": "-Dserver.port=80"
+ },
+ {
+ "name": "MicrosoftAppId",
+ "value": "[parameters('appId')]"
+ },
+ {
+ "name": "MicrosoftAppPassword",
+ "value": "[parameters('appSecret')]"
+ }
+ ],
+ "cors": {
+ "allowedOrigins": [
+ "https://botservice.hosting.portal.azure.net",
+ "https://hosting.onecloud.azure-test.net/"
+ ]
+ }
+ }
+ }
+ },
+ {
+ "apiVersion": "2017-12-01",
+ "type": "Microsoft.BotService/botServices",
+ "name": "[parameters('botId')]",
+ "location": "global",
+ "kind": "bot",
+ "sku": {
+ "name": "[parameters('botSku')]"
+ },
+ "properties": {
+ "name": "[parameters('botId')]",
+ "displayName": "[parameters('botId')]",
+ "endpoint": "[variables('botEndpoint')]",
+ "msaAppId": "[parameters('appId')]",
+ "developerAppInsightsApplicationId": null,
+ "developerAppInsightKey": null,
+ "publishingCredentials": null,
+ "storageResourceId": null
+ },
+ "dependsOn": [
+ "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]"
+ ]
+ }
+ ],
+ "outputs": {}
+ }
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/samples/05.multi-turn-prompt/deploymentTemplates/template-with-preexisting-rg.json b/samples/05.multi-turn-prompt/deploymentTemplates/template-with-preexisting-rg.json
new file mode 100644
index 000000000..b790d2bdc
--- /dev/null
+++ b/samples/05.multi-turn-prompt/deploymentTemplates/template-with-preexisting-rg.json
@@ -0,0 +1,158 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "appId": {
+ "type": "string",
+ "metadata": {
+ "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings."
+ }
+ },
+ "appSecret": {
+ "type": "string",
+ "metadata": {
+ "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"."
+ }
+ },
+ "botId": {
+ "type": "string",
+ "metadata": {
+ "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable."
+ }
+ },
+ "botSku": {
+ "defaultValue": "S1",
+ "type": "string",
+ "metadata": {
+ "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1."
+ }
+ },
+ "newAppServicePlanName": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "The name of the new App Service Plan."
+ }
+ },
+ "newAppServicePlanSku": {
+ "type": "object",
+ "defaultValue": {
+ "name": "P1v2",
+ "tier": "PremiumV2",
+ "size": "P1v2",
+ "family": "Pv2",
+ "capacity": 1
+ },
+ "metadata": {
+ "description": "The SKU of the App Service Plan. Defaults to Standard values."
+ }
+ },
+ "appServicePlanLocation": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "The location of the App Service Plan."
+ }
+ },
+ "existingAppServicePlan": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "Name of the existing App Service Plan used to create the Web App for the bot."
+ }
+ },
+ "newWebAppName": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"."
+ }
+ }
+ },
+ "variables": {
+ "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]",
+ "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]",
+ "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]",
+ "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]",
+ "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]",
+ "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]",
+ "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]"
+ },
+ "resources": [
+ {
+ "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.",
+ "type": "Microsoft.Web/serverfarms",
+ "condition": "[not(variables('useExistingAppServicePlan'))]",
+ "name": "[variables('servicePlanName')]",
+ "apiVersion": "2018-02-01",
+ "location": "[variables('resourcesLocation')]",
+ "sku": "[parameters('newAppServicePlanSku')]",
+ "kind": "linux",
+ "properties": {
+ "name": "[variables('servicePlanName')]",
+ "reserved":true
+ }
+ },
+ {
+ "comments": "Create a Web App using an App Service Plan",
+ "type": "Microsoft.Web/sites",
+ "apiVersion": "2016-08-01",
+ "location": "[variables('resourcesLocation')]",
+ "kind": "app",
+ "dependsOn": [
+ "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]"
+ ],
+ "name": "[variables('webAppName')]",
+ "properties": {
+ "name": "[variables('webAppName')]",
+ "serverFarmId": "[variables('servicePlanName')]",
+ "siteConfig": {
+ "linuxFxVersion": "JAVA|8-jre8",
+ "appSettings": [
+ {
+ "name": "JAVA_OPTS",
+ "value": "-Dserver.port=80"
+ },
+ {
+ "name": "MicrosoftAppId",
+ "value": "[parameters('appId')]"
+ },
+ {
+ "name": "MicrosoftAppPassword",
+ "value": "[parameters('appSecret')]"
+ }
+ ],
+ "cors": {
+ "allowedOrigins": [
+ "https://botservice.hosting.portal.azure.net",
+ "https://hosting.onecloud.azure-test.net/"
+ ]
+ }
+ }
+ }
+ },
+ {
+ "apiVersion": "2017-12-01",
+ "type": "Microsoft.BotService/botServices",
+ "name": "[parameters('botId')]",
+ "location": "global",
+ "kind": "bot",
+ "sku": {
+ "name": "[parameters('botSku')]"
+ },
+ "properties": {
+ "name": "[parameters('botId')]",
+ "displayName": "[parameters('botId')]",
+ "endpoint": "[variables('botEndpoint')]",
+ "msaAppId": "[parameters('appId')]",
+ "developerAppInsightsApplicationId": null,
+ "developerAppInsightKey": null,
+ "publishingCredentials": null,
+ "storageResourceId": null
+ },
+ "dependsOn": [
+ "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/samples/05.multi-turn-prompt/pom.xml b/samples/05.multi-turn-prompt/pom.xml
new file mode 100644
index 000000000..7dfbebe83
--- /dev/null
+++ b/samples/05.multi-turn-prompt/pom.xml
@@ -0,0 +1,244 @@
+
+
+
+ 4.0.0
+
+ com.microsoft.bot.sample
+ bot-multiturnprompt
+ sample
+ jar
+
+ ${project.groupId}:${project.artifactId}
+ This package contains the Multi-turn Prompt sample using Spring Boot.
+ http://maven.apache.org
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.4.0
+
+
+
+
+
+ MIT License
+ http://www.opensource.org/licenses/mit-license.php
+
+
+
+
+
+ Bot Framework Development
+
+ Microsoft
+ https://dev.botframework.com/
+
+
+
+
+ 1.8
+ 1.8
+ 1.8
+ com.microsoft.bot.sample.multiturnprompt.Application
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ 2.4.0
+ test
+
+
+ junit
+ junit
+ 4.13.1
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+ test
+
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.apache.logging.log4j
+ log4j-api
+ 2.11.0
+
+
+ org.apache.logging.log4j
+ log4j-core
+ 2.13.2
+
+
+
+ com.microsoft.bot
+ bot-integration-spring
+ 4.6.0-preview8
+ compile
+
+
+ com.microsoft.bot
+ bot-dialogs
+ 4.6.0-preview8
+ compile
+
+
+
+
+
+ build
+
+ true
+
+
+
+
+ src/main/resources
+ false
+
+
+
+
+ maven-compiler-plugin
+ 3.8.1
+
+
+ maven-war-plugin
+ 3.2.3
+
+ src/main/webapp
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ repackage
+
+
+ com.microsoft.bot.sample.multiturnprompt.Application
+
+
+
+
+
+ com.microsoft.azure
+ azure-webapp-maven-plugin
+ 1.7.0
+
+ V2
+ ${groupname}
+ ${botname}
+
+
+ JAVA_OPTS
+ -Dserver.port=80
+
+
+
+ linux
+ jre8
+ jre8
+
+
+
+
+ ${project.basedir}/target
+
+ *.jar
+
+
+
+
+
+
+
+
+
+
+
+ publish
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ maven-war-plugin
+ 3.2.3
+
+ src/main/webapp
+
+
+
+
+ org.sonatype.plugins
+ nexus-staging-maven-plugin
+ 1.6.8
+ true
+
+ true
+ ossrh
+ https://oss.sonatype.org/
+ true
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+
+
+ attach-sources
+
+ jar
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+ 8
+ false
+
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+
+
+
+
diff --git a/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/Application.java b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/Application.java
new file mode 100644
index 000000000..1324a2348
--- /dev/null
+++ b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/Application.java
@@ -0,0 +1,48 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.microsoft.bot.sample.multiturnprompt;
+
+import com.microsoft.bot.integration.AdapterWithErrorHandler;
+import com.microsoft.bot.integration.BotFrameworkHttpAdapter;
+import com.microsoft.bot.integration.Configuration;
+import com.microsoft.bot.integration.spring.BotController;
+import com.microsoft.bot.integration.spring.BotDependencyConfiguration;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Import;
+
+/**
+ * This is the starting point of the Sprint Boot Bot application.
+ *
+ * This class also provides overrides for dependency injections. A class that
+ * extends the {@link com.microsoft.bot.builder.Bot} interface should be
+ * annotated with @Component.
+ *
+ * @see DialogBot
+ */
+@SpringBootApplication
+
+// Use the default BotController to receive incoming Channel messages. A custom
+// controller could be used by eliminating this import and creating a new
+// RestController.
+// The default controller is created by the Spring Boot container using
+// dependency injection. The default route is /api/messages.
+@Import({BotController.class})
+
+public class Application extends BotDependencyConfiguration {
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+ /**
+ * Returns a custom Adapter that provides error handling.
+ *
+ * @param configuration The Configuration object to use.
+ * @return An error handling BotFrameworkHttpAdapter.
+ */
+ @Override
+ public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) {
+ return new AdapterWithErrorHandler(configuration);
+ }
+}
diff --git a/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/DialogBot.java b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/DialogBot.java
new file mode 100644
index 000000000..d71d53ed6
--- /dev/null
+++ b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/DialogBot.java
@@ -0,0 +1,55 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.microsoft.bot.sample.multiturnprompt;
+
+import com.microsoft.bot.builder.ActivityHandler;
+import com.microsoft.bot.builder.BotState;
+import com.microsoft.bot.builder.ConversationState;
+import com.microsoft.bot.dialogs.Dialog;
+import com.microsoft.bot.builder.TurnContext;
+import com.microsoft.bot.builder.UserState;
+import java.util.concurrent.CompletableFuture;
+import org.springframework.stereotype.Component;
+
+/**
+ * This IBot implementation can run any type of Dialog. The use of type parameterization is to
+ * allows multiple different bots to be run at different endpoints within the same project. This
+ * can be achieved by defining distinct Controller types each with dependency on distinct IBot
+ * types, this way ASP Dependency Injection can glue everything together without ambiguity. The
+ * ConversationState is used by the Dialog system. The UserState isn't, however, it might have
+ * been used in a Dialog implementation, and the requirement is that all BotState objects are
+ * saved at the end of a turn.
+ */
+@Component
+public class DialogBot extends ActivityHandler {
+ protected Dialog dialog;
+ protected BotState conversationState;
+ protected BotState userState;
+
+ public DialogBot(
+ ConversationState withConversationState,
+ UserState withUserState,
+ Dialog withDialog
+ ) {
+ dialog = withDialog;
+ conversationState = withConversationState;
+ userState = withUserState;
+ }
+
+ @Override
+ public CompletableFuture onTurn(
+ TurnContext turnContext
+ ) {
+ return super.onTurn(turnContext)
+ .thenCompose(result -> conversationState.saveChanges(turnContext))
+ .thenCompose(result -> userState.saveChanges(turnContext));
+ }
+
+ @Override
+ protected CompletableFuture onMessageActivity(
+ TurnContext turnContext
+ ) {
+ return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState"));
+ }
+}
diff --git a/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfile.java b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfile.java
new file mode 100644
index 000000000..a04f2f72b
--- /dev/null
+++ b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfile.java
@@ -0,0 +1,13 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.microsoft.bot.sample.multiturnprompt;
+
+import com.microsoft.bot.schema.Attachment;
+
+public class UserProfile {
+ public String transport;
+ public String name;
+ public Integer age;
+ public Attachment picture;
+}
diff --git a/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfileDialog.java b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfileDialog.java
new file mode 100644
index 000000000..302fe5d3a
--- /dev/null
+++ b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfileDialog.java
@@ -0,0 +1,224 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.microsoft.bot.sample.multiturnprompt;
+
+import com.microsoft.bot.builder.MessageFactory;
+import com.microsoft.bot.builder.StatePropertyAccessor;
+import com.microsoft.bot.builder.UserState;
+import com.microsoft.bot.connector.Channels;
+import com.microsoft.bot.dialogs.ComponentDialog;
+import com.microsoft.bot.dialogs.DialogTurnResult;
+import com.microsoft.bot.dialogs.WaterfallDialog;
+import com.microsoft.bot.dialogs.WaterfallStep;
+import com.microsoft.bot.dialogs.WaterfallStepContext;
+import com.microsoft.bot.dialogs.choices.ChoiceFactory;
+import com.microsoft.bot.dialogs.choices.FoundChoice;
+import com.microsoft.bot.dialogs.prompts.AttachmentPrompt;
+import com.microsoft.bot.dialogs.prompts.ChoicePrompt;
+import com.microsoft.bot.dialogs.prompts.ConfirmPrompt;
+import com.microsoft.bot.dialogs.prompts.NumberPrompt;
+import com.microsoft.bot.dialogs.prompts.PromptOptions;
+import com.microsoft.bot.dialogs.prompts.PromptValidatorContext;
+import com.microsoft.bot.dialogs.prompts.TextPrompt;
+import com.microsoft.bot.schema.Attachment;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+
+@Component
+public class UserProfileDialog extends ComponentDialog {
+ private StatePropertyAccessor userProfileAccessor;
+
+ public UserProfileDialog(UserState withUserState) {
+ super("UserProfileDialog");
+
+ userProfileAccessor = withUserState.createProperty("UserProfile");
+
+ WaterfallStep[] waterfallSteps = {
+ UserProfileDialog::transportStep,
+ UserProfileDialog::nameStep,
+ UserProfileDialog::nameConfirmStep,
+ UserProfileDialog::ageStep,
+ UserProfileDialog::pictureStep,
+ UserProfileDialog::confirmStep,
+ this::summaryStep
+ };
+
+ // Add named dialogs to the DialogSet. These names are saved in the dialog state.
+ addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps)));
+ addDialog(new TextPrompt("TextPrompt"));
+ addDialog(new NumberPrompt("NumberPrompt", UserProfileDialog::agePromptValidator, Integer.class));
+ addDialog(new ChoicePrompt("ChoicePrompt"));
+ addDialog(new ConfirmPrompt("ConfirmPrompt"));
+ addDialog(new AttachmentPrompt("AttachmentPrompt", UserProfileDialog::picturePromptValidator));
+
+ // The initial child Dialog to run.
+ setInitialDialogId("WaterfallDialog");
+ }
+
+ private static CompletableFuture transportStep(WaterfallStepContext stepContext) {
+ // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
+ // Running a prompt here means the next WaterfallStep will be run when the user's response is received.
+ PromptOptions promptOptions = new PromptOptions();
+ promptOptions.setPrompt(MessageFactory.text("Please enter your mode of transport."));
+ promptOptions.setChoices(ChoiceFactory.toChoices("Car", "Bus", "Bicycle"));
+
+ return stepContext.prompt("ChoicePrompt", promptOptions);
+ }
+
+ private static CompletableFuture nameStep(WaterfallStepContext stepContext) {
+ stepContext.getValues().put("transport", ((FoundChoice) stepContext.getResult()).getValue());
+
+ PromptOptions promptOptions = new PromptOptions();
+ promptOptions.setPrompt(MessageFactory.text("Please enter your name."));
+ return stepContext.prompt("TextPrompt", promptOptions);
+ }
+
+ private static CompletableFuture nameConfirmStep(WaterfallStepContext stepContext) {
+ stepContext.getValues().put("name", stepContext.getResult());
+
+ // We can send messages to the user at any point in the WaterfallStep.
+ return stepContext.getContext().sendActivity(MessageFactory.text(String.format("Thanks %s", stepContext.getResult())))
+ .thenCompose(resourceResponse -> {
+ // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
+ PromptOptions promptOptions = new PromptOptions();
+ promptOptions.setPrompt(MessageFactory.text("Would you like to give your age?"));
+ return stepContext.prompt("ConfirmPrompt", promptOptions);
+ });
+ }
+
+ private static CompletableFuture ageStep(WaterfallStepContext stepContext) {
+ if ((Boolean)stepContext.getResult()) {
+ // User said "yes" so we will be prompting for the age.
+ // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
+ PromptOptions promptOptions = new PromptOptions();
+ promptOptions.setPrompt(MessageFactory.text("Please enter your age."));
+ promptOptions.setRetryPrompt(MessageFactory.text("The value entered must be greater than 0 and less than 150."));
+
+ return stepContext.prompt("NumberPrompt", promptOptions);
+ }
+
+ // User said "no" so we will skip the next step. Give -1 as the age.
+ return stepContext.next(-1);
+ }
+
+ private static CompletableFuture pictureStep(WaterfallStepContext stepContext) {
+ stepContext.getValues().put("age", (Integer) stepContext.getResult());
+
+ String msg = (Integer)stepContext.getValues().get("age") == -1
+ ? "No age given."
+ : String.format("I have your age as %d.", (Integer)stepContext.getValues().get("age"));
+
+ // We can send messages to the user at any point in the WaterfallStep.
+ return stepContext.getContext().sendActivity(MessageFactory.text(msg))
+ .thenCompose(resourceResponse -> {
+ if (StringUtils.equals(stepContext.getContext().getActivity().getChannelId(), Channels.MSTEAMS)) {
+ // This attachment prompt example is not designed to work for Teams attachments, so skip it in this case
+ return stepContext.getContext().sendActivity(MessageFactory.text("Skipping attachment prompt in Teams channel..."))
+ .thenCompose(resourceResponse1 -> stepContext.next(null));
+ }
+
+ // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
+ PromptOptions promptOptions = new PromptOptions();
+ promptOptions.setPrompt(MessageFactory.text("Please attach a profile picture (or type any message to skip)."));
+ promptOptions.setRetryPrompt(MessageFactory.text("The attachment must be a jpeg/png image file."));
+
+ return stepContext.prompt("AttachmentPrompt", promptOptions);
+ });
+ }
+
+ private static CompletableFuture confirmStep(WaterfallStepContext stepContext) {
+ List attachments = (List)stepContext.getResult();
+ stepContext.getValues().put("picture", attachments == null ? null : attachments.get(0));
+
+ // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
+ PromptOptions promptOptions = new PromptOptions();
+ promptOptions.setPrompt(MessageFactory.text("Is this ok?"));
+ return stepContext.prompt("ConfirmPrompt", promptOptions);
+ }
+
+ private CompletableFuture summaryStep(WaterfallStepContext stepContext) {
+ if ((Boolean)stepContext.getResult()) {
+ // Get the current profile object from user state.
+ return userProfileAccessor.get(stepContext.getContext(), () -> new UserProfile())
+ .thenCompose(userProfile -> {
+ userProfile.transport = (String) stepContext.getValues().get("transport");
+ userProfile.name = (String) stepContext.getValues().get("name");
+ userProfile.age = (Integer) stepContext.getValues().get("age");
+ userProfile.picture = (Attachment) stepContext.getValues().get("picture");
+
+ String msg = String.format(
+ "I have your mode of transport as %s and your name as %s",
+ userProfile.transport, userProfile.name
+ );
+
+ if (userProfile.age != -1) {
+ msg += String.format(" and your age as %s", userProfile.age);
+ }
+
+ msg += ".";
+
+ return stepContext.getContext().sendActivity(MessageFactory.text(msg))
+ .thenApply(resourceResponse -> userProfile);
+ })
+ .thenCompose(userProfile -> {
+ if (userProfile.picture != null) {
+ return stepContext.getContext().sendActivity(
+ MessageFactory.attachment(userProfile.picture,
+ "This is your profile picture."
+ ));
+ }
+
+ return stepContext.getContext().sendActivity(
+ MessageFactory.text("A profile picture wasn't attached.")
+ );
+ })
+ .thenCompose(resourceResponse -> stepContext.endDialog());
+ }
+
+ // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is the end.
+ return stepContext.getContext().sendActivity(MessageFactory.text("Thanks. Your profile will not be kept."))
+ .thenCompose(resourceResponse -> stepContext.endDialog());
+ }
+
+ private static CompletableFuture picturePromptValidator(
+ PromptValidatorContext> promptContext
+ ) {
+ if (promptContext.getRecognized().getSucceeded()) {
+ List attachments = promptContext.getRecognized().getValue();
+ List validImages = new ArrayList<>();
+
+ for (Attachment attachment : attachments) {
+ if (StringUtils.equals(
+ attachment.getContentType(), "image/jpeg") || StringUtils.equals(attachment.getContentType(), "image/png")
+ ) {
+ validImages.add(attachment);
+ }
+ }
+
+ promptContext.getRecognized().setValue(validImages);
+
+ // If none of the attachments are valid images, the retry prompt should be sent.
+ return CompletableFuture.completedFuture(!validImages.isEmpty());
+ }
+ else {
+ // We can return true from a validator function even if Recognized.Succeeded is false.
+ return promptContext.getContext().sendActivity("No attachments received. Proceeding without a profile picture...")
+ .thenApply(resourceResponse -> true);
+ }
+ }
+
+ private static CompletableFuture agePromptValidator(
+ PromptValidatorContext promptContext
+ ) {
+ // This condition is our validation rule. You can also change the value at this point.
+ return CompletableFuture.completedFuture(
+ promptContext.getRecognized().getSucceeded()
+ && promptContext.getRecognized().getValue() > 0
+ && promptContext.getRecognized().getValue() < 150);
+ }
+}
diff --git a/samples/05.multi-turn-prompt/src/main/resources/application.properties b/samples/05.multi-turn-prompt/src/main/resources/application.properties
new file mode 100644
index 000000000..d7d0ee864
--- /dev/null
+++ b/samples/05.multi-turn-prompt/src/main/resources/application.properties
@@ -0,0 +1,3 @@
+MicrosoftAppId=
+MicrosoftAppPassword=
+server.port=3978
diff --git a/samples/05.multi-turn-prompt/src/main/resources/log4j2.json b/samples/05.multi-turn-prompt/src/main/resources/log4j2.json
new file mode 100644
index 000000000..67c0ad530
--- /dev/null
+++ b/samples/05.multi-turn-prompt/src/main/resources/log4j2.json
@@ -0,0 +1,18 @@
+{
+ "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": {
+ "root": {
+ "level": "debug",
+ "appender-ref": {"ref": "Console-Appender","level": "debug"}
+ }
+ }
+ }
+}
diff --git a/samples/05.multi-turn-prompt/src/main/webapp/META-INF/MANIFEST.MF b/samples/05.multi-turn-prompt/src/main/webapp/META-INF/MANIFEST.MF
new file mode 100644
index 000000000..254272e1c
--- /dev/null
+++ b/samples/05.multi-turn-prompt/src/main/webapp/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Class-Path:
+
diff --git a/samples/05.multi-turn-prompt/src/main/webapp/WEB-INF/web.xml b/samples/05.multi-turn-prompt/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 000000000..383c19004
--- /dev/null
+++ b/samples/05.multi-turn-prompt/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,12 @@
+
+
+ dispatcher
+
+ org.springframework.web.servlet.DispatcherServlet
+
+
+ contextConfigLocation
+ /WEB-INF/spring/dispatcher-config.xml
+
+ 1
+
\ No newline at end of file
diff --git a/samples/05.multi-turn-prompt/src/main/webapp/index.html b/samples/05.multi-turn-prompt/src/main/webapp/index.html
new file mode 100644
index 000000000..d5ba5158e
--- /dev/null
+++ b/samples/05.multi-turn-prompt/src/main/webapp/index.html
@@ -0,0 +1,418 @@
+
+
+
+
+
+
+ EchoBot
+
+
+
+
+
+
+
+
+
Spring Boot Bot
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.