-
Notifications
You must be signed in to change notification settings - Fork 5
Adding a custom log turn
In this example, we will see how to add a LogTurn
class which will provide a way to generate simple
VoiceXML <log>
statements like this:
<log label="myLabel">This is a log message.</log>
When executed by the platform, these statements will do client-side logging (the content will be logged in the VoiceXML platform system logs, not on the Java web server side).
First, we need to define a class extending VoiceXmlOutputTurn
:
public class LogTurn extends VoiceXmlOutputTurn {
Let's have 2 fields:
private final String mStatement;
private final String mLabel;
Now we need to provide a method to generate VoiceXML content. We have the choice to either override one of the following methods:
If we override createVoiceXmlDocument
,
we need to generate every bit of the VoiceXML document. However, if we override
fillVoiceXmlDocument
most of the document is already built. So we're provided with a template in which we only need to place
content in a VoiceXML form:
<?xml version="1.0" encoding="UTF-8"?>
<vxml
application="/rivr-cookbook/dialogue/root/d107d7bc-0553-4140-afcb-cfcc94630fbc"
version="2.1" xmlns="http://www.w3.org/2001/vxml">
<script>application.rivr.localErrorHandling = false; application.rivr.inputTurn = {};</script>
<form id="form">
<!-- This is the form element used by fillVoiceXmlDocument() -->
</form>
<catch>
<if cond="_event.substring(0, 5) == "error"">
<if cond="application.rivr.localErrorHandling">
<goto next="#fatalErrorForm"/>
<else/>
<script>application.rivr.localErrorHandling=true</script>
</if>
</if>
<script>application.rivr.addEventResult(_event, _message)</script>
<goto next="#submitForm"/>
</catch>
<form id="fatalErrorForm">
<block>
<exit/>
</block>
</form>
<form id="submitForm">
<block>
<var
expr="application.rivr.toJson(application.rivr.inputTurn)" name="inputTurn"/>
<if cond="application.rivr.hasRecording(application.rivr.inputTurn)">
<var
expr="application.rivr.inputTurn.recordingMetaData.data" name="recording"/>
<assign expr="undefined" name="application.rivr.inputTurn.recordingMetaData.data"/>
<submit enctype="multipart/form-data" method="post" namelist="inputTurn recording"
next="/rivr-cookbook/dialogue/d107d7bc-0553-4140-afcb-cfcc94630fbc/0/logging"/>
<else/>
<submit method="post" namelist="inputTurn"
next="/rivr-cookbook/dialogue/d107d7bc-0553-4140-afcb-cfcc94630fbc/0/logging"/>
</if>
</block>
</form>
</vxml>
The template will contain all the relevant error handling elements and everything required to send the result
back to the dialogue servlet (i.e. the submitForm
). When creating a custom turn, all that is required is to
goto the submitForm
at the end.
So let's override the fillVoiceXmlDocument
.
@Override
protected void fillVoiceXmlDocument(Document document, Element formElement, VoiceXmlDialogueContext dialogueContext)
throws VoiceXmlDocumentRenderingException {
The first thing to do is to create our <log>
element. To do that, we can use the standard methods for the DOM API.
However here, we will use an utility class provided with Rivr, DomUtils
.
Element logElement = DomUtils.appendNewElement(formElement, VoiceXmlDomUtil.LOG_ELEMENT);
Note that VoiceXmlDomUtil
also contains useful VoiceXML-related
constants and methods.
We set the label
attribute (if not null
):
if (mLabel != null) {
logElement.setAttribute("label", mLabel);
}
Then we add the log statement in the element:
if (mStatement != null) {
DomUtils.appendNewText(logElement, mStatement);
}
At the very end, we create a <goto>
statement to the submitForm
. There's a helper method for that:
VoiceXmlDomUtil.createGotoSubmit(formElement);
When deriving the VoiceXmlOutputTurn
class, two other methods must be
provided.
The system needs to have a text description of the type of turn that we are adding to the system. To do that,
we override the getOuputTurnType
method:
@Override
protected String getOuputTurnType() {
return "log";
}
The last method we need to override is addTurnProperties
.
We need to give a JSON description of the custom turn we've just created:
@Override
protected void addTurnProperties(JsonObjectBuilder builder) {
if (mStatement != null) {
builder.add("statement", mStatement);
}
if (mLabel != null) {
builder.add("label", mLabel);
}
}
The properties specified with those methods will be displayed in the dialogue runner web interface. They have no impact on the generated VoiceXML. So when using the dialogue runner, you'll be able to inspect this turn in the JSON view (i.e. type of turn and properties).
Now, we can use our new type of turn in a regular dialogue:
@Override
public VoiceXmlLastTurn run(VoiceXmlFirstTurn firstTurn, VoiceXmlDialogueContext context)
throws Exception {
LogTurn log = new LogTurn("logging", "this is a logging statement", "debug");
DialogueUtils.doTurn(log, context);
//end of dialogue
return new Exit("exit");
}
This is the result:
<?xml version="1.0" encoding="UTF-8"?>
<vxml
application="/rivr-cookbook/dialogue/root/df85987d-619d-4747-9ec1-60ee52b1985e"
version="2.1" xmlns="http://www.w3.org/2001/vxml">
<script>application.rivr.localErrorHandling = false; application.rivr.inputTurn = {};</script>
<form id="form">
<log label="debug">this is a logging statement</log>
<goto next="#submitForm"/>
</form>
<catch>
<if cond="_event.substring(0, 5) == "error"">
<if cond="application.rivr.localErrorHandling">
<goto next="#fatalErrorForm"/>
<else/>
<script>application.rivr.localErrorHandling=true</script>
</if>
</if>
<script>application.rivr.addEventResult(_event, _message)</script>
<goto next="#submitForm"/>
</catch>
<form id="fatalErrorForm">
<block>
<exit/>
</block>
</form>
<form id="submitForm">
<block>
<var expr="application.rivr.toJson(application.rivr.inputTurn)" name="inputTurn"/>
<if cond="application.rivr.hasRecording(application.rivr.inputTurn)">
<var expr="application.rivr.inputTurn.recordingMetaData.data" name="recording"/>
<assign expr="undefined" name="application.rivr.inputTurn.recordingMetaData.data"/>
<submit enctype="multipart/form-data" method="post" namelist="inputTurn recording"
next="/rivr-cookbook/dialogue/df85987d-619d-4747-9ec1-60ee52b1985e/0/logging"/>
<else/>
<submit method="post" namelist="inputTurn"
next="/rivr-cookbook/dialogue/df85987d-619d-4747-9ec1-60ee52b1985e/0/logging"/>
</if>
</block>
</form>
</vxml>
NOTE: This is just an example. A proper logging extension should not create a VoiceXML document for
the sole purpose of logging. The logging statements should rather be accumulated in memory and flushed every time
a VoiceXmlOutputTurn
or
VoiceXmlLastTurn
is generated. This could be accomplished with the
VoiceXmlDocumentAdapter
mechanism. See Customizing an OutputTurn with VoiceXmlDocumentAdapter.
You can download or browse the complete code for this example at GitHub.This is a complete working application that you can build and run for yourself.
You can also clone the Rivr Cookbook repository and checkout this example:
git clone -b log-turn [email protected]:nuecho/rivr-cookbook.git
Then, to build and run it:
cd rivr-cookbook
./gradlew jettyRun
The VoiceXML dialogue should be available at http://localhost:8080/rivr-cookbook/dialogue
To stop the application, press Control-C in the console.