Skip to content

Adding a custom log turn

Jean-Philippe Gariépy edited this page May 1, 2018 · 2 revisions

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:

LogTurn.java:

public class LogTurn extends VoiceXmlOutputTurn {

Let's have 2 fields:

LogTurn.java:

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) == &quot;error&quot;">
            <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.

LogTurn.java:

@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.

LogTurn.java:

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):

LogTurn.java:

if (mLabel != null) {
    logElement.setAttribute("label", mLabel);
}

Then we add the log statement in the element:

LogTurn.java:

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:

LogTurn.java:

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:

LogTurn.java:

@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:

LogTurn.java:

@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:

Dialogue.java:

@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) == &quot;error&quot;">
            <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.


Running this example

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 git@github.com: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.