Vous êtes sur la page 1sur 97

Avaya Speech Applications Builder Component Developers Guide

May 15, 2004

2004 Avaya Inc. All Rights Reserved. Notice While reasonable efforts were made to ensure that the information in this document was complete and accurate at the time of printing, Avaya Inc. can assume no liability for any errors. Changes and corrections to the information in this document may be incorporated in future releases. Preventing toll fraud "Toll fraud" is the unauthorized use of your telecommunications system by an unauthorized party (for example, anyone who is not a corporate employee, agent, subcontractor, or person working on your company's behalf). Be aware that there may be a risk of toll fraud associated with your system and that, if toll fraud occurs, it can result in substantial additional charges for your telecommunications services. Avaya fraud intervention If you suspect that you are being victimized by toll fraud and you need technical assistance or support, call Technical Service Center Toll Fraud Intervention Hotline at +1-800-643-2353 for the United States and Canada. For additional support telephone numbers, see the Avaya Web site: http://www.avaya.com Select Support, then select Escalation Lists. This Web site includes telephone numbers for escalation within the United States. For escalation telephone numbers outside the United States, select Global Escalation List. Providing telecommunications security Telecommunications security (of voice, data, and video communications) is the prevention of any type of intrusion to (that is, either unauthorized or malicious access to or use of) your company's telecommunications equipment by some party. Your company's "telecommunications equipment" includes both this Avaya product and any other voice/data/video equipment that could be accessed via this Avaya product (that is, "networked equipment"). An "outside party" is anyone who is not a corporate employee, agent, subcontractor, or person working on your company's behalf. Whereas, a "malicious party" is anyone (including someone who may be otherwise authorized) who accesses your telecommunications equipment with either malicious or mischievous intent. Such intrusions may be either to/through synchronous (timemultiplexed and/or circuit-based) or asynchronous (character-, message-, or packet-based) equipment or interfaces for reasons of: Use (of capabilities special to the accessed equipment) Theft (such as, of intellectual property, financial assets, or toll-facility access) Eavesdropping (privacy invasions to humans) Mischief (troubling, but apparently innocuous, tampering) Harm (such as harmful tampering, data loss or alteration, regardless of motive or intent) Part 15: Class A Statement For the MCC1, SCC1, G600, and CMC1 Media Gateways: Note: This equipment has been tested and found to comply with the limits for a Class A digital device, pursuant to Part 15 of the FCC Rules. These limits are designed to provide reasonable protection against harmful interference when the equipment is operated in a commercial environment. This equipment generates, uses, and can radiate radio frequency energy and, if not installed and used in accordance with the instruction manual, may cause harmful interference to radio communications. Operation of this equipment in a residential area is likely to cause harmful interference, in which case the user will be required to correct the interference at his own expense. Part 15: Class B Statement For the G700 Media Gateway: Note: This equipment has been tested and found to comply with the limits for a Class B digital device, pursuant to Part 15 of the FCC Rules. These limits are designed to provide reasonable protection against harmful interference in a residential installation. This equipment generates, uses, and can radiate radio-frequency energy and, if not installed and used in accordance with the instructions, may cause harmful interference to radio communications. However, there is no guarantee that interference will not occur in a particular installation. If this equipment does cause harmful interference to radio or television reception, which can be determined by turning the equipment off and on, the user is encouraged to try to correct the interference by one or more of the following measures: Reorient the receiving television or radio antenna wherethis may be done safely. To the extent possible, relocate the receiver with respect to the telephone equipment. Where the telephone equipment requires AC power, plug the telephone into a different AC outlet so that the telephone equipment and receiver are on different branch circuits. Consult the Dealer or an experienced radio/TV technician for help. Your responsibility for your company's telecommunications security The final responsibility for securing both this system and its networked equipment rests with you, an Avaya customer's system administrator, your telecommunications peers, and your managers. Base the fulfillment of your responsibility on acquired knowledge and resources from a variety of sources, including, but not limited to: Installation documents System administration documents Security documents Hardware-/software-based security tools Shared information between you and your peers Telecommunications security experts

To prevent intrusions to your telecommunications equipment, you and your peers should carefully program and configure: Your Avaya-provided telecommunications systems and their interfaces Your Avaya-provided software applications, as well as their underlying hardware/software platforms and interfaces Any other equipment networked to your Avaya products.

Be aware that there may be a risk of unauthorized intrusions associated with your system and/or its networked equipment. Also realize that, if such an intrusion should occur, it could result in a variety of losses to your company (including, but not limited to, human and data privacy, intellectual property, material assets, financial resources, labor costs, and legal costs).

Speech Applications Builder Component Developers Guide May 15, 2004 page 2 of 97

Canadian Department of Communications (DOC) Interference Information For MCC1, SCC1, G600, and CMC1 Media Gateways: This Class A digital apparatus complies with Canadian ICES003. Cet appareil numrique de la classe A est conforme la norme NMB-003 du Canada. For the G700 Media Gateway: This Class B digital apparatus complies with Canadian ICES003. Cet appareil numrique de la classe B est conforme la norme NMB-003 du Canada. This equipment meets the applicable Industry Canada Terminal Equipment Technical Specifications. This is confirmed by the registration number. The abbreviation, IC, before the registration number signifies that registration was performed based on a Declaration of Conformity indicating that Industry Canada technical specifications were met. It does not imply that Industry Canada approved the equipment. Japan For the MCC1, SCC1, G600, and CMC1 Media Gateways: This is a Class A product based on the standard of the Voluntary Control Council for Interference by Information Technology Equipment (VCCI). If this equipment is used in a domestic environment, radio disturbance may occur, in which case, the user may be required to take corrective actions. For the G700 Media Gateway: This is a Class B product based on the standard of the Voluntary Control Council for Interference by Information Technology Equipment (VCCI). If this equipment is used in a domestic environment, radio disturbance may occur, in which case, the user may be required to take corrective actions. Part 15: Personal Computer Statement This equipment has been certified to comply with the limits for a Class B computing device, pursuant to Subpart J of Part 15 of FCC Rules. Only peripherals (computing input/output devices, terminals, printers, etc.) certified to comply with the Class B limits may be attached to this computer. Operation with noncertified peripherals is likely to result in interference to radio and television reception. Part 68: Answer-Supervision Signaling Allowing this equipment to be operated in a manner that does not provide proper answer-supervision signaling is in violation of Part 68 rules. This equipment returns answer-supervision signals to the public switched network when: answered by the called station, answered by the attendant, or routed to a recorded announcement that can be administered by the CPE user.

DECLARATIONS OF CONFORMITY US FCC Part 68 Suppliers Declaration of Conformity (SDoC) Avaya Inc. in the United States of America hereby certifies that the Avaya switch equipment described in this document and bearing a TIA TSB-168 label identification number complies with the Federal Communications Commissions (FCC) Rules and Regulations 47 CFR Part 68, and the Administrative Council on Terminal Attachments (ACTA) adopted technical criteria. Avaya further asserts that Avaya handset equipped terminal equipment described in this document complies with Paragraph 68.316 of the FCC Rules and Regulations defining Hearing Aid Compatibility and is deemed compatible with hearing aids. Copies of SDoCs signed by the Responsible Party in the US can be obtained by contacting your local sales representative and are available on the following Web site: http://www.avaya.com/support All Avaya switch products are compliant with Part 68 of the FCC rules, but many have been registered with the FCC before the SDoC process was available. A list of all Avaya registered products may be found at: http://www.part68.org/ by conducting a search using "Avaya" as manufacturer. European Union Declarations of Conformity Avaya Inc. declares that the equipment specified in this document bearing the "CE" (Conformit Europenne) mark conforms to the European Union Radio and Telecommunications Terminal Equipment Directive (1999/5/EC), including the Electromagnetic Compatibility Directive (89/336/EEC) and Low Voltage Directive (73/23/EEC). This equipment has been certified to meet CTR3 Basic Rate Interface (BRI) and CTR4 Primary Rate Interface (PRI) and subsets thereof in CTR12 and CTR13, as applicable. Copies of these Declarations of Conformity (DoCs) signed by the Vice President of R&D, Avaya Inc., can be obtained by contacting your local sales representative and are available on the following Web site: http://www.avaya.com/support TCP/IP facilities Customers may experience differences in product performance, reliability, and security, depending upon network configurations/design and topologies, even when the product performs as warranted. Warranty Avaya Inc. provides a limited warranty on this product. Refer to your sales agreement to establish the terms of the limited warranty. In addition, Avayas standard warranty language, as well as information regarding support for this product, while under warranty, is available through the following Web site: http://www.avaya.com/support Link disclaimer Avaya Inc. is not responsible for the contents or reliability of any linked Web sites and does not necessarily endorse the products, services, or information described or offered within them. We cannot guarantee that these links will work all of the time and we have no control over the availability of the linked pages.

This equipment returns answer-supervision signals on all direct inward dialed (DID) calls forwarded back to the public switched telephone network. Permissible exceptions are: A call is unanswered. A busy tone is received. A reorder tone is received.

Speech Applications Builder Component Developers Guide May 15, 2004 page 3 of 97

Trademarks Avaya is a trademark of Avaya Inc. Insert all other Avaya Trademarks here, then delete this paragraph. DO NOT include other companys trademarks. All trademarks identified by the or are registered trademarks or trademarks, respectively, of Avaya Inc. All other trademarks are the property of their respective owners. Ordering information: Avaya Publications Center Voice: +1-207-866-6701 1-800-457-1764 (Toll-free, U.S. and Canada only) Fax: +1-207-626-7269 1-800-457-1764 (Toll-free, U.S. and Canada only) Write: Globalware Solutions 200 Ward Hill Avenue Haverhill, MA 01835 USA Attention: Avaya Account Manager Web: E-mail: Order: http://www.avayadocs.com totalware@gwsmail.com

Avaya support Avaya provides a telephone number for you to use to report problems or to ask questions about your contact center. The support telephone number is1-800-242-2121 in the United States. For additional support telephone numbers, see the Avaya Web site: http://www.avaya.com Select Support, then select Escalation Lists. This Web site includes telephone numbers for escalation within the United States. For escalation telephone numbers outside the United States, select Global Escalation List. Comments To comment on this document, send e-mail to crminfodev@avaya.com. Acknowledgment This document was written by the CRM Information Development group.

Speech Applications Builder Component Developers Guide May 15, 2004 page 4 of 97

Contents
Contents ............................................................................................................................................. 5 Introduction ........................................................................................................................................ 7
Scope Targeted Audience Reference Documentation 7 7 7

Overview............................................................................................................................................. 8
Background System Requirements 8 9

SAB Dialog Components ................................................................................................................. 10


Typical Dialog Flow Types of Components Folder Data 10 11 12

Writing a Process Component......................................................................................................... 13


IStep, AbstractStep and Component Lifecycle Calling External Systems Handling Arrays in Components Handling Complex Folder Data in Components 13 14 14 14

Writing a Rule Component............................................................................................................... 15


IRule, AbstractRule and Component Lifecycle 15

Writing a Dialog Component............................................................................................................ 17


VoiceDialog, DialogStep and Base Classes VoiceDialog Implementation and Lifecycle DialogStep Implementation and Lifecycle Prompts Variable Handlers Grammars Building a Voice Dialog and its Wrapper Testing Dialog Components 17 18 20 21 25 25 27 47

Component Packaging and Deployment......................................................................................... 49


Packaging the Component In a Jar Archive Using the Component in the Configurator Upgrading Configuration Data and Component Versions 49 51 51

Appendix A: Process Step Example and Code............................................................................... 52


Example Code for Process Component 52

Speech Applications Builder Component Developers Guide May 15, 2004 page 5 of 97

Appendix B: Rule Component Example and Code ......................................................................... 63


Rule Component Example Rule Component Code 63 67

Appendix C: Dialog Component Example and Code ...................................................................... 71


Dialog Component Example 71

Speech Applications Builder Component Developers Guide May 15, 2004 page 6 of 97

Introduction
Scope
This manual is designed to help application developers create new components for the Speech Applications Builder (SAB) Configurator which is the graphical user interface of the SAB. It provides the developer with detailed, step-by-step instructions for writing, wrapping and deploying the component code into the SAB tool environment.

Targeted Audience
This manual is primarily aimed at application developers who wish to create customized components to supplement the existing library of pre-built SAB components.
Note: A reasonable knowledge of Java programming is assumed.

Reference Documentation
Other related documents are as follows: Speech Applications Builder (SAB) Installation Guide Speech Applications Builder (SAB) Configurator User Guide Speech Applications Builder (SAB) Platform Configuration and Deployment Guide

Note: The code provided in this manual is copyright to Avaya and may be used for training and demonstration purposes only. They may not, however, be reproduced for commercial purposes.

Speech Applications Builder Component Developers Guide May 15, 2004 page 7 of 97

Overview
The Speech Applications Builder (SAB) components are represented in the Configurators Dialog Model by three types of icons: a flat square for a process component; a speech-bubble for dialogs and a diamond-shaped junction for rule components and connecting paths through the dialog flow. Each component represents a Step in the dialog flow, walking the caller through to a result. Figure 1 below shows the three component types:

Dialog Component

Process Component

Rule Component

Figure 1. SAB Component Types

Background
SAB is a network-based development tool that assists organizations and Call Centers to set up, create and manage telephone interaction with their customers. Automated Voice services are convenient communication solutions for a particular business process, often performing the task of data collection using tried and tested dialogs like Address Collection and Credit Card Verification. The main function of SAB is to build re-usable dialogs, but it can also handle calls and voice recognition, providing a live channel for picking up audio input and interacting with callers. The SAB platform gives you the infrastructure for writing standard dialogs using building blocks (or components). It is the library of ready-made SAB components that helps you to quickly create, deliver
Speech Applications Builder Component Developers Guide May 15, 2004 page 8 of 97

and maintain a voice recognition service. The components delivered with SAB are regularly used within dialogs and are predictable in nature and therefore convenient for testing and delivery of routine dialogs. You can design and deploy your own dialog components when the standard components do not provide the desired functionality.

System Requirements
You will need the following: SAB application (check with system administrator for version number). Java Dev environment. (JDK 1.4.Runtime environment. The runtime of SAB is 1.3 compatible. Depending upon the target deployment platform, a specific component may be required to be 1.3 compatible, but this is determined on an individual basis.

As an Application Developer you do not need direct knowledge of VoiceXML (VXML), although some idea of how VXML works is helpful background information; a definitive guide can be found at http://www.w3.org/TR/voicexml20/. You do need good knowledge of Java and Java servlets. We recommend Java Servlet Programming, J. Hunter et al. (OReilly, UK, 2001). Note: VXML is the HTML of the voice world and it allows us to provide voice services using Web-based technologies such as servlets and HTTP request/response interactions.

Speech Applications Builder Component Developers Guide May 15, 2004 page 9 of 97

SAB Dialog Components


The Speech Applications Builder (SAB) Configurator works on the principle that each part of a telephone conversation is a re-usable task-oriented dialog component handling interaction with callers, processing collected data or deciding on the next appropriate step in the dialog. If you look carefully at telephony interaction, there are usually three types of repetitive activity for every call: Process activity manipulation of collected data and integration with other systems Rule activity determining dialog direction Dialog activity - voice interaction (prompts and recognition)

The three types of activities are the basis for the three types of components and the type you use or create depends on the task you want to perform.

Typical Dialog Flow


The dialog flow can automate a very simple process such as redirecting callers to an agent. For example, a call comes into a call centre. It is received by an automated SAB application. The application greets and then asks the caller for their PIN, and on picking up a result, either transfers the caller to an Agent or delivers a PIN not recognized prompt and ends the call. To create this type of application, you would use the following components:

Name CallReceive SayDialog NumberQuestion VerifyPIN Pin Validity Check CallRedirect SayDialog HangupDialog

Function Captures incoming call data Greets Caller Asks a question, confirming and disambiguating, if necessary Calculates validity of PIN Determines direction of call on true/false calculation Transfers the call to another line Speaks to the caller Contains set parameters for call closing

Type of Component Dialog Dialog Dialog Process Rule Dialog Dialog Dialog

Speech Applications Builder Component Developers Guide May 15, 2004 page 10 of 97

Types of Components
Figure 2: Component Objects displays a selection of components in a SAB application flow, indicating the progress of a call between the components and the types of objects involved. All components are described in the SAB Configurator as Steps and Rules and their function determines what they encapsulate.

Process Steps
These components simply encapsulate a piece of process functionality, which might be performing a calculation or interfacing with a backend system. The implementation of a Process Step is a single Java class that conforms to a specific interface, and which may call any other helper classes as required.

Rules
These components perform some processing in their current context, and return a Boolean value: either true or false. The implementation is again a single Java class, which may call any other helpers as required.

Dialog Steps
These encapsulate an interaction, or a series of interactions, with the caller. Their implementation is more complex than the other components, since they are typically constructed as a composition of other Dialog components.

START

CallReceiveStep

Call Database

NumberStep

Rule

Call Receive Dialog

DB

Number Dialog

Wrapped by the NumberStep Wrapped

Single Result

Confirm Dialog

Figure 2. Component Objects

Speech Applications Builder Component Developers Guide May 15, 2004 page 11 of 97

Folder Data
This is XML data carried through the dialog flow in a defined folder. XML data is organized into variables with their own datatypes and name. Each datatype will decide the volume it can load. Components are written as standalone entities, and are generally not dependent on other components to do their task. However, in order to achieve meaningful results with the process, we need interaction between components. This interaction is generally in terms of information that is passed from one component to another. This information is passed in the form of xml and is called a folder. Each entity inside the XML passed is called a folder variable. Each folder variable has a name and a data type where the name is used to refer to the variable and the data type controls the value that can be stored in the variable. The data types used are xsd data types (for example, xsd:date, xsd:string). Components can access the folder and retrieve or set the values for the variables. They can even add or remove variables from the folder.

Speech Applications Builder Component Developers Guide May 15, 2004 page 12 of 97

Writing a Process Component


A process component is the simplest type of Step. It implements a narrowly defined piece of functionality. Developing a Process Step is simple and becomes complex only when the task to be performed demands complexity.

IStep, AbstractStep and Component Lifecycle


The AbstractStep class provides a base implementation of the IStep interface. The methods to be implemented by the Process Step developer fall into two groups. The first serves to describe the component: createStepTypeDefinition(Document) getDescription()

The Step Type Definition returned by the first of these methods describes the component in terms of its appearance on screen, its versioning and any configuration properties exposed by it. The getDescription() method simply returns a human-readable description of the component and its task. The remaining two methods allow the component to be configured and perform its actual task: doInit(StepDefinition) process(Folder, RunnableStepAssignment)

The first method initializes the component with the configuration supplied by the Configurator user. The options for the configuration are described by the Step Type Definition the Step Definition passed into the doInit() method contains the actual values for the various options. The doInit() method is called whenever the configuration is updated (at design time), or when the component is first deployed (at run time). It is always called before the component is asked to service process requests. The code in this method should extract the required configuration, and store the settings as instance variables, since the configuration is tied to the instance of the component. At runtime, the method will be called only once, since it is required that components are immutable in terms of their configuration. The process() method performs the actual work of the component. The runtime environment invokes it whenever the dialog flow reaches the component. The current Folder is passed in as the first argument - this should be used to provide any dynamic data upon which the Process Step performs its task, and to store any resultant data for use by the rest of the flow. The process method will only be called at runtime; it may be invoked by multiple threads concurrently, and so it should be thread-safe. In particular, the process() method should not usually modify the instance variables of the Step object, since any changes would be reflected in all calls currently being handled.
See Appendix A Process Component Example and Code to see a working example and the code used.

Speech Applications Builder Component Developers Guide May 15, 2004 page 13 of 97

Calling External Systems


The process() method of a Process Step may perform any required functionality, such as invoking an external system. Standard Java libraries and third party utilities may be used to perform the work; the only additional requirement is that the class is exposed with the standard interface. It is worth noting that any function performed within the process() method must be safe for use by concurrent threads if your external system cannot handle concurrent access, then you must take precautions to enforce serialization of access.

Handling Arrays in Components


The platform supports using the collection of values for a single field. There are two ways in which these can be used. One way is to use them as multiple values for a single field on the step configuration. This is achieved by setting isArrayType flag on addField() while in createStepTypeDefinition(). If a field has been declared in this way, StepDefinition.getValue(fieldName) from doInit() will return a java.util.List. The other way is to use folder variables which hold multiple values. With this, Folder.getElement(xpath) will return an org.jdom.Element and you must traverse through the data. Also, you can specify if the variable field added is for scalar variables or array variables.

Handling Complex Folder Data in Components


The platform supports using user defined complex data types. To do this, the component developer must specify the name of the complex data type while using addVariableField() from createStepTypeDefinition(). As with array fields, Folder.getElement(xpath) will return an org.jdom.Element and the developer must traverse through the data.

Speech Applications Builder Component Developers Guide May 15, 2004 page 14 of 97

Writing a Rule Component


A Rule determines the path of execution of a dialog flow or temporarily halts the execution, waiting for a certain condition to exist before permitting the flow to continue. Depending on the evaluation of a Rule either a Step is executed or another Rule is evaluated. When a Rule is used to determine what Step is to be executed next it is called a Transition Rule. Transition Rules determine the path of execution. By putting another Rule on a result branch of a Rule, complex decision logic can be achieved from simpler Rules.

IRule, AbstractRule and Component Lifecycle


Rules must implement the IRule interface. AbstractRule provides a suitable base class it should be extended for all user-defined rules. Rule classes are structured in much the same way as Process Steps. Refer to the Writing a Process Component section of this document as some details covered in that section will not be repeated here. However, all the necessary code to build a rule, along with comments, is provided in this section. As with the Process Step, the methods to be implemented fall into two groups: those to describe the component and those to configure and use it. The descriptor methods differ slightly from those for the Process Step: createRuleTypeDefinition(Document) getRuleDescription() getRuleDescriptionWhenTrue() getRuleDescriptionWhenFalse()

The Rule Type Definition returned by the first method returns exactly the same information as the Step Type Definition for the Process Step. Rules may expose arbitrary properties to be configured by the Configurator user, in much the same way as all other components. Typically, rules will be configured with references to Folder variables to be examined. The additional getRuleDescriptionWhen* methods provide additional human-readable messages for the Configurator users. The last two methods allow the rule to be configured and used: doInit(RuleDefinition) doEvaluate(Folder, RunnableStepAssignment)

The first of these works exactly like the doInit() method in the Process Step; it allows the rule component to be initialised using configuration data entered by the Configurator user. It will be called repeatedly at design time (whenever the configuration is updated by the user), and once at runtime immediately after the component is instantiated. The doEvaluate() method is called at runtime and performs the actual body of the rule. It must return either true or false. It may examine the current state of any Folder data, and perform any necessary calculations. As with Process Steps, Rules may be invoked by several concurrent threads and so must be written with thread safety in mind. The doEvaluate() method should not typically alter instance variables in the rule class (unless, for example, a loop counter is required in this case, care must be taken to synchronize access to the instance variables).
Speech Applications Builder Component Developers Guide May 15, 2004 page 15 of 97

See Appendix B: Rule Component Example and Code for a working Rule Component example and its code.

Speech Applications Builder Component Developers Guide May 15, 2004 page 16 of 97

Writing a Dialog Component


Dialog components perform voice interaction with the caller. A voice interaction could include: asking the caller a question iterating a message to the caller transferring the caller to an agent

VoiceDialog, DialogStep and Base Classes


Writing Dialog components can be relatively more complicated than writing Process or Rule components. This is due to a fundamental difference in the way they are built: while Process and Rule components are typically built to perform one small, focused, stand-alone task, Dialog components are typically built up from other Dialog components to encapsulate more tailored or complex functionality. For example, the Yes/No dialog asks the user for a yes/no answer to a simple question. A more complex Dialog component may ask the user for some broader input, such as the NumberDialog. If the system is not confident of the caller's response, it may then ask the caller to confirm it - i.e. "Did you say X?". This is implemented in NumberDialog so that it contains a YesNoDialog to ask this confirmation question. It also contains a SingleResultQuestionDialog to ask the main number question. This generic question component allows any wrapping components to specify the prompts and grammars to use, along with other aspects of its setup. A complex Dialog component may ask for some composite data, for example the collection of credit card details. A component such as this may wrap many smaller components, for example a DateDialog to ask for each date, and a NumberDialog to ask for an issue number. Each Top Level Dialog component must still be exposed to the SAB system as a DialogStep. They expose the dialogs interface in the GUI. The Steps extend the base class DialogStep (in com.netdecisions.ccp.model.vr); the wrapped implementations implement the VoiceDialog interface (in com.fluencyvoice.runner.dialog.voice).

Component Granularity - Strategies for Functionality and Re-use


Since VoiceDialog implementations are generally constructed from other VoiceDialog objects, any functionality exposed as a VoiceDialog object is available for use in a composite component. If functionality is defined only as a DialogStep, it may not be re-used to make a more complex component (although it could still be combined with other components to form a rich dialog flow within the Configurator). For example, consider the Number component in SAB. This could be implemented as a simple wrapper DialogStep, using a raw SingleResultQuestionDialog component. However, there are two issues with this approach: The implementation of the NumberDialog requires that it can have a valid number range set upon it. In this case, if the caller speaks a number outside this range, a message is played to them and the question asked again. This involves not just a SingleResultQuestionDialog, but a MessageDialog too. A DialogStep is not capable of wrapping several VoiceDialog components and so there must be a single VoiceDialog that provides the entire component's flow.

Speech Applications Builder Component Developers Guide May 15, 2004 page 17 of 97

If the Number dialog component was written as a DialogStep around a more general question component, it would not be available as a simple, complete component for larger components to build upon. For example suppose a Debit Card component is required that allows the user to say an Issue number. It would be useful to simply delegate the gathering of the number to a subcomponent; this is possible if we have a specific VoiceDialog implementation for numbers. performs a more specific task than an already existing component is going to be implemented by wrapping that existing component and modifying its settings is not likely to be used as part of a larger component

You can write a DialogStep wrapper for an existing VoiceDialog component if your new component:

Note that this may apply to a new, custom-type question you want to ask the caller; it could well be implemented by wrapping a SingleResultQuestionDialog and encapsulating the specification of a grammar, default prompts and your results handling technique.

VoiceDialog Implementation and Lifecycle


The VoiceDialog interface specifies what is required of reusable "voice building blocks" in SAB. The important features of VoiceDialog components are as follows: They are typically constructed as composites of smaller or more general VoiceDialog subcomponents. They have an asynchronous request/response API: a component is started by a request being passed in, and when it has completed its work, it will issue a response to the caller. They expose their configuration as JavaBean properties (which may only be set at design and initialization time, not at run time).

As with all other components in SAB, VoiceDialog components are used in a multi-threaded environment and must be written accordingly. A VoiceDialog implementation should be based on the AbstractVoiceDialog base class. There are four abstract methods in that class that must be implemented by a VoiceDialog component. The first two of these return meta-data about requests that the component will accept and the responses it may issue: doGetDialogRequests() doGetDialogResponses()

Each of these methods returns an array of classes for the requests and responses. The remaining two methods to be implemented handle requests and responses received by the component: doHandleRequest(DialogRequest) doHandleResponse(DialogResponse)

Constructing Composite and Tailored VoiceDialogs


When building a VoiceDialog implementation it is typical to wrap other VoiceDialogs. A new VoiceDialog typically provides one or both of the following: A more tailored encapsulation of another VoiceDialog. For example, wrapping SingleResultQuestionDialog to ask for numbers, as in the case of NumberDialog.

Speech Applications Builder Component Developers Guide May 15, 2004 page 18 of 97

A collection of several nested VoiceDialogs which together perform a more complex composite flow. An example would be credit card details collection.

The Request/Response Cycle


The flow of control through a tree of VoiceDialog components is determined by passing Requests and Responses between them. A StartRequest is accepted by the top-level VoiceDialog. It causes the component to perform its task by invoking sub-dialog components, and performing any required processing. When the component has finished its task, it issues a Response to the caller. This varies depending on the outcome of the interaction with the caller. Some examples of response types are SomethingGatheredResponse, NotAnsweredResponse or CallEndedResponse.

1 StartRequest Voice Dialog

2 request 7 response Further request to sub-dialog 3 Voice Dialog 4 5 Voice Dialog Voice Dialog 6

Voice Dialog

Voice Dialog

Voice Dialog

Figure 3. Request/Response Cycle in VoiceDialog Components

Figure 3 above illustrates a possible sequence of activity when a composite VoiceDialog is handling a request.
Note that the structure of the sub-components in the tree is static, but the ones that are invoked (and the ordering of invocation) may vary, depending on the circumstances of the request and the course of the interaction with the user.

Managing Data Between Interactions


A VoiceDialog is a multithreaded component, and accordingly, when developing VoiceDialogs, you must ensure that they code appropriately. In particular, it is vital to avoid: Storing of data for an interaction in an instance field of the component. Instance data must only be used for configuration properties of a component, which is not specific to any one call and will remain unchanged at runtime. Setting any properties on sub-dialog components at runtime; for example, modifying the prompts set on a sub-dialog during the doHandleRequest() or doHandleResponse() methods. Configuration properties must only be set at design and initialization time, not at runtime.
Speech Applications Builder Component Developers Guide May 15, 2004 page 19 of 97

Consider, for example, a component that collects credit card details from the user, asking first the number and then the expiry date. This is done by first invoking a nested component to collect the number, and another one to collect the date. When both are collected, a response is issued containing both pieces of data. This means that the component must remember the caller's response to the first question while the second is being asked. To do this, SAB provides the DataManager. The DataManager is retrieved from the DataManagerFactory (in the com.fluencyvoice.runner.core package), and allows components to store data in the following scopes:

Request Scope
Data is associated with the current caller and with one component instance, and is available only for the duration of the component's handling of the single request. Data in this scope should be Serializable, and depending on the DataManager in use, may be available in all VMs running the application.

Session Scope
Data is associated with the current caller, but is available to components until is it explicitly destroyed. Data in this scope should be Serializable, and depending on the DataManager in use, may be available to all VMs running the application.

Application Scope
Data is associated for all callers in the current application, in the current VM. This scope is generally not used by components, but provides an alternative to "static" data (when it need to be specific to the application, not truly global). To put data into the Session scope of the DataManager from within a component, use the following code: DataManagerFactory.getDataManager().putRequestData(this.getId(), SOME_KEY, data); To retrieve that data, use the following code: data = DataManagerFactory.getDataManager().getRequestData(getId(), SOME_KEY); The exact implementation of DataManager used by the SAB runtime depends on the deployment requirements. (Refer to the Speech Applications Builder (SAB) Platform Configuration and Deployment Guide for further details).

DialogStep Implementation and Lifecycle


DialogSteps expose VoiceDialogs to the SAB Configurator and runtime system; each of them wrap a single VoiceDialog object. When writing components you should extend the base DialogStep base class which requires a number of methods to be implemented. The first of these methods, as with Process Steps, serves to describe the component: createStepTypeDefinition(Document) getStepDefinition()

These methods work in the same way as those for Process Steps.

Speech Applications Builder Component Developers Guide May 15, 2004 page 20 of 97

The next methods deal with initializing and configuring the component: initStep(StepDefinition) createDialog() configureDialog(Dialog) - throws a StepInitialization exception - throws a StepInitialization exception

These are called by the SAB system in this order. The first fetches configuration data from the StepDefinition, and stores it in the instance data of the class. The second method actually creates the VoiceDialog object that this step wraps. The third method then configures that VoiceDialog with the configuration stored in the instance data of the DialogStep. This involves calling the JavaBean property set methods on the VoiceDialog. Steps and VoiceDialogs handle configuration data externally. Steps set up their own configuration from a definition passed into the initStep() method, while VoiceDialogs expose all their configuration as JavaBean properties. The remaining two implementation methods listed below define how DialogRequests are created to start the VoiceDialog, and how any DialogResponses are handled: convertToRequest(DialogFolder) - throws a Business Process exception addResultToFolderData(DialogFolder, DialogResponse) - throws a Business Process exception

The first of these should return a DialogRequest that will start the VoiceDialog. Many VoiceDialogs have requests that can take dynamic data (for example, data used in a dynamic prompt), and this dynamic data can be retrieved from the folder by the convertToRequest() method. The second method, addResultToFolderData, should perform any updates to the folder based on the response from the VoiceDialog.
Note that DialogSteps provide one path out of the step for every type of response issued by the VoiceDialog (i.e. defined by its getDialogResponses() method). Each of these paths can lead to a different part of the flow in the application. If a VoiceDialog returns no information apart from the type of the response it has issued, then no updates are needed to the folder.

Prompts
Prompts are an important part of an application's interaction with the caller. Prompts specify what the user will hear and are an essential part of the SAB environment. Prompts can be simple scripts, (such as Hello), that are used for Text-to-Speech synthesis, prerecorded audio (.wav files), or complex and nested structures. The more complex prompts can generate a mixture of audio and text-to-speech playback, concatenated from both dynamic and static data. Prompts are configured as properties on VoiceDialog components, and are encapsulated within implementations of the DialogPrompt interface.

The DialogPrompt Interface


The com.fluencyvoice.runner.dialog.voice.prompt package includes the DialogPrompt interface that must be implemented by all prompt objects. It provides a number of methods, both to access descriptive data about the prompt and to allow the rendering of the prompt.

Speech Applications Builder Component Developers Guide May 15, 2004 page 21 of 97

DialogPrompts themselves are typically set as fixed properties (fixed during runtime) on a VoiceDialog and any dynamic behavior is achieved by the prompt object when it is rendered, using any available dynamic data as appropriate. The SAB Configurator builds up prompts that are configured in the tool into the appropriate DialogPrompt structures; what follows in this section is intended only for use when a component developer has to hard-code prompt definitions (for example, to specify default prompts).

Basic Prompts using TTS and Audio Files


The most basic type of prompt that can be set on a VoiceDialog is the BasicDialogPrompt. It allows you to specify a piece of Text-to-Speech (TTS), known as the prompt's script, to be spoken by the Voice platform. A BasicDialogPrompt is declared and set as a property of a VoiceDialog in this way: // Construct a new MessageDialog welcomeDialog = new MessageDialog(); // Construct the BasicDialogPrompt BasicDialogPrompt welcomePrompt = new BasicDialogPrompt("Welcome to our application."); // Set the prompt on the message component welcome.setPrompt(welcomePrompt); The BasicDialogPrompt also allows the developer to specify audio files to play, rather than relying on TTS. It is often better to use pre-recorded prompts to interact with the caller because they provide the user with a higher quality experience and playback of audio does not require as much processing as Text to Speech. This is achieved by adding a URL reference to a .wav file, in addition to the following script: // Construct the BasicDialogPrompt with a reference to a ".wav" file BasicDialogPrompt welcomePrompt = new BasicDialogPrompt("Welcome to our application.", "resource:///myapp/welcome.wav"); Note that the URL for the prompt given in this example is prefixed with "resource://"; this is a special prefix for SAB that will result in the resource being loaded from the classpath. In this case, the prompt will be loaded as a Java resource from the location "/myapp/welcome.wav" on the classpath. Other URL types may be used to reference prompts - either specifying a different server or a different way to load the prompt other than off the classpath.
Note: It is recommended that you always provide a text script for the prompt. The advantages are that: If the audio file is not present, the text will be used as a backup. It is much easier to identify, read and maintain code when the actual text is available, as well as possibly cryptic URLs that point to your pre-recorded audio. With the text script available SAB can produce readable logs that allow you to determine the actual interaction with the caller. This is very valuable when trying to debug live systems.

Speech Applications Builder Component Developers Guide May 15, 2004 page 22 of 97

Concatenating Prompts
When dealing with text scripts it is very easy to modify the contents of the Prompt because this can be done in the Java code. When handling pre-recorded audio this becomes much more difficult. To help with the manipulation of prompts, SAB provides the ConcatenatedPrompt class. This class creates one prompt from a collection of sub-prompts.
Yes/NoDialog Welcome to Moon Trekking. Are you an existing customer? Replies Yes Concatenation Welcome back We are transferring you to an Agent. No Thank you for calling

Figure 4. Concatenation

Each sub-prompt can then specify a different piece of pre-recorded audio or TTS that can play back as a single concatenated prompt. In our sample application, we use this for the message that is announced before we transfer the caller to an agent. We currently have two prompts for this, but a large part of each script is the same. By using the ConcatenatedPrompt we can record part common to both and prefix it with an appropriate sub-prompt depending on how the caller answered the question.
Note: Avaya does not recommend breaking up these particular prompts in a real application. Where it is easy to record script as a single file this often produces a better experience. You might, however, run into situations where you need to use concatenation.

Escalating Prompts
Escalating Prompts are used to deal with recognition errors such as the caller staying silent or not speaking clearly. An escalating prompt is a prompt that can deliver more and more explicit instruction encouraging the caller to speak, or speak more clearly. The original prompt is repeated with changes to the output, for example, Say your First and Last Name. Please say your First and Last Name. Please say your First and Last Name, for example, John Brown. Escalation is usually applied when implementing error handlers. The core SAB components automatically invoke escalating prompts during error handling. The EscalatingDialogPrompt allows for the addition of prompts to be played in order as the prompt is repeatedly invoked.

Question No Match

Please say your name? I didnt understand. Whats your name? I still didnt understand. Say your name, please.

No Input

I didnt hear anything. I still didnt hear. Please hold while I transfer you to an agent.
Figure 5. Escalating Prompt

Speech Applications Builder Component Developers Guide May 15, 2004 page 23 of 97

Prompts can be built up using both nested concatenated and escalating prompts as the following example demonstrates.

Escalation Say your first and last name Concatenation Welcome to Moon Banking Please say your first and last name Please say your first and last name, for example, John Brown
Figure 6. Concatenation and Escalation

Using Dynamic Data in Prompts


In a voice application there are many situations where you want to change the script of a prompt based on the data you collect. This could be because you do not know the exact content of the data; information like dates, times, addresses and figures are too varied or unpredictable to be supported by static prompts. Static Data
Thank you for calling Moon Money.

Dynamic Data

Your savings account balance on...

Tuesday 4th October 2002.


...is...

One thousand eight hundred and forty two pounds.

Figure 7. Static and Dynamic Data

The voice prompt says the following: Thank you for calling Moon Money. Your savings account balance on Tuesday 4th October 2002, is one thousand eight hundred and forty two pounds. To handle this issue, the DialogPrompt interface supports the concept of dynamic data. Dynamic data is passed into the prompt object allowing it to build a prompt based on an interpretation of that data.

Speech Applications Builder Component Developers Guide May 15, 2004 page 24 of 97

As a general rule, this prompt is a concatenated prompt joining static pieces to dynamic data as shown in Figure 7. The concept of dynamic prompt data is used throughout SAB and many dialogs come with prompt classes that can announce data such as dates, names and monetary amounts. These classes announce the data as dynamic data and produce an appropriate prompt based on the interpretation of that data.

Variable Handlers
In addition to these DialogPrompts, the platform also supports Variable Handlers. A variable handler works in a similar way to a dialog prompt in that it renders some dynamic data in a predefined way. For instance, a money variable handler might take the value of 2.45 and read it back in TTS as two dollars and forty-five cents. The difference between a dialog prompt and a variable handler is that a dialog prompt is associated with a particular voice dialog at the code level whereas a variable handler can be assigned from the tool, making them more flexible.

Grammars
Grammars define how a users speech is recognized and interpreted. Grammars define: Words that may be spoken The patterns in which those words may occur The language of the spoken words What these words mean, and how they should be interpreted (for instance, in a Number grammar, the user saying 'double five' would be interpreted as '55').

One of the benefits of the SAB platform is that most of the components provided encapsulate the
definition of grammars; for example, if you want to collect a number from the user, you can simply use a NumberDialog. Component developers will at times require new grammars to be defined. A grammar must be one of two types: Predefined: the grammar is static and defined in an external file. Dynamic: generated on the fly at runtime, for example in response to one of the caller's earlier answers or a query to a database.

SAB supports both types of grammars via the same infrastructure - the VoiceGrammar interface (in the package com.fluencyvoice.runner.widget.voice.grammar). There are different VoiceGrammar implementations for the different types of grammar, and there are different renderers for the different speech platforms.

Pre-defined Grammars
Predefined grammars should be used where the caller's responses are known at design time. For example, if the caller is asked to say a number, their response can be interpreted by a standard grammar.

Speech Applications Builder Component Developers Guide May 15, 2004 page 25 of 97

In code, predefined grammars are defined using the PredefinedGrammar class: questionVoiceDialog.setQuestionGrammar( new PredefinedGrammar( "Some_Rule", "com/myapp/Some_Grammar", new String[] { "my_slot" })); This declaration includes the grammar's rule name, path where the rule can be found and an array of slots returned by the grammar. Since different Speech platforms (for example, Nuance and IBM) use different formats for their grammar files (and in some cases, require different files for the different acoustic models they support), SAB provides a way to map the grammar path given to an actual grammar file to serve to the recognition server. This is done using Grammar Packages - each grammar package corresponds to a particular set of files for a particular platform. Grammar files are typically served from the classpath; a component requiring grammars will typically provide them in companion Jar files. As an example of Grammar Packages, Nuance uses NGO (Nuance Grammar Object) files that are tied to acoustic models and so the Nuance acoustic model (English-UK-1.7.0) is supported by the SAB acoustic model package (grammar-nuance-english-uk-1.7.0). When the above grammar reference to the file key "com/myapp/Some_Grammar" is used on a system with this grammar package, it will be mapped to the file reference: "grammar-nuance-english-uk-1.7.0/com/myapp/Some_Grammar.ngo" In this way, SAB enables grammars for multiple different grammar packages to be on the classpath concurrently; only those that are currently required will be used.

Grammar Results and Slot Processors


Grammars return their results in slots, which are essentially a set of name-value pairs. These are returned from the SAB adaptor layer as a Java Map of Strings. To convert these Maps into a more useful object representation, SAB uses Slot Processors. These are two-way converters between a Map of slots and a typed Object. The type of object depends on the VoiceDialog in question. Whenever a VoiceDialog wraps a SingleResultQuestionDialog (as most simple types of questions do), it should specify a Slot Processor on the question. (For example, NumberDialog specifies a NumberSlotProcessor on its SingleResultQuestionDialog - it converts between slots and Long objects).

Dynamic Grammars and the Grammar Model


Dynamic grammars are for use when the caller's response needs to be interpreted based on runtime information. For example, in some components, the response to the first question is asked to restrict the possibilities for a second question, to aid recognition. SAB provides an object-based abstraction for building up these dynamic grammars, and a set of renderers for each Voice platform, e.g. Nuance, IBM, Speechworks. The package com.fluencyvoice.runner.widget.voice.grammar contains the grammar abstraction classes, including GrammarModel and GrammarRule. These allow a tree structure to be built up, specifying the rules or slots of the grammar.

Speech Applications Builder Component Developers Guide May 15, 2004 page 26 of 97

An example usage of this type of grammar is shown here: GrammarModel model = new GrammarModel(); for(int i = 0 ; i < validAnswers.length; i ++){ HashMap slots = new HashMap(1); slots.put(SLOT_NAME,validAnswers[i]); model.addRule(new GrammarRule(validAnswers[i],slots)); } questionDialog.setQuestionGrammar( new DynamicGrammar(ruleName,model,new String[]{SLOT_NAME},getId())); This creates a new GrammarModel, adds a list of valid answers to that grammar, then wraps it with a DynamicGrammar and sets it on a question dialog component.

Building a Voice Dialog and its Wrapper


An example consisting of step-by-step instructions for building voice dialogs and their wrappers is given below where a sample VoiceDialog is created, and a DialogStep wrapper is developed around it. The example shown is a variant of the NumberDialog, since this is a fairly straightforward component that exhibits many common pieces of functionality.
Note that this is by no means the simplest component that could be developed - many standard "question" type components would be more straightforward. In particular, the NumberDialog wraps two sub-dialogs, whereas many will need to wrap only one.

Creating a VoiceDialog
The NumberDialog collects a number from the caller and performs any confirmation and disambiguation as required, based on the confidence thresholds set. To do this, it uses the SingleResultQuestionDialog, which it wraps and delegates to. The NumberDialog also allows the application developer to specify a range of numbers that are acceptable. If the user states a number outside that range, he will be informed that his entry is not a valid one (by a nested MessageDialog), and re-prompted. (The grammar used by the component shown here can recognize 1 to 1,000,000. Any numbers outside that range will not be recognized at all, and will give a Not Recognized response). The NumberDialog class extends AbstractVoiceDialog, as described in the VoiceDialog Implementation and Lifecycle section of this document. The first step in the implementation is to declare the requests it will handle and the responses it will issue: /** * Returns all the DialogRequest classes this will handle. This covers just * the single StartRequest. */ protected Class[] doGetDialogRequests() { return new Class[] { StartRequest.class }; }
Speech Applications Builder Component Developers Guide May 15, 2004 page 27 of 97

/** * Returns all the DialogResponse classes that this will issue. This covers * just two: either a number is gathered, or not. */ protected Class[] doGetDialogResponses() { return new Class[] { NumberGatheredResponse.class, NotAnsweredResponse.class }; } The actual classes are typically defined as inner classes in a VoiceDialog component: /** * Start request for this dialog. */ public static class StartRequest extends AbstractDialogRequest { public StartRequest(Dialog source) { super(source); } } /** * Indicates that a number has been successfully gathered from the * user, and contains that number as a <code>Long</code>. Any confirmation * or disambiguation, where required, will have been carried out. */ public static class NumberGatheredResponse extends AbstractDialogResponse { // The result. private Long result; /** * @param source The component issuing this response. * @param result The result gathered by the component. */ public NumberGatheredResponse(Dialog source, Long result) { super(source); if (Assertions.ON) { Assertions.assertTrue(result != null); } this.result = result;
Speech Applications Builder Component Developers Guide May 15, 2004 page 28 of 97

} /** * Returns the number gathered by this component. * @return The response number as a Long, never null. */ public Long getNumber() { return result; } } /** * Indicates that no answer could be gathered from the user. The * component will have re-prompted the user according to the counts * specified. */ public static class NotAnsweredResponse extends AbstractDialogResponse { public NotAnsweredResponse(Dialog source) { super(source); } } This has defined what the very high-level runtime behavior of the VoiceDialog will be - it will take a StartRequest, and issue either a NumberGatheredResponse or a NotAnsweredResponse. (It may also issue a CallEndedResponse of some type - this is behavior specified by the base AbstractVoiceDialog class, however, and may be completely ignored by this component's implementation.) Next, we declare various constants that will be required by the component - these define options for grammars, and default values for the various properties: private static final Logger LOGGER = LoggerFactory.getLogger(NumberDialog.class); /** * Default question prompt to use if none is specified. */ private static final DialogPrompt DEFAULT_QUESTION_PROMPT = new BasicDialogPrompt("Please say a number"); /** * Here we build up the default confirmation prompt to use - it is a * concatenation of a prompt to say the dynamic numbers, and the
Speech Applications Builder Component Developers Guide May 15, 2004 page 29 of 97

* confirmation question. */ private static final ConcatenatedDialogPrompt DEFAULT_CONFIRMATION_PROMPT; static { DEFAULT_CONFIRMATION_PROMPT = new ConcatenatedDialogPrompt(); DEFAULT_CONFIRMATION_PROMPT.addPrompt(new NumberDialogPrompt()); DEFAULT_CONFIRMATION_PROMPT.addPrompt(new BasicDialogPrompt("is this correct?")); } /** * Default maximum number the user can enter. 1 million - set to the maximum * possible. */ private static final long DEFAULT_MAX_NUM_SIZE = 1000000L; /** * Default minimum number the user can enter. Zero - set to the minimum * possible. */ private static final long DEFAULT_MIN_NUM_SIZE = 0L; /** * The default number of times the user is allowed to say a number outside * of the limits. If this is exceeded, a NotAnsweredResponse will be issued. */ private static final int DEFAULT_MAX_INVALID_NUMBER_COUNT = 1; /** * The default NumberStyle to allow the user. This is mixed - both digit-by* digit and natural styles are allowed. */ private static final NumberStyle DEFAULT_NUMBER_STYLE = NumberStyle.MIXED; /** * The session key under which to store the count of how many times the user * has said an invalid number. This will be stored in the request scope.
Speech Applications Builder Component Developers Guide May 15, 2004 page 30 of 97

*/ private static final String REQUEST_SCOPE_KEY_INVALID_NUMBER_COUNT = "invalidNumberCount"; // The slot used by the grammar to return the number chosen. private static final String SLOT_NAME = "choice"; // The paths to the various grammars that may be used. private static final String SPELT_OUT_GRAMMAR_PATH = "com/fluencyvoice/dialog/number/Number_SpeltOut"; private static final String NATURAL_GRAMMAR_PATH = "com/fluencyvoice/dialog/number/Number_Natural"; private static final String MIXED_GRAMMAR_PATH = "com/fluencyvoice/dialog/number/Number_Mixed"; // The names of the various rules of the grammars that may be used. private static final String SPELT_OUT_RULE_NAME = "ZeroToAMillion_SpeltOut"; private static final String NATURAL_RULE_NAME = "ZeroToAMillion_Natural"; private static final String MIXED_RULE_NAME = "ZeroToAMillion_Mixed"; Instance variables are used to hold configuration properties, and references to sub-dialogs: /** * The sub-dialog component that asks the user the question (with any * confirmation, disambiguation as required). */ private SingleResultQuestionDialog numQuestionDialog; /** * The sub-dialog component that plays a message to the user if they say a * message that's out of the range acceptable. */ private MessageDialog outOfRangeMessageDialog; /** * The default prompt to play to the user if they say a number outside the * allowed range. This is recreated when the max and min values are updated, * as it says 'please say a number between X and Y'.
Speech Applications Builder Component Developers Guide May 15, 2004 page 31 of 97

* <p> * See {@link #setupDefaultOutOfRangePrompt()}. */ private DialogPrompt defaultOutOfRangePrompt; /** * The minimum number that's acceptable from the user. */ private Long minNumberSize = new Long(0); /** * The maximum number that's acceptable from the user. */ private Long maxNumberSize = new Long(DEFAULT_MAX_NUM_SIZE); /** * The NumberStyle that defines the ways the user can enter their number. */ private NumberStyle numberStyle = null; The constructor for the NumberDialog instantiates the sub-dialog components, and registers them with the base class: /** * Constructor. This constructs the sub-dialog components to be used by this * component, registers them, then sets up the default properties of this * dialog. */ public NumberDialog() { // Create sub-components numQuestionDialog = new SingleResultQuestionDialog(); outOfRangeMessageDialog = new MessageDialog(); // Register sub-components registerSubDialog(numQuestionDialog); registerSubDialog(outOfRangeMessageDialog);

Speech Applications Builder Component Developers Guide May 15, 2004 page 32 of 97

// Setup the details of this dialog and the contained dialogs, as per defaults. setupDefaultOutOfRangePrompt(); outOfRangeMessageDialog.setPrompt(defaultOutOfRangePrompt); numQuestionDialog.setSlotProcessor(new NumberSlotProcessor()); numQuestionDialog.setQuestionPrompt(DEFAULT_QUESTION_PROMPT); numQuestionDialog.setConfirmationPrompt(DEFAULT_CONFIRMATION_PROMPT); resetGrammar(); }
Note that the constructor also sets up the default properties and configuration for the sub-dialogs, and any dynamically-created defaults for this component.

The doHandleRequest() method is implemented next. It is simple - after performing checks, it delegates through to the nested SingleResultQuestionDialog to ask the question: /** * Handles the StartRequest for this dialog component. This performs some * sanity checks on the configuration of the component's properties, then * passes a request to the nested SingleResultQuestionDialog component, to * ask the actual question. */ protected void doHandleRequest(DialogRequest request) { // Check the request is of the correct type. if (!(request instanceof StartRequest)) { throw new IllegalArgumentException( "Start with NumberDialog.StartRequest, but not a " + request.getClass()); } // If assertions are enabled, check that the minimum value is actually under // the maximum value. if(Assertions.ON) { Assertions.assertTrue(getActualMaxNumberSize() >= getActualMinNumberSize(), "The Maximum allowed number (" + getActualMaxNumberSize() + ") is less than the Minimum allowed number (" + getActualMinNumberSize() + ")"); }
Speech Applications Builder Component Developers Guide May 15, 2004 page 33 of 97

// Pass a request to the nested question sub-dialog. numQuestionDialog.handleRequest(new SingleResultQuestionDialog.StartRequest(this)); } This is typical for the doHandleRequest() method, simply passing a request to a nested component. The doHandleResponse() method is more complex; it handles responses from both the wrapped SingleResultQuestionDialog and the MessageDialog. To work out what is currently happening in the component, it's a simple matter of checking which sub-dialog is issuing the response. /** * Handles responses sent to this dialog from the nested sub-dialogs. These * will either be from the SingleResultQuestionDialog responsible for asking * the question, or from the MessageDialog responsible for saying the out* of-bounds message. */ protected void doHandleResponse(DialogResponse response) { // If the response is from the SingleResultQuestionDialog to ask the number... if (response.getSource().equals(numQuestionDialog)) { // If it's an answer.... if (response instanceof SingleResultQuestionDialog.AnsweredResponse) { SingleResultQuestionDialog.AnsweredResponse result = (SingleResultQuestionDialog.AnsweredResponse) response; if (Assertions.ON) Assertions.assertTrue(result != null); Long resultValue = (Long) result.getResult(); // Check that it's within the allowed range if (resultValue.longValue() > getMaxNumberSize().longValue() || resultValue.longValue() < getMinNumberSize().longValue()) { Integer counter = (Integer) getRequestData(REQUEST_SCOPE_KEY_INVALID_NUMBER_COUNT); int count = 0; if (counter != null) count = counter.intValue(); if ((count < DEFAULT_MAX_INVALID_NUMBER_COUNT)) { count++;
Speech Applications Builder Component Developers Guide May 15, 2004 page 34 of 97

putRequestData(REQUEST_SCOPE_KEY_INVALID_NUMBER_COUNT, new Integer(count)); outOfRangeMessageDialog.handleRequest(new MessageDialog.StartRequest(this)); } else { // Has said an out-of-bounds number too many times, return a // not-answered response. issueResponse(new NotAnsweredResponse(this)); } } else { // Got a number - return it. issueResponse(new NumberGatheredResponse(this, resultValue)); } } // If it's not been answered (this will happen after retries as necessary)... else if (response instanceof SingleResultQuestionDialog.NotAnsweredResponse) { // Issue the not-answered response from this dialog. issueResponse(new NotAnsweredResponse(this)); } } // If the reponse is from the out-of-bounds MessageDialog, then just ask the question again. else if (response.getSource().equals(outOfRangeMessageDialog)) { // It only sends back CompletedResponses if (response instanceof MessageDialog.CompletedResponse) { // Re-ask the question numQuestionDialog.handleRequest(new SingleResultQuestionDialog.StartRequest(this)); } } } The NumberDialog now has all its core functionality implemented (apart from a couple of helper methods, which will be included at the end of this section). Now its configuration properties are exposed. First are the simple properties that are being exposed from the wrapped sub-dialogs. They are just delegated through to the sub-dialog in question: /** * Set this to <tt>null</tt> to use the default value from the nested
Speech Applications Builder Component Developers Guide May 15, 2004 page 35 of 97

* SingleResultQuestionDialog. */ public void setNoMatchPrompt(DialogPrompt noMatchPrompt) { numQuestionDialog.setNoMatchPrompt(noMatchPrompt); } /** * @return prompt from underlying SingleResultQuestionDialog */ public DialogPrompt getNoMatchPrompt() { return numQuestionDialog.getNoMatchPrompt(); }
Note that the standard semantics for the property getters and setters is that a "null" value means use the default - so setting a property to null means use the default (if any), and a returned null means the default will be used. If defaults are being defined in the component, the methods must check for that:

/** * @param DialogPrompt prompt to say when number out of range or null for default */ public void setNumberOutOfRangePrompt(DialogPrompt outOfRangePrompt){ if (outOfRangePrompt == null) { outOfRangeMessageDialog.setPrompt(defaultOutOfRangePrompt); } else { outOfRangeMessageDialog.setPrompt(outOfRangePrompt); } } /** * @return DialogPrompt prompt to say when number out of range, null for default */ public DialogPrompt getNumberOutOfRangePrompt() { DialogPrompt result = outOfRangeMessageDialog.getPrompt(); if (result == defaultOutOfRangePrompt) { return null; } else { return result; }
Speech Applications Builder Component Developers Guide May 15, 2004 page 36 of 97

Properties that are held as member variables on the NumberDialog are exposed too, and setters may contain any assertions required (actual user-friendly validation of values is done in the DialogStep wrapper, however): /** * Returns the maximum number to be accepted by the caller, or null if the * default is to be used. */ public Long getMaxNumberSize() { return maxNumberSize; } /** * Sets the maximum number to be accepted from the caller; any numbers * above this will be rejected, with a message informing the caller, and * they will be re-prompted. Setting this to null will mean the default it * used. * <p> * A call to this results in the default out-of-range prompt being reset. */ public void setMaxNumberSize(Long maxNumberSize) { // Check it's not too big (we only support up to 1 million currently) or small if(maxNumberSize!=null && maxNumberSize.longValue()>DEFAULT_MAX_NUM_SIZE) { throw new IllegalArgumentException( "The NumberDialog does not support numbers larger than 1 million."); } else if(maxNumberSize!=null && maxNumberSize.longValue()<0) { throw new IllegalArgumentException( "The NumberDialog only supports positive numbers."); } else { this.maxNumberSize = maxNumberSize; // Set up the out-of-range prompt again. setupDefaultOutOfRangePrompt(); } }

Speech Applications Builder Component Developers Guide May 15, 2004 page 37 of 97

See Appendices A, B and C for a listing for all the codes for all the components, including all the property methods. Once these are added, all that remains are the methods called in the above code to set up the grammar and out-of-range prompt as per the configuration properties.
Note that many VoiceDialog implementations would not have such dependent data, and everything would be simply set up in the constructor or exposed as properties.

/** * Creates the default out-of-range prompt to play to the user if they say * an invalid number. The default prompt tells them what the minimum and * maximum numbers are. */ private void setupDefaultOutOfRangePrompt() { // A very simple TTS prompt by default. defaultOutOfRangePrompt = new BasicDialogPrompt( "Sorry, please say something between " + getActualMinNumberSize() + " and " + getActualMaxNumberSize()); } /** * Resets the grammar for the question to the currently set NumberStyle. */ private void resetGrammar() { NumberStyle currentStyle = getActualNumberStyle(); if(NumberStyle.DIGIT_BY_DIGIT.equals(currentStyle)) { numQuestionDialog.setQuestionGrammar( new PredefinedGrammar( SPELT_OUT_RULE_NAME, SPELT_OUT_GRAMMAR_PATH, new String[] { SLOT_NAME })); } else if (NumberStyle.NATURAL.equals(currentStyle)) { numQuestionDialog.setQuestionGrammar( new PredefinedGrammar( NATURAL_RULE_NAME, NATURAL_GRAMMAR_PATH,
Speech Applications Builder Component Developers Guide May 15, 2004 page 38 of 97

new String[] { SLOT_NAME })); } else if(NumberStyle.MIXED.equals(currentStyle)) { numQuestionDialog.setQuestionGrammar( new PredefinedGrammar( MIXED_RULE_NAME, MIXED_GRAMMAR_PATH, new String[] { SLOT_NAME })); } else { throw new IllegalStateException("Unknown NumberStyle set on the NumberDialog."); } } When the implementation of the VoiceDialog is complete, you can wrap it in a DialogStep for use in the Configurator.

Creating a DialogStep
To expose the NumberDialog to the SAB system, so that it may be used in applications alongside other components and configured in the Configurator, a DialogStep must be created. This class, called NumberDialogStep, will be built according to the steps outlined in the DialogStep Implementation and Lifecycle section of this document. The step allows the Configurator user to set the range of numbers to allow the caller to enter, various prompts for the different possible circumstances, and a folder variable for the caller's answer. First, a number of constants and instance variables are defined for use in the step class: // Descriptions of this step. private static final String STEP_NAME = "Number Dialog Step"; private static final String STEP_DESCRIPTION = "Gathers a number from the user, handling confirmation and disambiguation."; private static final String STEP_CATEGORY = "Sample Steps"; private static final String STEP_IMAGE = "images/nouns/noun_number.png"; // Pages for the configuration properties private static final String PROMPTS_PAGE = "Prompts"; private static final String NUMBER_OUT_OF_RANGE_PAGE = "Number Out of Range"; private static final String CHOICES_AND_RESULT_PAGE = "Choices and Result"; // Prompt specifying the question to ask the user. private static final String QUESTION_TO_ASK_PROMPT_NAME = "QuestionToAskPrompt";
Speech Applications Builder Component Developers Guide May 15, 2004 page 39 of 97

private static final String QUESTION_TO_ASK_PROMPT_DESCRIPTION = "The question to ask"; private String questionTTS; private WavDBODataType questionWav; // Prompt specifying that the number spoken is out of the allowed range. private static final String NUMBER_OUT_OF_RANGE_PROMPT_NAME = "NumberOutOfRangePrompt"; private static final String NUMBER_OUT_OF_RANGE_PROMPT_DESCRIPTION = "Number out of range prompt"; private String invalidNumberTTS; private WavDBODataType invalidNumberWav; // The lower bound of the allowed range of numbers. private static final String RANGE_FROM_NAME = "RangeFrom"; private static final String RANGE_FROM_DESCRIPTION = "Allowed numbers range from"; private BigDecimal rangeFrom; // The upper bound of the allowed range of numbers. private static final String RANGE_TO_NAME = "RangeTo"; private static final String RANGE_TO_DESCRIPTION = "Allowed numbers range to"; private BigDecimal rangeTo; // The variable in the folder to put the result into. private static final String RESULT_REF_NAME = "ResultantVariable"; private static final String RESULT_REF_DESCRIPTION = "Variable for the Gathered Result"; private String resultantVar; Using the constants defined, the Step Definition is defined next, along with the getStepDescription() method: /** * Creates the definition of this step and its configuration properties. */ public RepositoryComponent createStepTypeDefinition(Document jarFile) throws BusinessRuleException { // Overall step definition RepositoryComponent stepDef =

Speech Applications Builder Component Developers Guide May 15, 2004 page 40 of 97

new RepositoryComponent( STEP_NAME, STEP_DESCRIPTION, this.getClass(), STEP_IMAGE, jarFile, STEP_CATEGORY, 6); // Add the main question prompt stepDef.addField( QUESTION_TO_ASK_PROMPT_NAME, QUESTION_TO_ASK_PROMPT_DESCRIPTION, true, PromptDBODataType.class); stepDef.getDboDefinition().getValueDefinition(QUESTION_TO_ASK_PROMPT_NAME) .setPage(PROMPTS_PAGE); // Add the confirmation prompt (as standard) addConfirmation(stepDef); // Add the additional no-input and no-match prompts (as standard) addAdditionalPrompts(stepDef); // Add the number out-of-range prompt stepDef.addField( NUMBER_OUT_OF_RANGE_PROMPT_NAME, NUMBER_OUT_OF_RANGE_PROMPT_DESCRIPTION, true, PromptDBODataType.class); stepDef.getDboDefinition() .getValueDefinition(NUMBER_OUT_OF_RANGE_PROMPT_NAME) .setPage(NUMBER_OUT_OF_RANGE_PAGE); // Add the number range lower and upper bounds stepDef.getDboDefinition().addField( RANGE_FROM_NAME, RANGE_FROM_DESCRIPTION,
Speech Applications Builder Component Developers Guide May 15, 2004 page 41 of 97

true, false, DBODataTypeFactory.DECIMAL_NUMBER_CLASS); stepDef.getDboDefinition() .getValueDefinition(RANGE_FROM_NAME) .setPage(CHOICES_AND_RESULT_PAGE); stepDef.getDboDefinition().addField( RANGE_TO_NAME, RANGE_TO_DESCRIPTION, true, false, DBODataTypeFactory.DECIMAL_NUMBER_CLASS); stepDef.getDboDefinition() .getValueDefinition(RANGE_TO_NAME) .setPage(CHOICES_AND_RESULT_PAGE); // Add the resultant variable reference stepDef.getDboDefinition().addVariableField( RESULT_REF_NAME, RESULT_REF_DESCRIPTION, true, false, SchemaUtils.ALL_XSD_NUMBER_TYPES, true, "xsd:decimal"); stepDef.getDboDefinition() .getValueDefinition(RESULT_REF_NAME) .setPage(CHOICES_AND_RESULT_PAGE); return stepDef; } /** * Returns a human-readable description of this step. */ public String getStepDescription() { return STEP_DESCRIPTION; } The initStep() method is defined next, and works in a similar way to the doInit() method in Process and Rule components. Here the various configuration data of the step, stored as instance data in the NumberDialogStep class, is set based on the StepDefinition passed in.
Speech Applications Builder Component Developers Guide May 15, 2004 page 42 of 97

/** * Initialises the Dialog Step, performing any configuration required and * validating any input from the user. */ protected void initStep(StepDefinition def) throws StepInitializationException { if (LOG.isDebugEnabled()) { LOG.debug("Initializing Number Dialog step"); } // Get values PromptDBODataType prompt = getPrompt(QUESTION_TO_ASK_PROMPT_NAME); if (prompt == null) { try { def.setValue(QUESTION_TO_ASK_PROMPT_NAME, (((BasicDialogPrompt) NumberDialog.DEFAULT_QUESTION_PROMPT).getScript())); } catch (BusinessRuleException e) { throw new StepInitializationException("The question to ask is unassigned."); } } questionTTS = prompt.getTTSPrompt(); questionWav = prompt; prompt = getPrompt(NUMBER_OUT_OF_RANGE_PROMPT_NAME); if(prompt != null){ numberOutOfRangeTTS = prompt.getTTSPrompt(); numberOutOfRangeWav = prompt; } rangeFrom = (BigDecimal) def.getValue(RANGE_FROM_NAME); rangeTo = (BigDecimal) def.getValue(RANGE_TO_NAME); resultantVar = (String) def.getValue(RESULT_REF_NAME); if (resultantVar == null) {
Speech Applications Builder Component Developers Guide May 15, 2004 page 43 of 97

throw new StepInitializationException("The resultant variable must be selected"); } } This configures the Dialog Step in a similar way to that of Process Steps and introduces the NumberDialog that it wraps. The first method to define creates the NumberDialog instance, and will be called at startup time: /** * Creates the wrapped NumberDialog object. */ protected Dialog createDialog() { return new NumberDialog(); } The next method configures that NumberDialog based on the configuration instance data in this Step. The properties on the NumberDialog are all set using the JavaBean property setter methods: /** * Configures the NumberDialog component, based on the configuration options * stored as instance data in this NumberDialogStep. */ protected void configureDialog(Dialog dialog) { // Cast the Dialog to NumberDialog. NumberDialog numberDialog = (NumberDialog) dialog; // Set up the main question prompt. String wavUrl = null; if(questionWav != null && questionWav.hasAudioFile()){ wavUrl = generateWAVDataURL(QUESTION_TO_ASK_PROMPT_NAME, 0); } if(questionTTS!= null || (questionWav != null && questionWav.hasAudioFile())){ numberDialog.setQuestionPrompt(new BasicDialogPrompt(questionTTS, wavUrl)); } // Set up the confirmation prompt (using base class functionality). numberDialog.setConfirmationPrompt(getConfirmationPrompt(new NumberDialogPrompt())); // Set up the out-of-range prompt. wavUrl = null;
Speech Applications Builder Component Developers Guide May 15, 2004 page 44 of 97

if(numberOutOfRangeWav != null && numberOutOfRangeWav.hasAudioFile()){ wavUrl = generateWAVDataURL(NUMBER_OUT_OF_RANGE_PROMPT_NAME , 0); } if (rangeFrom != null) { numberDialog.setMinNumberSize(new Long(rangeFrom.longValue())); } if (rangeTo != null) { numberDialog.setMaxNumberSize(new Long(rangeTo.longValue())); } if (numberOutOfRangeTTS != null || (numberOutOfRangeWav != null && numberOutOfRangeWav.hasAudioFile())) { ConcatenatedDialogPrompt prompt = new ConcatenatedDialogPrompt(); prompt.addPrompt( new BasicDialogPrompt(numberOutOfRangeTTS, wavUrl)); prompt.addPrompt( new BasicDialogPrompt(" from " + numberDialog.getMinNumberSize())); prompt.addPrompt( new BasicDialogPrompt(" to " + numberDialog.getMaxNumberSize())); numberDialog.setNumberOutOfRangePrompt(prompt); }else{ numberDialog.setNumberOutOfRangePrompt(null); } // Set up no-input/no-match/etc. numberDialog.setNoInputPrompt(getNoInputPrompt()); numberDialog.setNoMatchPrompt(getNoMatchPrompt()); numberDialog.setMaxReprompt(getMaxReprompts()); // Set up thresholds for acceptance, etc. numberDialog.setAcceptanceConfidenceThreshold(getAcceptanceThreshold()); numberDialog.setAcceptanceConfidenceMargin(getAcceptanceMargin()); } At this stage we have the component configured and ready for use. All that is left is to define runtime behaviour - how the NumberDialog is started and what happens when it has finished. To start it, a simple StartRequest is passed in. No dynamic data is currently taken from the folder: /**
Speech Applications Builder Component Developers Guide May 15, 2004 page 45 of 97

* Produces a new request to pass to the NumberDialog. Since it takes no * dynamic data from the folder, a new NumberDialog.StartRequest is simply * returned. */ protected DialogRequest convertToRequest(DialogFolder info) { return new NumberDialog.StartRequest(this); } When the NumberDialog has issued a response, depending on the type of response, some data may be inserted into the folder: /** * Receives a response given by the NumberDialog, and adds any result to the * folder. A result is only given in the event that there is a * NumberGatheredResponse issued by the NumberDialog, in which case the * number is added to the folder at the location the Configurator user has * specified. */ protected void addResultToFolderData(DialogFolder folder, DialogResponse response) { if (response instanceof NumberDialog.NumberGatheredResponse) { NumberDialog.NumberGatheredResponse answer = (NumberDialog.NumberGatheredResponse) response; if (LOG.isDebugEnabled()) { LOG.debug("Answer : " + answer); } // Set the result in the folder. try { folder.setValueAt(resultantVar, answer.getNumber().toString()); } catch (InvalidLocationException e) { ExceptionService.of().handleException(e); } } } Implementing a full Dialog component is more time consuming than a Process Step or a Rule, but that reflects the fact that Dialog components are generally more complex.

Speech Applications Builder Component Developers Guide May 15, 2004 page 46 of 97

VoiceDialog Properties
The Voice Dialog Properties expose finer settings like BargeIn, Endpointing, and Input Modes Allowed, to control the interaction. The SingleResultQuestionDialog supports these settings on all interactions namely Main Question, Confirmation, NoInput, NoMatch and MisRecognition. On UI, these settings are exposed by VoicePropertiesDBODataType.

Testing Dialog Components


JUnit Test Cases written for Dialog Wrappers (Steps)/Process Steps
The purpose of writing test cases for Wrappers is to detect any step configuration errors during step initialization, and to check all folder variables required to run the step. Currently, all the test cases for wrappers have pre-requisites attached to them. To write a test case for wrappers, the following files need to be in place: A Simulator for the Dialog A Test Case for the Simulator

An AbstractDialogStepTestCase is written for the Wrapper Test Case. All test cases need to extend this abstract class to get the desired test functionality. AbstractDialogStepTestCase has the following methods: execute (Dialog Simulator):

This method needs to be called to simulate the execution of the step and hence, requires a Dialog Simulator. AddStepData: AddStepArrayTypeData: AddFolderData AddFolderElement Adds the data to the step, to configure any type of fields in the step. Adds the data to the step, to configure array type fields in the step. Adds the data to the folder that is required to execute the step. Adds the data to the folder for user-defined data type elements. A wrapper test case that extends this abstract class must include a set of methods that would validate the step when all mandatory fields are provided to it. For each mandatory field, a test method must be written, where all the fields except the mandatory field should be provided. On execution of this test method, a Step Initialization Exception must be thrown (for example, if there are 4 valid mandatory fields, there must be 4 test methods to check the existence of these fields). In addition to that, there must be methods that test the functionality of the step along with the dialog. This is done by adding folder data to the step. In the case of erroneous folder inputs, you must check for a business process exception.
Speech Applications Builder Component Developers Guide May 15, 2004 page 47 of 97

All test cases follow the same pattern with varying inputs and step initialization settings. Simulators for wrappers, test cases for the simulators and test cases for the wrappers are currently being placed in the following package: src_tests /test/com/netdecisions/components/dialog/nameofcomponent An AbstractStepTestCase is written for the Process Step Test Case. All test cases need to extend this abstract class to get the desired test functionality. Most of the methods in this abstract test class are the same as the abstract dialog test class. Besides the methods used for validating the steps for initialization and providing inputs to folder data, there is an important difference between writing a test case for a wrapper (i.e. step for the dialog) and for a process step. The test cases for the wrappers check all responses from the dialog. Test cases for the process steps are currently being placed in the following package: src_tests /test/com/netdecisions/components/step/nameofcomponent.

Test the dialog flow


You need to test the dialog flow using the SAB tool.

Speech Applications Builder Component Developers Guide May 15, 2004 page 48 of 97

Component Packaging and Deployment


Once your component has been written, it needs to be packaged so that it can be used within a SAB application.

Packaging the Component In a Jar Archive


For components to be made available for use within the SAB Configurator they must be packaged within a standard JAR archive. It recognizes steps by the interface they implement and SAB does not require any special structure for the archive. Several components may be packaged within the same archive. When the JAR file is imported, SAB scans it, and any components within in it are recognized.

Example of an Ant Build Script


A standard approach to automating the build process is to use the Ant tool from Jakarta. A sample build script for building a component archive JAR is given below and it assumes the following directory structure: /SampleProject /java /dist /classes /lib /src Project root directory Location of build script Destination for distributable JAR file Destination for Class files Location of library JAR files Location of source files

The Ant script contains targets to compile the classes ("compile") and to build the JAR archive ("jar"): <?xml version="1.0" encoding="UTF-8"?> <project basedir="." default="jar" name="sample-project"> <!-- Locations of various directories. --> <property name="build.directory" value="classes"/> <property name="source.directory" value="src"/> <property name="lib.directory" value="lib/"/> <!-- Definition of classpath. --> <path id="classpath">

<fileset dir="${lib.directory}"/> </path> <!-- Details for the JAR archive to build. --> <property name="jar.directory" value="dist"/> <property name="jar.name" value="${ant.project.name}.jar"/> <property name="jar.basedirectory" value="${build.directory}"/> <!-- Initialise the script. --> <target name="init"> <tstamp/> </target> <!-- Perform a clean build of the classes. --> <target name="compile" depends="init"> <delete dir="${build.directory}"/> <mkdir dir="${build.directory}"/> <javac destdir="${build.directory}" srcdir="${source.directory}"> <classpath refid="classpath"/> </javac> </target> <!-- Build the JAR archive. --> <target name="jar" depends="init, compile"> <mkdir dir="${jar.directory}"/> <jar jarfile="${jar.directory}/${jar.name}" basedir="${jar.basedirectory}"/> </target> </project> When the "jar" target is run in this script, a JAR archive called "sample-project.jar" is created in the "dist" directory.

Speech Applications Builder Component Developers Guide May 15, 2004 page 50 of 97

Using the Component in the Configurator


See the SAB Configurator User Guide for details on deploying the component in the Configurator.

Upgrading Configuration Data and Component Versions


During the lifecycle of a component, it is upgraded several times. During most of these upgrades, the functionality of the component changes, but the configuration remains the same. This kind of upgrade is handled by the platform and all you have to do is increment the version number. The platform even copies data from the old version to the new version. At times, the configuration of the component must be changed, like adding or removing configuration settings. This is also handled by the platform. However, when you need to change variable names or their data types, the conversion between the old and the new values must be explicit. DialogStep implements an interface called upgradable which defines a single method called upgradeComponentData(DBODefinitionn data). You must override this method to define the conversion path.

Speech Applications Builder Component Developers Guide May 15, 2004 page 51 of 97

Appendix A: Process Step Example and Code


Example Code for Process Component
The sample below illustrates the construction of a simple Process Step. This Step permits the SAB Configurator user to add two variables together or add a variable to a constant. The Step involves taking an existing number type (xsd:decimal schema from the Folder), which serves as a variable in this case, and adding either another xsd:decimal schema type from the Folder (again, another variable) or a constant defined by the Configurator user. The option of using either a variable from the Folder or a constant value defined by the user is determined by the use of a Boolean type (xsd:boolean). Our class will extend AbstractStep (in package com.netdecisions.ccp.model.bp). The steps for producing the concrete implementation class are given here. First, a number of constants and instance variables are defined for use throughout the class:

Figure 8. Edit Step Addition Step

// Descriptions of this step. private static final String STEP_NAME = "Addition Step"; private static final String STEP_CATEGORY = "Sample Steps"; private static final String STEP_DESCRIPTION =

"Takes two numbers from the folder, adds them, then puts the result in the folder. private static final String STEP_IMAGE = "images/nouns/noun_number.png"; // The first input reference for the calculation. private static final String FIRST_INPUT_REF_NAME = "FirstInputRef; //This is the variable name used by the component. private static final String FIRST_INPUT_REF_DESCRIPTION = "First Input Variable"; //This is the display label used on GUI. private String firstInputRef; // This is the actual value of the variable. // The second input reference for the calculation. private static final String SECOND_INPUT_REF_NAME = "SecondInputRef"; private static final String SECOND_INPUT_REF_DESCRIPTION = "Second Input Variable"; private String secondInputRef; // The reference to put the result into. private static final String RESULT_REF_NAME = "ResultRef"; private static final String RESULT_REF_DESCRIPTION = "Variable For the Result"; private String resultRef; The constants here define the metadata for the component, and references to configuration properties it exposes. The instance variables will hold the actual values of the configuration properties, since the configuration applies to an instance of the Step. In normal circumstances, any variables defined at the instance scope should not be updated at runtime. The description of the Step Type Definition is given next: /** * Creates the definition of this step, and its configuration properties. */ public RepositoryComponent createStepTypeDefinition(Document jarFile) throws BusinessRuleException { // Overall step definition RepositoryComponent stepDefinition = new RepositoryComponent( STEP_NAME,
Speech Applications Builder Component Developers Guide May 15, 2004 page 53 of 97

STEP_DESCRIPTION, this.getClass(), STEP_IMAGE, jarFile, STEP_CATEGORY, 3); // Input variables stepDefinition.addVariableField( FIRST_INPUT_REF_NAME, FIRST_INPUT_REF_DESCRIPTION, true, false, SchemaUtils.ALL_XSD_NUMBER_TYPES); stepDefinition.addVariableField( SECOND_INPUT_REF_NAME, SECOND_INPUT_REF_DESCRIPTION, true, false, SchemaUtils.ALL_XSD_NUMBER_TYPES); // Result variable this has changed RT to provide the changes stepDefinition.addNewOrExistingVariableField( RESULT_REF_NAME, RESULT_REF_DESCRIPTION, true, false, SchemaUtils.ALL_XSD_NUMBER_TYPES, "xsd:" + SchemaUtils.XSD_DECIMAL); return stepDefinition; } The first part of this method defines the generic metadata for the component. This covers the following: the human-readable name for the step

Speech Applications Builder Component Developers Guide May 15, 2004 page 54 of 97

a longer description of the functionality provided the class file providing the step (i.e. this class) an image file to provide a graphical representation of this step the JAR file containing this step (passed into the method) the category for the step. (It is a string that identifies the category of the component. For example, a project name, or a system component). The version number of the current component implementation this should be updated as further releases are made, and will be used by the Configurator to notify users that components have been upgraded. Failure to change will prevent importing.

The second part of this method specifies any variables that need to be exposed for configuration. For each, you can specify data such as: the name of the variable a human-readable description of it whether it is required whether it is an array or a single value the type of the variable

For variables referring to positions in the folder, the XSD Schema type of those variables allowed may be specified, and depending on which add method you call, whether the variable is pre-existing (typically for input variables) or whether it is new (typically allowed for output variables). See handling arrays, and complex data types from the folder for more details. The jarFile parameter is the Document in which this component lives. This method is only called on an import, and it identifies the source of this component. That JAR file will then be associated with this component in the repository and when someone wishes to use the component, the platform will pull the class file from the Document object passed into this method. The variables whose values will be added together are defined next. These variables take their names from the FIRST_INPUT_REF_NAME and SECOND_INPUT_REF_NAME constants. Finally, the resultant variable is defined. This holds the result of the calculation that may be an existing folder variable of XML session data or a new one defined by the Configurator user. It is declared in the same way as the input variables, but additional arguments indicate it may be a new variable, and specify the exact type. The other metadata methods are defined next: /** * Returns a human-readable description of this step. */ public String getStepDescription() { return STEP_DESCRIPTION; } /** * Doesn't need any environment properties. */
Speech Applications Builder Component Developers Guide May 15, 2004 page 55 of 97

public String[] getRequiredEnvironmentProperties() { return new String[0]; } The next method to define is doInit(): /** * Initialises the step, performing any configuration required and * validating any properties specified by the user. */ protected void doInit(StepDefinition def) throws StepInitializationException { // Get the values for the variable references, from the step definition firstInputRef = (String)def.getValue(FIRST_INPUT_REF_NAME); secondInputRef = (String)def.getValue(SECOND_INPUT_REF_NAME); resultRef = (String)def.getValue(RESULT_REF_NAME); // Check that everything that is required has been specified. if(firstInputRef==null) { throw new StepInitializationException("First input variable must be specified."); } if(secondInputRef==null) { throw new StepInitializationException("Second input variable must be specified."); } if(resultRef==null) { throw new StepInitializationException("Result variable must be specified."); } } The purpose of this method is to initialize the component with information provided by the Configurator user. A part of the initialization is to verify as much of the components configuration as possible. The platform uses this method to constantly validate the component as set up by the user, whenever it is changed. When the user attempts to validate the dialog flow, this method is invoked on all the Steps in the flow. SAB takes the RepositoryComponent and creates a StepDefinition. It creates a user interface panel for the RepositoryComponent, allowing the Configurator interface user to select and/or enter the variables and values to customise the component for use in the dialog flow. As the user customizes the component, the Configurator takes the submitted values and fills the StepDefinition as appropriate. In the doInit() method the component retrieves the parameters and validates them. Any condition that does not satisfy the component should throw a StepInitializationException.
Speech Applications Builder Component Developers Guide May 15, 2004 page 56 of 97

Once the component is configured correctly, all that remains is to define the process() method that actually performs the function of this step: /** * Performs the calculation. */ public void process(Folder folder, RunnableStepAssignment arg1) throws BusinessProcessException { // Extract the numbers from the folder Double firstNum = new Double((String)folder.getElement(firstInputRef)); Double secondNum = new Double((String)folder.getElement(secondInputRef)); // Do the addition Double result = new Double(firstNum.doubleValue() + secondNum.doubleValue()); // Set the result back in the folder try { folder.setValueAt(resultRef, result.toString()); } catch(InvalidLocationException e) { throw new BusinessProcessException("Unable to save result to folder.", e); } } This method is called by the SAB runtime, made possible by multiple concurrent threads. An example is shown below: Fetch the input variables from the folder from locations specified by the components configuration using XPath and XML. Perform the addition calculation, to produce a new variable. Set the resultant variable on the folder XML at an XPath location specified by the components configuration

If any problem occurs during the processing of this Step, a BusinessProcessException should be thrown with details (and possibly any causing exception).

Speech Applications Builder Component Developers Guide May 15, 2004 page 57 of 97

Process Component Example Code package examples.fluencyvoice.process.addition; import java.util.Hashtable; import javax.naming.InitialContext; import javax.rmi.PortableRemoteObject; import com.netdecisions.agility.fw.kernel.common.BusinessRuleException; import com.netdecisions.ccp.BusinessProcessException; import com.netdecisions.ccp.StepInitializationException; import com.netdecisions.ccp.dbo.bo.DBODefinition; import com.netdecisions.ccp.model.bp.AbstractStep; import com.netdecisions.ccp.model.bp.RunnableStepAssignment; import com.netdecisions.ccp.model.bp.StepDefinition; import com.netdecisions.ccp.model.bp.folder.Folder; import com.netdecisions.ccp.model.bp.folder.InvalidLocationException; import com.netdecisions.ccp.model.bp.folder.SchemaUtils; import com.netdecisions.ccp.repository.bo.RepositoryComponent; import com.netdecisions.ccp.repository.bo.document.Document; /** * A simple addition process step. * <p> * Takes two numeric values from the folder, adds them together, then puts the * result back into the folder. * <p> * Allows the user to specify the location in the folder for the variables * involved. */ public class AdditionStep extends AbstractStep { // Descriptions of this step. private static final String STEP_NAME = "Addition Step"; private static final String STEP_CATEGORY = "Sample Steps";

Speech Applications Builder Component Developers Guide May 15, 2004 page 58 of 97

private static final String STEP_DESCRIPTION = "Takes two numbers from the folder, adds them then puts the result in the folder."; private static final String STEP_IMAGE = "images/nouns/noun_number.png"; // The first input reference for the calculation. private static final String FIRST_INPUT_REF_NAME = "FirstInputRef"; private static final String FIRST_INPUT_REF_DESCRIPTION = "First Input Variable"; private String firstInputRef; // The second input reference for the calculation. private static final String SECOND_INPUT_REF_NAME = "SecondInputRef"; private static final String SECOND_INPUT_REF_DESCRIPTION = "Second Input Variable"; private String secondInputRef; // The refernce to put the result into. private static final String RESULT_REF_NAME = "ResultRef"; private static final String RESULT_REF_DESCRIPTION = "Variable For the Result"; private String resultRef; /** * Constructor does nothing for this step. */ public AdditionStep() { super(); } /** * Returns a human-readable description of this step. */ public String getStepDescription() { return STEP_DESCRIPTION; } /** * Creates the definition of this step, and its configuration properties. */

Speech Applications Builder Component Developers Guide May 15, 2004 page 59 of 97

public RepositoryComponent createStepTypeDefinition(Document jarFile) throws BusinessRuleException { // Overall step definition RepositoryComponent stepDefinition = new RepositoryComponent( STEP_NAME, STEP_DESCRIPTION, this.getClass(), STEP_IMAGE, jarFile, STEP_CATEGORY, 3); // Input variables stepDefinition.addVariableField( FIRST_INPUT_REF_NAME, FIRST_INPUT_REF_DESCRIPTION, true, false, SchemaUtils.ALL_XSD_NUMBER_TYPES); stepDefinition.addVariableField( SECOND_INPUT_REF_NAME, SECOND_INPUT_REF_DESCRIPTION, true, false, SchemaUtils.ALL_XSD_NUMBER_TYPES); // Result variable stepDefinition.addNewOrExistingVariableField( RESULT_REF_NAME, RESULT_REF_DESCRIPTION, true, false, SchemaUtils.ALL_XSD_NUMBER_TYPES, "xsd:" + SchemaUtils.XSD_DECIMAL);
Speech Applications Builder Component Developers Guide May 15, 2004 page 60 of 97

return stepDefinition; } /** * Doesn't need any environment properties. */ public String[] getRequiredEnvironmentProperties() { return new String[0]; } /** * Initialises the step, performing any configuration required and * validating any properties specified by the user. */ protected void doInit(StepDefinition def) throws StepInitializationException { // Get the values for the variable references, from the step definition firstInputRef = (String)def.getValue(FIRST_INPUT_REF_NAME); secondInputRef = (String)def.getValue(SECOND_INPUT_REF_NAME); resultRef = (String)def.getValue(RESULT_REF_NAME); // Check that everything that is required has been specified. if(firstInputRef==null) { throw new StepInitializationException("First input variable must be specified."); } if(secondInputRef==null) { throw new StepInitializationException("Second input variable must be specified."); } if(resultRef==null) { throw new StepInitializationException("Result variable must be specified."); } }
Speech Applications Builder Component Developers Guide May 15, 2004 page 61 of 97

/** * Performs the calculation. */ public void process(Folder folder, RunnableStepAssignment arg1) throws BusinessProcessException { // Extract the numbers from the folder Double firstNum = new Double((String)folder.getElement(firstInputRef)); Double secondNum = new Double((String)folder.getElement(secondInputRef)); // Do the addition Double result = new Double(firstNum.doubleValue() + secondNum.doubleValue()); // Set the result back in the folder try { folder.setValueAt(resultRef, result.toString()); } catch(InvalidLocationException e) { throw new BusinessProcessException("Unable to save result to folder.", e); } } }

Speech Applications Builder Component Developers Guide May 15, 2004 page 62 of 97

Appendix B: Rule Component Example and Code


Rule Component Example
In this section, an example Rule will be constructed to illustrate the steps needed to build a Rule class. The rule will examine a text Folder variable specified by the Configurator user, and will evaluate whether it contains a sub-string specified by the user. If it does contain the sub-string, it will return true, otherwise false. Our class will extend AbstractRule (in package com.netdecisions.ccp.model.rule); the steps in producing the concrete implementation class are given here. First, a number of constants and instance variables are defined for use throughout the class: // Descriptions of this rule. private static final String RULE_NAME = "String Contains Rule"; private static final String RULE_CATEGORY = "Sample Steps"; private static final String RULE_DESCRIPTION = "Sees if a string variable in the folder contains a specified sub-string."; private static final String RULE_IMAGE = "images/rules/rule_boolean.gif"; // The folder variable reference for the comparison. private static final String VARIABLE_REF_NAME = "Variable to Examine"; private static final String VARIABLE_REF_DESC = "Variable from the folder to be examined."; private String variableRef; // The substring value to look for. private static final String SUB_STR_NAME = "Required substring"; private static final String SUB_STR_DESC = "The string you're looking for inside the variable."; private String subStr; As with the process step, the constants here define metadata for the component and references to configuration properties. The instance variables will hold the configuration of the rule instance. The Rule Type Definition is given next: /** * Creates the definition of this rule, and its configuration properties. */ public RepositoryComponent createRuleTypeDefinition(Document jarFile) throws BusinessRuleException {

// Overall rule definition. RepositoryComponent ruleTypeDefinition = new RepositoryComponent( RULE_NAME, RULE_DESCRIPTION, this.getClass(), RULE_IMAGE, jarFile, RULE_CATEGORY, 1); // Variable from the folder for examination. ruleTypeDefinition.addVariableField( VARIABLE_REF_NAME, VARIABLE_REF_DESC, true, false, SchemaUtils.ALL_XSD_TYPES); // Value of the substring to look for. ruleTypeDefinition.addField( SUB_STR_NAME, SUB_STR_DESC, true, StringDBODataType.class); return ruleTypeDefinition; } The code given here is essentially the same as for the Process Step. The metadata of the overall component is populated in the first part of the method, and the two configuration properties are added to the definition.
Note: The first of these is a Folder variable while the second is a static value field simply set directly by the Configurator user.

You must specify the description methods next and define three of the Rules. There is a description for the Rule component, and one for each of the cases which would true and false when it is evaluated.
Speech Applications Builder Component Developers Guide May 15, 2004 page 64 of 97

/** * Returns a human-readable description of this rule. */ public String getRuleDescription() { return RULE_DESCRIPTION; } /** * Returns a human-readable description of this rule when it is evaluated to * false. */ public String getRuleDescriptionWhenFalse() { return "Sub-string '" + subStr + "' isn't contained."; } /** * Returns a human-readable description of this rule when it is evaluated to * true. */ public String getRuleDescriptionWhenTrue() { return "Sub-string '" + subStr + "' is contained."; }

Note how the methods can be customised to return the current configuration settings as part of the descriptive text.

The doInit() method is defined next, and it initialises the Rule with any configuration set by the user (and validates that configuration). This works exactly the same as for Process components. /** * Initialises the rule, performing any configuration required and * validating any properties specified by the user. */ protected void doInit(RuleDefinition ruleDefinition) throws RuleInitializationException { // Setup the variable key into the folder.
Speech Applications Builder Component Developers Guide May 15, 2004 page 65 of 97

variableRef = (String)ruleDefinition.getValue(VARIABLE_REF_NAME); if(variableRef==null) { throw new RuleInitializationException("You must specify a variable in the folder for examination."); } // Setup the constant value for comparison. subStr = (String)ruleDefinition.getValue(SUB_STR_NAME); if(subStr==null) { throw new RuleInitializationException("You must specify the value for the required substring."); } } With the Rule now equipped for configuration and initialization, the doEvaluate() method remains. This performs the actual calculation for the rule, and will return a Boolean value depending on that calculation. /** * Evaluates the rule, returning true or false depending on the current * conditions. * <p> * This will fetch the variable value from the folder, then return whether * it contains the required substring. */ protected boolean doEvaluate(Folder folder, RunnableStepAssignment stepAssignment) throws BusinessProcessException { // Get the variable from the folder. String variableVal = folder.getElement(variableRef).toString(); // Return whether it's equal to the value for comparison. return (variableVal.indexOf(subStr)>=0); } The first line retrieves the String variable from the Folder and the second checks if it contains the required sub-string. As with the Process Steps process() method, exceptions occurring within this method should be caught and re-thrown as a BusinessProcessException.
Speech Applications Builder Component Developers Guide May 15, 2004 page 66 of 97

Rule Component Code


package examples.fluencyvoice.rule; import com.netdecisions.agility.fw.kernel.common.BusinessRuleException; import com.netdecisions.ccp.BusinessProcessException; import com.netdecisions.ccp.RuleInitializationException; import com.netdecisions.ccp.dbo.bo.StringDBODataType; import com.netdecisions.ccp.model.bp.RunnableStepAssignment; import com.netdecisions.ccp.model.bp.folder.Folder; import com.netdecisions.ccp.model.bp.folder.SchemaUtils; import com.netdecisions.ccp.model.rule.AbstractRule; import com.netdecisions.ccp.model.rule.RuleDefinition; import com.netdecisions.ccp.repository.bo.RepositoryComponent; import com.netdecisions.ccp.repository.bo.document.Document; /** * A rule for determining whether a given string variable in the folder contains * a specified substring. * <p> * Allows the user to specify the location in the folder for the variable, and * the required substring. */ public class StringContainsRule1 extends AbstractRule { // Descriptions of this rule. private static final String RULE_NAME = "String Contains Rule"; private static final String RULE_CATEGORY = "Sample Steps"; private static final String RULE_DESCRIPTION = "Sees if a string variable in the folder contains a specified sub-string."; private static final String RULE_IMAGE = "images/rules/rule_boolean.gif"; // The folder variable reference for the comparison. private static final String VARIABLE_REF_NAME = "Variable to Examine"; private static final String VARIABLE_REF_DESC = "Variable from the folder to be examined."; private String variableRef;

Speech Applications Builder Component Developers Guide May 15, 2004 page 67 of 97

// The substring value to look for. private static final String SUB_STR_NAME = "Required substring"; private static final String SUB_STR_DESC = "The string you're looking for inside the variable."; private String subStr; /** * Creates the definition of this rule, and its configuration properties. */ public RepositoryComponent createRuleTypeDefinition(Document jarFile) throws BusinessRuleException { // Overall rule definition. RepositoryComponent ruleTypeDefinition = new RepositoryComponent( RULE_NAME, RULE_DESCRIPTION, this.getClass(), RULE_IMAGE, jarFile, RULE_CATEGORY, 1); // Variable from the folder for examination. ruleTypeDefinition.addVariableField( VARIABLE_REF_NAME, VARIABLE_REF_DESC, true, false, SchemaUtils.ALL_XSD_TYPES); // Value of the substring to look for. ruleTypeDefinition.addField( SUB_STR_NAME, SUB_STR_DESC, true, StringDBODataType.class);

Speech Applications Builder Component Developers Guide May 15, 2004 page 68 of 97

return ruleTypeDefinition; } /** * Returns a human-readable description of this rule. */ public String getRuleDescription() { return RULE_DESCRIPTION; } /** * Returns a human-readable description of this rule when it is enaluated to * false. */ public String getRuleDescriptionWhenFalse() { return "Sub-string '" + subStr + "' isn't contained."; } /** * Returns a human-readable description of this rule when it is enaluated to * true. */ public String getRuleDescriptionWhenTrue() { return "Sub-string '" + subStr + "' is contained."; } /** * Initializes the rule, performing any configuration required and * validating any properties specified by the user. */ protected void doInit(RuleDefinition ruleDefinition) throws RuleInitializationException { // Setup the variable key into the folder. variableRef = (String)ruleDefinition.getValue(VARIABLE_REF_NAME);
Speech Applications Builder Component Developers Guide May 15, 2004 page 69 of 97

if(variableRef==null) { throw new RuleInitializationException("You must specify a variable in the folder for examination."); } // Setup the constant value for comparison. subStr = (String)ruleDefinition.getValue(SUB_STR_NAME); if(subStr==null) { throw new RuleInitializationException("You must specify the value for the required substring."); } } /** * Evaluates the rule, returning true or false depending on the current * conditions. * <p> * This will fetch the variable value from the folder, then return whether * it contains the required substring. */ protected boolean doEvaluate(Folder folder, RunnableStepAssignment stepAssignment) throws BusinessProcessException { // Get the variable from the folder. String variableVal = folder.getElement(variableRef).toString(); // Return whether it's equal to the value for comparison. return (variableVal.indexOf(subStr)>=0); } }

Speech Applications Builder Component Developers Guide May 15, 2004 page 70 of 97

Appendix C: Dialog Component Example and Code


Dialog Component Example
For the NumberDialog: package com.fluencyvoice.dialog.number; import java.io.Serializable; import java.util.HashMap; import java.util.Map; import com.fluencyvoice.runner.core.AbstractDialogRequest; import com.fluencyvoice.runner.core.AbstractDialogResponse; import com.fluencyvoice.runner.core.Dialog; import com.fluencyvoice.runner.core.DialogRequest; import com.fluencyvoice.runner.core.DialogResponse; import com.fluencyvoice.runner.dialog.voice.AbstractVoiceDialog; import com.fluencyvoice.runner.dialog.voice.basic.MessageDialog; import com.fluencyvoice.runner.dialog.voice.basic.SingleResultQuestionDialog; import com.fluencyvoice.runner.dialog.voice.basic.SlotProcessor; import com.fluencyvoice.runner.dialog.voice.prompt.BasicDialogPrompt; import com.fluencyvoice.runner.dialog.voice.prompt.ConcatenatedDialogPrompt; import com.fluencyvoice.runner.dialog.voice.prompt.DialogPrompt; import com.fluencyvoice.runner.logging.Logger; import com.fluencyvoice.runner.logging.LoggerFactory; import com.fluencyvoice.runner.util.Assertions; import com.fluencyvoice.runner.widget.voice.grammar.PredefinedGrammar; import com.fluencyvoice.runner.widget.voice.grammar.VoiceGrammar; /** * The NumberDialog allows the collection of a single number from the caller. It * currently recognises integers between zero and 1 million, and allows the * constraint of the acceptable range within this interval. * <p> * The dialog is started by passing in a {@link NumberDialog.StartRequest},
Speech Applications Builder Component Developers Guide May 15, 2004 page 71 of 97

* which takes no special arguments. * <p> * The following responses may be issued, in addition to any defined in the base * class: * <ul> * <li>{@link NumberDialog.NumberGatheredResponse} * <li>{@link NumberDialog.NotAnsweredResponse} * </ul> * * @author Mehbooblal Mujawar (Original) * @author $Author: gponma $ * @version $Revision: 1.32 $ $Date: 2003/03/04 12:51:34 $ */ public class NumberDialog extends AbstractVoiceDialog { private static final Logger LOGGER = LoggerFactory.getLogger(NumberDialog.class); /** * Default question prompt to use if none is specified. */ private static final DialogPrompt DEFAULT_QUESTION_PROMPT = new BasicDialogPrompt("Please say a number"); /** * Here we build up the default confirmation prompt to use - it is a * concatenation of a prompt to say the dynamic numbers, and the * confirmation question. */ private static final ConcatenatedDialogPrompt DEFAULT_CONFIRMATION_PROMPT; static { DEFAULT_CONFIRMATION_PROMPT = new ConcatenatedDialogPrompt(); DEFAULT_CONFIRMATION_PROMPT.addPrompt(new NumberDialogPrompt()); DEFAULT_CONFIRMATION_PROMPT.addPrompt(new BasicDialogPrompt("is this correct?")); } /**
Speech Applications Builder Component Developers Guide May 15, 2004 page 72 of 97

* Default maximum number the user can enter. 1 million - set to the maximum * possible. */ private static final long DEFAULT_MAX_NUM_SIZE = 1000000L; /** * Default minimum number the user can enter. Zero - set to the minimum * possible. */ private static final long DEFAULT_MIN_NUM_SIZE = 0L; /** * The default number of times the user is allowed to say a number outside * of the limits. If this is exceeded, a NotAnsweredResponse will be issued. */ private static final int DEFAULT_MAX_INVALID_NUMBER_COUNT = 1; /** * The default NumberStyle to allow the user. This is mixed - both digit-by* digit and natural styles are allowed. */ private static final NumberStyle DEFAULT_NUMBER_STYLE = NumberStyle.MIXED; /** * The session key under which to store the count of how many times the user * has said an invalid number. This will be stored in the request scope. */ private static final String REQUEST_SCOPE_KEY_INVALID_NUMBER_COUNT = "invalidNumberCount"; // The slot used by the grammar to return the number chosen. private static final String SLOT_NAME = "choice"; // The paths to the various grammars that may be used. private static final String SPELT_OUT_GRAMMAR_PATH = "com/fluencyvoice/dialog/number/Number_SpeltOut";

Speech Applications Builder Component Developers Guide May 15, 2004 page 73 of 97

private static final String NATURAL_GRAMMAR_PATH = "com/fluencyvoice/dialog/number/Number_Natural"; private static final String MIXED_GRAMMAR_PATH = "com/fluencyvoice/dialog/number/Number_Mixed"; // The names of the various rules of the grammars that may be used. private static final String SPELT_OUT_RULE_NAME = "ZeroToAMillion_SpeltOut"; private static final String NATURAL_RULE_NAME = "ZeroToAMillion_Natural"; private static final String MIXED_RULE_NAME = "ZeroToAMillion_Mixed"; /** * The sub-dialog component that asks the user the question (with any * confirmation, disambiguation as required). */ private SingleResultQuestionDialog numQuestionDialog; /** * The sub-dialog component that plays a message to the user if they say a * message that's out of the range acceptable. */ private MessageDialog outOfRangeMessageDialog; /** * The default prompt to play to the user if they say a number outside the * allowed range. This is recreated when the max and min values are updated, * as it says 'please say a number between X and Y'. * <p> * See {@link #setupDefaultOutOfRangePrompt()}. */ private DialogPrompt defaultOutOfRangePrompt; /** * The minimum number that's acceptable from the user. */ private Long minNumberSize = new Long(0); /**
Speech Applications Builder Component Developers Guide May 15, 2004 page 74 of 97

* The maximum number that's acceptable from the user. */ private Long maxNumberSize = new Long(DEFAULT_MAX_NUM_SIZE); /** * The NumberStyle that defines the ways the user can enter their number. */ private NumberStyle numberStyle = null; /** * Constructor. This constructs the sub-dialog components to be used by this * component, registers them, then sets up the default properties of this * dialog. */ public NumberDialog() { // Create sub-components numQuestionDialog = new SingleResultQuestionDialog(); outOfRangeMessageDialog = new MessageDialog(); // Register sub-components registerSubDialog(numQuestionDialog); registerSubDialog(outOfRangeMessageDialog); // Setup the details of this dialog and the contained dialogs, as per defaults. setupDefaultOutOfRangePrompt(); outOfRangeMessageDialog.setPrompt(defaultOutOfRangePrompt); numQuestionDialog.setSlotProcessor(new NumberSlotProcessor()); numQuestionDialog.setQuestionPrompt(DEFAULT_QUESTION_PROMPT); numQuestionDialog.setConfirmationPrompt(DEFAULT_CONFIRMATION_PROMPT); resetGrammar(); } /** * Handles the StartRequest for this dialog component. This performs some
Speech Applications Builder Component Developers Guide May 15, 2004 page 75 of 97

* sanity checks on the configuration of the component's properties, then * passes a request to the nested SingleResultQuestionDialog component, to * ask the actual question. */ protected void doHandleRequest(DialogRequest request) { // Check the request is of the correct type. if (!(request instanceof StartRequest)) { throw new IllegalArgumentException( "Start with NumberDialog.StartRequest, but not a " + request.getClass()); } // If assertions are enabled, check that the minimum value is actually under // the maximum value. if(Assertions.ON) { Assertions.assertTrue(getActualMaxNumberSize() >= getActualMinNumberSize(), "The Maximum allowed number (" + getActualMaxNumberSize() + ") is less than the Minimum allowed number (" + getActualMinNumberSize() + ")"); } // Pass a request to the nested question sub-dialog. numQuestionDialog.handleRequest(new SingleResultQuestionDialog.StartRequest(this)); } /** * Handles responses sent to this dialog from the nested sub-dialogs. These * will either be from the SingleResultQuestionDialog responsible for asking * the question, or from the MessageDialog responsible for saying the out* of-bounds message. */ protected void doHandleResponse(DialogResponse response) { // If the response is from the SingleResultQuestionDialog to ask the number...

Speech Applications Builder Component Developers Guide May 15, 2004 page 76 of 97

if (response.getSource().equals(numQuestionDialog)) { // If it's an answer.... if (response instanceof SingleResultQuestionDialog.AnsweredResponse) { SingleResultQuestionDialog.AnsweredResponse result = (SingleResultQuestionDialog.AnsweredResponse) response; if (Assertions.ON) Assertions.assertTrue(result != null); Long resultValue = (Long) result.getResult(); // Check that it's within the allowed range if (resultValue.longValue() > getMaxNumberSize().longValue() || resultValue.longValue() < getMinNumberSize().longValue()) { Integer counter = (Integer) getRequestData(REQUEST_SCOPE_KEY_INVALID_NUMBER_COUNT); int count = 0; if (counter != null) count = counter.intValue(); if ((count < DEFAULT_MAX_INVALID_NUMBER_COUNT)) { count++; putRequestData(REQUEST_SCOPE_KEY_INVALID_NUMBER_COUNT, new Integer(count)); outOfRangeMessageDialog.handleRequest(new MessageDialog.StartRequest(this)); } else { // Has said an out-of-bounds number too many times, return a not-answered response. issueResponse(new NotAnsweredResponse(this)); } } else { // Got a number - return it. issueResponse(new NumberGatheredResponse(this, resultValue)); } } // If it's not been answered (this will happen after retries as necessary)... else if (response instanceof SingleResultQuestionDialog.NotAnsweredResponse) { // Issue the not-answered response from this dialog. issueResponse(new NotAnsweredResponse(this));
Speech Applications Builder Component Developers Guide May 15, 2004 page 77 of 97

} } // If the reponse is from the out-of-bounds MessageDialog, then just ask the question again. else if (response.getSource().equals(outOfRangeMessageDialog)) { // It only sends back CompletedResponses if (response instanceof MessageDialog.CompletedResponse) { // Re-ask the question numQuestionDialog.handleRequest(new SingleResultQuestionDialog.StartRequest(this)); } } } /** * Creates the default out-of-range prompt to play to the user if they say * an invalid number. The default prompt tells them what the minimum and * maximum numbers are. */ private void setupDefaultOutOfRangePrompt() { // A very simple TTS prompt by default. defaultOutOfRangePrompt = new BasicDialogPrompt( "Sorry, please say something between " + getActualMinNumberSize() + " and " + getActualMaxNumberSize()); } /** * Resets the grammar for the question to the currently set NumberStyle. */ private void resetGrammar() { NumberStyle currentStyle = getActualNumberStyle(); if(NumberStyle.DIGIT_BY_DIGIT.equals(currentStyle)) { numQuestionDialog.setQuestionGrammar( new PredefinedGrammar( SPELT_OUT_RULE_NAME,
Speech Applications Builder Component Developers Guide May 15, 2004 page 78 of 97

SPELT_OUT_GRAMMAR_PATH, new String[] { SLOT_NAME })); } else if (NumberStyle.NATURAL.equals(currentStyle)) { numQuestionDialog.setQuestionGrammar( new PredefinedGrammar( NATURAL_RULE_NAME, NATURAL_GRAMMAR_PATH, new String[] { SLOT_NAME })); } else if(NumberStyle.MIXED.equals(currentStyle)) { numQuestionDialog.setQuestionGrammar( new PredefinedGrammar( MIXED_RULE_NAME, MIXED_GRAMMAR_PATH, new String[] { SLOT_NAME })); } else { throw new IllegalStateException("Unknown NumberStyle set on the NumberDialog."); } } /** * Returns all the DialogRequest classes this will handle. This covers just * the single StartRequest. */ protected Class[] doGetDialogRequests() { return new Class[] { StartRequest.class }; } /** * Returns all the DialogResponse classes that this will issue. This covers * just two: either a number is gathered, or not. */ protected Class[] doGetDialogResponses() { return new Class[] { NumberGatheredResponse.class, NotAnsweredResponse.class };
Speech Applications Builder Component Developers Guide May 15, 2004 page 79 of 97

} /** * Sets the prompt to use for the question. * <p> * Set this to <tt>null</tt> to use the default value ({@link * #DEFAULT_QUESTION_PROMPT}). */ public void setQuestionPrompt(DialogPrompt questionPrompt) { if (questionPrompt == null) { numQuestionDialog.setQuestionPrompt(DEFAULT_QUESTION_PROMPT); } else { numQuestionDialog.setQuestionPrompt(questionPrompt); } } /** * Returns the prompt to use for the question, or <tt>null</tt> to use the * default value ({@link #DEFAULT_QUESTION_PROMPT}). */ public DialogPrompt getQuestionPrompt() { DialogPrompt result = numQuestionDialog.getQuestionPrompt(); if (result == DEFAULT_QUESTION_PROMPT) { return null; } else { return result; } } /** * Sets the maximum number of reprompts to allow before a {@link * #NotAnsweredResponse} is issued. * <p> * Set this to <tt>null</tt> to use the default value from the nested * SingleResultQuestionDialog.
Speech Applications Builder Component Developers Guide May 15, 2004 page 80 of 97

*/ public void setMaxReprompt(Integer maxReprompt) { numQuestionDialog.setMaxReprompt(maxReprompt); } /** * @param DialogPrompt prompt to say when number out of range or null for default */ public void setNumberOutOfRangePrompt(DialogPrompt outOfRangePrompt){ if (outOfRangePrompt == null) { outOfRangeMessageDialog.setPrompt(defaultOutOfRangePrompt); } else { outOfRangeMessageDialog.setPrompt(outOfRangePrompt); } } /** * @return DialogPrompt prompt to say when number out of range, null for default */ public DialogPrompt getNumberOutOfRangePrompt() { DialogPrompt result = outOfRangeMessageDialog.getPrompt(); if (result == defaultOutOfRangePrompt) { return null; } else { return result; } } /** * @param DialogPrompt as confirmationPrompt */ public void setConfirmationPrompt(DialogPrompt confirmPrompt) { if (confirmPrompt != null) { numQuestionDialog.setConfirmationPrompt(confirmPrompt); } else{
Speech Applications Builder Component Developers Guide May 15, 2004 page 81 of 97

numQuestionDialog.setConfirmationPrompt(DEFAULT_CONFIRMATION_PROMPT); } } /** * @return DialogPrompt prompt to use for confirmation, null for default */ public DialogPrompt getConfirmationPrompt() { DialogPrompt result = numQuestionDialog.getConfirmationPrompt(); if (result == DEFAULT_CONFIRMATION_PROMPT) { return null; } else { return result; } } /** * @return MaxReprompt from underlying SingleResultQuestionDialog */ public Integer getMaxReprompt() { return numQuestionDialog.getMaxReprompt(); } /** * Set this to <tt>null</tt> to use the default value. */ public void setAcceptanceConfidenceThreshold(Float acceptanceConfidenceThreshold) { numQuestionDialog.setAcceptanceConfidenceThreshold(acceptanceConfidenceThreshold); } /** * @return Float from underlying SingleResultQuestionDialog */ public Float getAcceptanceConfidenceThreshold() { return numQuestionDialog.getAcceptanceConfidenceThreshold(); }
Speech Applications Builder Component Developers Guide May 15, 2004 page 82 of 97

/** * Set this to <tt>null</tt> to use the default value. */ public void setAcceptanceConfidenceMargin(Float acceptanceConfidenceMargin) { numQuestionDialog.setAcceptanceConfidenceMargin(acceptanceConfidenceMargin); } /** * @return Float from underlying SingleResultQuestionDialog */ public Float getAcceptanceConfidenceMargin() { return numQuestionDialog.getAcceptanceConfidenceMargin(); } /** * Set this to <tt>null</tt> to use the default value from underlying SingleResultQuestionDialog */ public void setMaxDisambiguation(Integer maxDisambiguateListSize) { numQuestionDialog.setMaxDisambiguation(maxDisambiguateListSize); } /** * @return max disambiguation attempts from underlying SingleResultQuestionDialog */ public Integer getMaxDisambiguation() { return numQuestionDialog.getMaxDisambiguation(); } /** * Set this to <tt>null</tt> to use the default value from the nested * SingleResultQuestionDialog. */ public void setNoMatchPrompt(DialogPrompt noMatchPrompt) { numQuestionDialog.setNoMatchPrompt(noMatchPrompt); }
Speech Applications Builder Component Developers Guide May 15, 2004 page 83 of 97

/** * @return prompt from underlying SingleResultQuestionDialog */ public DialogPrompt getNoMatchPrompt() { return numQuestionDialog.getNoMatchPrompt(); } /** * <p> Set this to <tt>null</tt> to use the default value from the nested * SingleResultQuestionDialog. */ public void setNoInputPrompt(DialogPrompt noInputPrompt) { numQuestionDialog.setNoInputPrompt(noInputPrompt); } /** * @return DialogPrompt from underlying SingleResultQuestionDialog */ public DialogPrompt getNoInputPrompt() { return numQuestionDialog.getNoInputPrompt(); } /** * Start request for this dialog. */ public static class StartRequest extends AbstractDialogRequest { public StartRequest(Dialog source) { super(source); } } /** * Indicates that a number has been successfully gathered from the * user, and contains that number as a <code>Long</code>. Any confirmation * or disambiguation, where required, will have been carried out.
Speech Applications Builder Component Developers Guide May 15, 2004 page 84 of 97

*/ public static class NumberGatheredResponse extends AbstractDialogResponse { // The result. private Long result; /** * @param source The component issuing this response. * @param result The result gathered by the component. */ public NumberGatheredResponse(Dialog source, Long result) { super(source); if (Assertions.ON) { Assertions.assertTrue(result != null); } this.result = result; } /** * Returns the number gathered by this component. * @return The response number as a Long, never null. */ public Long getNumber() { return result; } } /** * Indicates that no answer could be gathered from the user. The * component will have reprompted the user according to the counts * specified. */ public static class NotAnsweredResponse extends AbstractDialogResponse { public NotAnsweredResponse(Dialog source) { super(source); } } /**
Speech Applications Builder Component Developers Guide May 15, 2004 page 85 of 97

* converts slots (which must contain a 'number' slot) into */ public static class NumberSlotProcessor implements SlotProcessor { /** * Returns a {@link Number} object with the name from the slot 'number'. */ public Serializable convertSlotDataToObject(Map slotData) { if (slotData.containsKey(SLOT_NAME)) { return new Long((String) slotData.get(SLOT_NAME)); } else { if (slotData.keySet().size() < 1) { throw new IllegalArgumentException("Slot did not contain any keys - expected 'number'"); } else { throw new IllegalArgumentException("Given slot did not contain the key 'number'"); } } } /** * @see com.fluencyvoice.runner.dialog.voice.basic.simulator.SlotCreator#createSlots(Serializable) */ public Map convertObjectToSlotData(Serializable processedObject) { String strValue = (String) processedObject; Map map = new HashMap(); map.put(NumberDialog.SLOT_NAME, strValue); return map; } } /** * Returns the minNumberSize. * @return Long */ public Long getMinNumberSize() {
Speech Applications Builder Component Developers Guide May 15, 2004 page 86 of 97

return minNumberSize; } private long getActualMinNumberSize() { if (minNumberSize == null) { return DEFAULT_MIN_NUM_SIZE; } else { return minNumberSize.longValue(); } } /** * Sets the minNumberSize. * @param minNumberSize The minNumberSize to set */ public void setMinNumberSize(Long minNumberSize) { if ((minNumberSize != null) && (minNumberSize.longValue() < 0)) { throw new IllegalArgumentException("The NumberDialog only supports positive numbers."); } else { this.minNumberSize = minNumberSize; setupDefaultOutOfRangePrompt(); } } /** * Returns the maximum number to be accepted by the caller, or null if the * default is to be used. */ public Long getMaxNumberSize() { return maxNumberSize; } /** * Sets the maximum number to be accepted from the caller; any numbers
Speech Applications Builder Component Developers Guide May 15, 2004 page 87 of 97

* above this will be rejected, with a message informing the caller, and * they will be re-prompted. Setting this to null will mean the default it * used. * <p> * A call to this results in the default out-of-range prompt being reset. */ public void setMaxNumberSize(Long maxNumberSize) { // Check it's not too big (we only support up to 1 million currently) or small if(maxNumberSize!=null && maxNumberSize.longValue()>DEFAULT_MAX_NUM_SIZE) { throw new IllegalArgumentException("The NumberDialog does not support numbers larger than 1 million."); } else if(maxNumberSize!=null && maxNumberSize.longValue()<0) { throw new IllegalArgumentException("The NumberDialog only supports positive numbers."); } else { this.maxNumberSize = maxNumberSize; // Set up the out-of-range prompt again. setupDefaultOutOfRangePrompt(); } } private long getActualMaxNumberSize() { if (maxNumberSize == null) { return DEFAULT_MAX_NUM_SIZE; } else { return maxNumberSize.longValue(); } } public void setNumberStyle(NumberStyle style) { this.numberStyle = style; } public NumberStyle getNumberStyle() {

Speech Applications Builder Component Developers Guide May 15, 2004 page 88 of 97

return numberStyle; } public NumberStyle getActualNumberStyle() { if(numberStyle==null) { return DEFAULT_NUMBER_STYLE; } else { return numberStyle; } } } For the NumberDialogStep: package com.netdecisions.components.dialog.ccp.number; import java.math.BigDecimal; import com.fluencyvoice.dialog.number.NumberDialog; import com.fluencyvoice.runner.core.Dialog; import com.fluencyvoice.runner.core.DialogRequest; import com.fluencyvoice.runner.core.DialogResponse; import com.fluencyvoice.runner.dialog.voice.prompt.BasicDialogPrompt; import com.fluencyvoice.runner.dialog.voice.prompt.ConcatenatedDialogPrompt; import com.netdecisions.agility.fw.kernel.common.BusinessRuleException; import com.netdecisions.agility.fw.services.global.exceptionhandling.ExceptionService; import com.netdecisions.agility.util.log.Log; import com.netdecisions.ccp.StepInitializationException; import com.netdecisions.ccp.dbo.bo.PromptDBODataType; import com.netdecisions.ccp.dbo.bo.WavDBODataType; import com.netdecisions.ccp.dbo.util.DBODataTypeFactory; import com.netdecisions.ccp.model.bp.StepDefinition; import com.netdecisions.ccp.model.bp.folder.InvalidLocationException; import com.netdecisions.ccp.model.bp.folder.SchemaUtils; import com.netdecisions.ccp.model.vr.DialogStep; import com.netdecisions.ccp.model.vr.bo.DialogFolder;
Speech Applications Builder Component Developers Guide May 15, 2004 page 89 of 97

import com.netdecisions.ccp.repository.bo.RepositoryComponent; import com.netdecisions.ccp.repository.bo.document.Document; /** * Wrapper step for the NumberDialog; allows the collection of a number from the * caller. * <p> * Allows the Configurator user to specify prompts, a valid number range, and * where in the folder to put the collected number. */ public class NumberDialogStep extends DialogStep { private static final Log LOG = Log.getLog(NumberDialogStep.class); // Descriptions of this step. private static final String STEP_NAME = "Number Dialog Step"; private static final String STEP_DESCRIPTION = "Gathers a number from the user, handling confirmation and disambiguation."; private static final String STEP_CATEGORY = "Sample Steps"; private static final String STEP_IMAGE = "images/nouns/noun_number.png"; // Pages for the configuration properties private static final String PROMPTS_PAGE = "Prompts"; private static final String NUMBER_OUT_OF_RANGE_PAGE = "Number Out of Range"; private static final String CHOICES_AND_RESULT_PAGE = "Choices and Result"; // Prompt specifying the question to ask the user. private static final String QUESTION_TO_ASK_PROMPT_NAME = "QuestionToAskPrompt"; private static final String QUESTION_TO_ASK_PROMPT_DESCRIPTION = "The question to ask"; private String questionTTS; private WavDBODataType questionWav; // Prompt specifying that the number spoken is out of the allowed range. private static final String NUMBER_OUT_OF_RANGE_PROMPT_NAME = "NumberOutOfRangePrompt";

Speech Applications Builder Component Developers Guide May 15, 2004 page 90 of 97

private static final String NUMBER_OUT_OF_RANGE_PROMPT_DESCRIPTION = "Number out of range prompt"; private String numberOutOfRangeTTS; private WavDBODataType numberOutOfRangeWav; // The lower bound of the allowed range of numbers. private static final String RANGE_FROM_NAME = "RangeFrom"; private static final String RANGE_FROM_DESCRIPTION = "Allowed numbers range from"; private BigDecimal rangeFrom; // The upper bound of the allowed range of numbers. private static final String RANGE_TO_NAME = "RangeTo"; private static final String RANGE_TO_DESCRIPTION = "Allowed numbers range to"; private BigDecimal rangeTo; // The variable in the folder to put the result into. private static final String RESULT_REF_NAME = "ResultantVariable"; private static final String RESULT_REF_DESCRIPTION = "Variable for the Gathered Result"; private String resultantVar;

/** * Creates the definition of this step and its configuration properties. */ public RepositoryComponent createStepTypeDefinition(Document jarFile) throws BusinessRuleException { // Overall step definition RepositoryComponent stepDef = new RepositoryComponent( STEP_NAME, STEP_DESCRIPTION, this.getClass(), STEP_IMAGE, jarFile, STEP_CATEGORY,

Speech Applications Builder Component Developers Guide May 15, 2004 page 91 of 97

6); // Add the main question prompt stepDef.addField( QUESTION_TO_ASK_PROMPT_NAME, QUESTION_TO_ASK_PROMPT_DESCRIPTION, true, PromptDBODataType.class); stepDef.getDboDefinition().getValueDefinition(QUESTION_TO_ASK_PROMPT_NAME) .setPage(PROMPTS_PAGE); // Add the confirmation prompt (as standard) addConfirmation(stepDef); // Add the additional no-input and no-match prompts (as standard) addAdditionalPrompts(stepDef); // Add the number out-of-range prompt stepDef.addField( NUMBER_OUT_OF_RANGE_PROMPT_NAME, NUMBER_OUT_OF_RANGE_PROMPT_DESCRIPTION, true, PromptDBODataType.class); stepDef.getDboDefinition() .getValueDefinition(NUMBER_OUT_OF_RANGE_PROMPT_NAME) .setPage(NUMBER_OUT_OF_RANGE_PAGE); // Add the number range lower and upper bounds stepDef.getDboDefinition().addField( RANGE_FROM_NAME, RANGE_FROM_DESCRIPTION, true, false, DBODataTypeFactory.DECIMAL_NUMBER_CLASS); stepDef.getDboDefinition() .getValueDefinition(RANGE_FROM_NAME) .setPage(CHOICES_AND_RESULT_PAGE); stepDef.getDboDefinition().addField( RANGE_TO_NAME,
Speech Applications Builder Component Developers Guide May 15, 2004 page 92 of 97

RANGE_TO_DESCRIPTION, true, false, DBODataTypeFactory.DECIMAL_NUMBER_CLASS); stepDef.getDboDefinition() .getValueDefinition(RANGE_TO_NAME) .setPage(CHOICES_AND_RESULT_PAGE); // Add the resultant variable reference stepDef.getDboDefinition().addVariableField( RESULT_REF_NAME, RESULT_REF_DESCRIPTION, true, false, SchemaUtils.ALL_XSD_NUMBER_TYPES, true, "xsd:decimal"); stepDef.getDboDefinition() .getValueDefinition(RESULT_REF_NAME) .setPage(CHOICES_AND_RESULT_PAGE); return stepDef; } /** * Returns a human-readable description of this step. */ public String getStepDescription() { return STEP_DESCRIPTION; } /** * Initialises the Dialog Step, performing any configuration required and * validating any input from the user. */ protected void initStep(StepDefinition def) throws StepInitializationException { if (LOG.isDebugEnabled()) {
Speech Applications Builder Component Developers Guide May 15, 2004 page 93 of 97

LOG.debug("Initializing Number Dialog step"); } // Get values PromptDBODataType prompt = getPrompt(QUESTION_TO_ASK_PROMPT_NAME); if (prompt == null) { try { def.setValue(QUESTION_TO_ASK_PROMPT_NAME, (((BasicDialogPrompt) NumberDialog.DEFAULT_QUESTION_PROMPT).getScript())); } catch (BusinessRuleException e) { throw new StepInitializationException("The question to ask is unassigned."); } } questionTTS = prompt.getTTSPrompt(); questionWav = prompt; prompt = getPrompt(NUMBER_OUT_OF_RANGE_PROMPT_NAME); if(prompt != null){ numberOutOfRangeTTS = prompt.getTTSPrompt(); numberOutOfRangeWav = prompt; } rangeFrom = (BigDecimal) def.getValue(RANGE_FROM_NAME); rangeTo = (BigDecimal) def.getValue(RANGE_TO_NAME); resultantVar = (String) def.getValue(RESULT_REF_NAME); if (resultantVar == null) { throw new StepInitializationException("The resultant variable must be selected"); } if (LOG.isDebugEnabled()) { LOG.debug(QUESTION_TO_ASK_PROMPT_NAME + ": " + questionTTS); LOG.debug(QUESTION_TO_ASK_PROMPT_NAME + ": " + questionWav);

Speech Applications Builder Component Developers Guide May 15, 2004 page 94 of 97

LOG.debug(NUMBER_OUT_OF_RANGE_PROMPT_NAME + ": " + numberOutOfRangeTTS); LOG.debug(NUMBER_OUT_OF_RANGE_PROMPT_NAME + ": " + numberOutOfRangeWav); LOG.debug(RANGE_FROM_NAME + ": " + rangeFrom); LOG.debug(RANGE_TO_NAME + ": " + rangeTo); LOG.debug(RESULT_REF_NAME + ": " + resultantVar); } } /** * Creates the wrapped NumberDialog object. */ protected Dialog createDialog() { return new NumberDialog(); } /** * Configures the NumberDialog component, based on the configuration options * stored as instance data in this NumberDialogStep. */ protected void configureDialog(Dialog dialog) { // Cast the Dialog to NumberDialog. NumberDialog numberDialog = (NumberDialog) dialog; // Set up the main question prompt. String wavUrl = null; if(questionWav != null && questionWav.hasAudioFile()){ wavUrl = generateWAVDataURL(QUESTION_TO_ASK_PROMPT_NAME, 0); } if(questionTTS!= null || (questionWav != null && questionWav.hasAudioFile())){ numberDialog.setQuestionPrompt(new BasicDialogPrompt(questionTTS, wavUrl)); } // Set up the confirmation prompt (using base class functionality).
Speech Applications Builder Component Developers Guide May 15, 2004 page 95 of 97

numberDialog.setConfirmationPrompt(getConfirmationPrompt(new NumberDialogPrompt())); // Set up the out-of-range prompt. wavUrl = null; if(numberOutOfRangeWav != null && numberOutOfRangeWav.hasAudioFile()){ wavUrl = generateWAVDataURL(NUMBER_OUT_OF_RANGE_PROMPT_NAME , 0); } if (rangeFrom != null) { numberDialog.setMinNumberSize(new Long(rangeFrom.longValue())); } if (rangeTo != null) { numberDialog.setMaxNumberSize(new Long(rangeTo.longValue())); } if (numberOutOfRangeTTS != null || (numberOutOfRangeWav != null && numberOutOfRangeWav.hasAudioFile())) { ConcatenatedDialogPrompt prompt = new ConcatenatedDialogPrompt(); prompt.addPrompt(new BasicDialogPrompt(numberOutOfRangeTTS, wavUrl)); prompt.addPrompt(new BasicDialogPrompt(" from " + numberDialog.getMinNumberSize())); prompt.addPrompt(new BasicDialogPrompt(" to " + numberDialog.getMaxNumberSize())); numberDialog.setNumberOutOfRangePrompt(prompt); }else{ numberDialog.setNumberOutOfRangePrompt(null); } // Set up no-input/no-match/etc. numberDialog.setNoInputPrompt(getNoInputPrompt()); numberDialog.setNoMatchPrompt(getNoMatchPrompt()); numberDialog.setMaxReprompt(getMaxReprompts()); // Set up thresholds for acceptance, etc. numberDialog.setAcceptanceConfidenceThreshold(getAcceptanceThreshold()); numberDialog.setAcceptanceConfidenceMargin(getAcceptanceMargin()); } /**

Speech Applications Builder Component Developers Guide May 15, 2004 page 96 of 97

* Produces a new request to pass to the NumberDialog. Since it takes no * dynamic data from the folder, a new NumberDialog.StartRequest is simply * returned. */ protected DialogRequest convertToRequest(DialogFolder info) { return new NumberDialog.StartRequest(this); } /** * Receives a response given by the NumberDialog, and adds any result to the * folder. A result is only given in the event that there's a * NumberGatheredResponse issued by the NumberDialog, in which case the * number is added to the folder at the location the Configurator user has * specified. */ protected void addResultToFolderData(DialogFolder folder, DialogResponse response) { if (response instanceof NumberDialog.NumberGatheredResponse) { NumberDialog.NumberGatheredResponse answer = (NumberDialog.NumberGatheredResponse) response; if (LOG.isDebugEnabled()) { LOG.debug("Answer : " + answer); } // Set the result in the folder. try { folder.setValueAt(resultantVar, answer.getNumber().toString()); } catch (InvalidLocationException e) { ExceptionService.of().handleException(e); } } } }

Speech Applications Builder Component Developers Guide May 15, 2004 page 97 of 97