Beruflich Dokumente
Kultur Dokumente
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
Speech Applications Builder Component Developers Guide May 15, 2004 page 5 of 97
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
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
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.
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
DB
Number Dialog
Single Result
Confirm Dialog
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
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
Speech Applications Builder Component Developers Guide May 15, 2004 page 14 of 97
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
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.
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)
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.
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 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.
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).
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.
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).
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
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.
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.
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.
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.
Speech Applications Builder Component Developers Guide May 15, 2004 page 48 of 97
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
Speech Applications Builder Component Developers Guide May 15, 2004 page 51 of 97
// 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
// 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
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
* 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