Skip to content

Spring framework integration

Jean-Philippe Gariépy edited this page Apr 25, 2015 · 2 revisions

There are so many ways you can leverage the Spring framework. This example will show you how to provide a custom dialogue factory that will create VoiceXmlDialogue objects from the Spring bean factory.

The first step is to create a Dialogue as a Java bean:

Dialogue.java:

public class Dialogue implements VoiceXmlDialogue, InitializingBean {

    private String mMessage;

    public void setMessage(String message) {
        mMessage = message;
    }

    @Override
    public VoiceXmlLastTurn run(VoiceXmlFirstTurn firstTurn, VoiceXmlDialogueContext context)
            throws Exception {

        Message message = new Message("message", new SpeechSynthesis(mMessage));
        DialogueUtils.doTurn(message, context);

        //end of dialogue
        return new Exit("exit");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if (mMessage == null) throw new BeanInitializationException("Property 'message' is not set.");
    }
}

Note that this dialogue has a mandatory message property.

Now in the bean factory configuration file, we can provide some definitions for some dialogue beans:

applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean name="x" class="com.nuecho.rivr.cookbook.dialogue.Dialogue" scope="prototype">
    <property name="message">
      <value>This is a message from bean X.</value>
    </property>
  </bean>

  <bean name="y" class="com.nuecho.rivr.cookbook.dialogue.Dialogue" scope="prototype">
    <property name="message">
      <value>This is a message from bean Y.</value>
    </property>
  </bean>

</beans>

We make those beans as prototype, meaning that they are reinstanciated each time they are requested. This is required if the dialogue class is stateful (which is not really the case in our example). A stateless dialogue bean can be shared across many "phone calls" while a stateful dialogue bean can only used by one call.

You need to set the application context in the web.xml:

web.xml:

<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

We can define a custom dialogue factory that will lookup the dialogue in the bean factory according to the value of the dialogue HTTP parameter:

DialogueFactory.java:

public class DialogueFactory implements VoiceXmlDialogueFactory {

    @Override
    public VoiceXmlDialogue create(DialogueInitializationInfo<VoiceXmlInputTurn,
                                   VoiceXmlOutputTurn, VoiceXmlDialogueContext> initializationInfo)
            throws DialogueFactoryException {

        if (!(initializationInfo instanceof WebDialogueInitializationInfo))
            throw new DialogueFactoryException("Can only work in web mode.");

        WebDialogueInitializationInfo<?, ?, ?> webInitializationInfo =
                (WebDialogueInitializationInfo<?, ?, ?>) initializationInfo;
        WebApplicationContext applicationContext =
                getRequiredWebApplicationContext(webInitializationInfo.getServletContext());
        String dialogueBeanName = webInitializationInfo.getHttpServletRequest().getParameter("dialogue");

        if (dialogueBeanName == null)
            throw new DialogueFactoryException("Missing HTTP parameter 'dialogue'.");

        if (!applicationContext.containsBean(dialogueBeanName))
            throw new DialogueFactoryException("No such dialogue: '" + dialogueBeanName + "'");

        if (!applicationContext.isTypeMatch(dialogueBeanName, VoiceXmlDialogue.class))
            throw new DialogueFactoryException("Bad type for dialogue: '"
                                               + dialogueBeanName
                                               + "'.  Should be a "
                                               + VoiceXmlDialogue.class.getName());

        VoiceXmlDialogue dialogue = applicationContext.getBean(dialogueBeanName, VoiceXmlDialogue.class);
        if (dialogue == null)
            throw new DialogueFactoryException("No such dialogue: '" + dialogueBeanName + "'");

        return dialogue;
    }
}

You need to place the name of the dialogue factory class in the web.xml:

web.xml:

<init-param>
  <param-name>com.nuecho.rivr.voicexml.dialogueFactory.class</param-name>
  <param-value>com.nuecho.rivr.cookbook.dialogue.DialogueFactory</param-value>
</init-param>

You can try it with the following URI:


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

Rivr can be extended and customized in different ways. If for some reason, the output and last turns provided by Rivr aren't sufficient, there's always a way out. There are two mechanisms that can be used to extend the fonctionality of Rivr.

The first technique is to customize the VoiceXML generated by a turn. The typical case is to add a platform-specific attribute on a VoiceXML element. To do that, we set an instance of VoiceXmlDocumentAdapter on the VoiceXmlOutputTurn or the VoiceXmlLastTurn. The VoiceXmlDocumentAdapter is an interface with one single method:

void adaptVoiceXmlDocument(Document voiceXmlDocument) throws VoiceXmlDocumentRenderingException

This method allows an implementing class to change the content of the VoiceXML document by modifying the structure of the org.w3c.dom.Document. The manipulation of this object is done by using the standard DOM API.

The second technique is to create a new subclass of VoiceXmlOutputTurn or VoiceXmlLastTurn. When creating this class, the developper has full control over the generated VoiceXML content.

The following examples show how to realize both of the previous techniques.