Building a
Telephony Web Application
By: Rauf Qureshi
May 1, 2007
Abstract:
This short tutorial shows basic steps involved in putting together a telephony
web application. A typical telephony web application
provides features that can be accessed via the web as well as via the telephone. The example application
provides stock quotes through the web as well as telephony interfaces to registered users.
We will examine building the web interface using Java ServerFaces and the telephony interface
using VoiceXML. The application uses a Model-View-Controller approach in order
to keep the presentation separate from business rules. The presentation layers will be created using Java ServerFaces for the
visual and VoiceXML for the audio/spoken interface. Both types of presentations utilize common programming source
which consists of java beans and controllers. Hibernate is used for the database persistence.
Telephone and web interfaces utilize the common database.
1. Building Blocks
Now that I have given away my thunder in the abstract above, the application in this tutorial was a part of a prototype that I put together not too long ago. This tutorial is based upon lessons learned during that process. Let us examine the building blocks of the telephony/web application under consideration. This application allows registered users to view their stock quotes via the web once signed in. The application retrieves real time (disclaimer: 20 minutes delayed) stock quotes from Yahoo and displays results on the screen. The telephony part of this application announces the stock quote results over the phone by capturing the ANI (calling phone number) and matching it to the existing profiles.
New users can register on the web by following simple links. Once registered, user can create a profile by entering ticker symbols of their favorite stocks that need to be monitored. The telephone number where calls will be made from can also be entered in the profile. The telephony part of the application uses ANI as the authentication mechanism.
1.1 System Architecture: The diagram in Figure 1 below provides a system level overview. A telephony-web application is typically deployed on two separate servers; Application server and a Voice server.
1.1.1 Voice Server:
The voice server is responsible for communicating with the user over the telephone and perform telephony tasks.
The voice server pulls VoiceXML, grammar and prompt files from the application server over the internet or a dedicated
link based upon the requirements.
The voice browser in the voice server uses JSP based
VoiceXML files to collect speech input from the user over the telephone and presents to the inner guts of the voice
server for processing. The voice browser also presents the output to the user in the form of spoken words
over the telephone.
Note that the voiceXML code itself is neither generated
nor stored by the voice server. The voice server fetches the voicexml code from the web application server over the internet.
In some cases, voice server and application servers can be co-located in order to reduce dependency on the internet link
between the two.
Figure 1. Telephony Web System Architecture - overview
Main components of a voice server are listed below:
A great amount of effort is spent in usability analysis and voice dialogue building techniques during the course of a telephony web application design, which are out of scope of this tutorial.
1.1.2 Web Application Server:
The web application server houses the database and necessary java codes that are used to communicate
with the user of the application and perform tasks related to business logic.
The web application server in this demo consists of the following:
1.2 Application Architecture:
The high level architecture is described in the Figure 2 below: A common database is used to store user profiles
and preferences. The application interfaces with the database using Hibernate. Java beans are used to define
business rules, while controllers and helper classes use Hibernate data persitence in order to perform database
related tasks. Moreover, the presentation layers, which are Java ServerFaces for the web and VoiceXML for the telephony,
use controllers in order to present results and process input from the user, both from the web as well as over the phone.
Figure 2. Telephony Web application Architecture - 100 feet view
2. Use Cases
The use cases in this example application are as follows:
Let us examine the java code and database schema which comprise this application. The database schema in this demo is pretty simple. There is only one table JSFVXMLUSERS, which is used to store user profiles. Although database persitence using Hibernate is an overkill for an application comprising of only one table in the database, I wanted to show how Hibernate can be configured and used for this application. Once you get comfortable with the source code, you may add tables and database calls.
One of the main advantages of using Hibernate is that you do not have to worry about the database related java code using JDBC API's. Hibernate also allows you to get away by not having to learn SQL, although some pundits swears against that :-). Seriously, Hibernate provides an easy way to add, remove, update and query database records. You can learn more by visiting Hibernate related pages. There is tons of material out there that can help you in learning and mastering the database persistence using Hibernate. Google is your best friend.
Anyhow, Hibernate uses a configuration file where database specific parameters are defined.
Mapping files, which relate database tables to java beans are also defined in the Hibernate configuration file.
Listing 1. Hibernate configuration settings
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost/dbURL</property>
<property name="connection.username">username</property>
<property name="connection.password">password</property>
<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size">5</property>
<!-- SQL dialect - This is where type of database is defined-->
<property name="dialect">org.hibernate.dialect.MySQLMyISAMDialect</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">update</property>
<property name="cglib.use_reflection_optimizer">false</property>
<!-- Add below, with full path, the individual mapping files for each table-->
<mapping resource="user.hbm.xml" />
</session-factory>
</hibernate-configuration>
|
Next step is defining a Hibernate Mapping file, which maps columns in the table to the java beans.
Listing 2. Hibernate mapping between the java bean parameters and table columns.
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.hunza.jsfvxml.bus.User" table="JSFVXMLUSERS">
<!-- A 32 hex character is our surrogate key. It's automatically
generated by Hibernate with the UUID pattern. -->
<id name="userId" column="USERID">
<generator class="increment"/>
</id>
<property name="email">
<column name="EMAIL"/>
</property>
.....
<property name="ani">
<column name="ANI"/>
</property>
</class>
</hibernate-mapping>
|
A java bean is used to define parameters for the user, getters and setters. See the listing below:
Listing 3. Java bean representing user properties.
package com.hunza.jsfvxml.bus;
public class User {
/* The purpose of this class is to define a bean to represent a user.
*/
private String userName, password, email;
/*
* tickerSymbols is a space separated string consisting of ticker symbols
*/
private String tickerSymbols;
private String ani, firstName;
private Integer userId;
// Getters
public String getUserName() { return userName; }
public String getPassword() { return password; }
public String getEmail() { return email; }
public String getTickerSymbols() { return tickerSymbols;}
public String getAni() { return ani;}
public String getFirstName() { return firstName;}
public Integer getUserId() { return userId; }
//Setters
public void setUserName(String s) { this.userName = s; }
public void setPassword(String s) { this.password = s; }
public void setEmail(String s) { this.email = s; }
public void setUserId(Integer I) { this.userId = I;}
public void setAni(String s) { this.ani = s;}
public void setFirstName(String s) { this.firstName = s;}
public void setTickerSymbols(String S) { this.tickerSymbols = S;}
}
|
A helper class is used by the presentation controller, userController, to manage database related tasks
through the database persistence. The helper class sits between the presentation controller and the Hibernate.
Listing 4. Helper class used by the presentation Controller.
package com.hunza.jsfvxml.db;
import java.util.*;
import org.hibernate.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.hunza.jsfvxml.bus.User;
import com.hunza.jsfvxml.Hibernate.HibernateUtil;
import com.hunza.jsfvxml.exceptions.*;
public class UserManager {
protected final Log logger = LogFactory.getLog(getClass());
/**
* Updates a user record in the database.
* @return true if the user is updated successfully
* @return false if the user could not be updated.
*
*/
public boolean updateUser(User user) {
boolean result = false;
try {
logger.debug("Updating user");
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
session.update(user);
tx.commit();
HibernateUtil.closeSession();
logger.debug("User updated successfully");
result = true;
} catch (HibernateException ex) {
logger.error("Error while updating the user: " + ex);
}
return result;
}
/**
* Returns a user in the form of user bean by matching the ANI passed in the
* argument to the ANI of the user record in the database.
*
* @return - null if user not found.
*/
public User findUserByAni(String ani) {
User user = new User();
boolean userFound = false;
List list = new ArrayList();
try {
logger.debug("Finding user with ANI: " + ani);
Session session = HibernateUtil.getSession();
session.beginTransaction();
list = session.createQuery("from User user where user.ani = '" + ani + "'").list();
if (list.size() == 1) {
logger.debug("One user matching the ani");
user = (User)list.get(0);
userFound = true;
}
else {
logger.error("There are either more than one users with this ANI..");
logger.error("or no user was found with the matching ani!");
}
HibernateUtil.closeSession();
} catch (HibernateException ex) {
logger.error("Error while getting the user: " + ex);
}
if (userFound) {
logger.debug("returning user");
return user;
}
else {
logger.info("user not found!");
return null;
}
}
}
|
Controllers have dual purpose; well just one! A controller class sits behind the presentation layer;
whether via the web or via the telephone and sends the output to the presentation layer and processes the input collected
by the presentation layer.
A section of the Controller is given below:
Listing 5. Controller class used by the JSF as well as VoiceXML pages.
package com.hunza.jsfvxml.web;
import javax.faces.context.FacesContext;
import javax.faces.application.FacesMessage;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.hunza.jsfvxml.db.UserManager;
import com.hunza.jsfvxml.bus.User;
import com.hunza.jsfvxml.util.jsfUtils;
public class UserController {
private User user = new User();
private String password2;
protected final Log logger = LogFactory.getLog(getClass());
/*
* UserController initializer that extracts the user from the session, if one exists already.
*/
{
if ( null != FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("user"))
this.user = (User)FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("user");
}
public User getUser() {
return user;
}
public void setUser(User u) {
this.user = u;
}
public String getPassword2() { return password2;}
public void setPassword2(String s) { this.password2 = s;}
public String signUp() {
String result = "failure";
logger.debug("Entering signUp");
FacesContext context = FacesContext.getCurrentInstance();
if ( null != getUser().getPassword() &&
null != getPassword2() &&
!getUser().getPassword().equals(getPassword2())) {
logger.error("passwords are not identical");
FacesMessage message = new FacesMessage("passwords not identical!");
context.addMessage("signUpForm", message);
}
else if ((new UserManager()).findUser(getUser().getUserName())) {
logger.info("user already exists!");
FacesMessage message = new FacesMessage("Email already exists!");
context.addMessage("signUpForm", message);
}
else {
(new UserManager()).addUser(getUser());
logger.info("Adding user bean in the session");
jsfUtils.addToSession("user",getUser());
result="success";
}
return result;
}
public String logout(){
logger.debug("Signing out: " + user.getEmail());
logger.info("Removing user bean from the session");
jsfUtils.removeFromSession("user");
// TODO - clean the session before handing over the control
return "success";
}
/*
* Updates the user record in the database. Calls the updateUser() method in the UserManager
*/
public String updateUser() {
if (new UserManager().updateUser(getUser())) {
logger.info("User updated successfully!");
return "success";
}
else {
logger.error("User could not be updated!");
return "failure";
}
}
}
|
The presentation layer for the web is created in Java ServerFaces. A JSF page utilizes JSF tags, which
in turn spawns an html file, which is interpreted by the web browsers (Internet Explorer, Firefox etc). The JSF pages
collect the user input, check for the input errors based upon the defined rules, display error messages and hand over
the collected input to the Controller. JSF pages also present results on the web provided by the controller.
Listing 6. Java ServerFaces - tickers.jsp
< f:subview id="tickers">
< f:loadBundle basename="messages" var="prompts"/>
< h:form>
< h:outputText styleClass="resource_selected" value=" You are signed in as: "/>
< h:outputText styleClass="resource_selected" value="#{userController.user.email}"/>
< f:verbatim><br><br></f:verbatim>
< h:dataTable id="tickers"
value="#{tickerController.tickers}"
var="ticker"
rowClasses="oddRow, evenRow"
headerClass="tableHeader">
<!-- Ticker Symbol -->
< h:column>
< f:facet name="header">
< h:panelGroup>
< h:outputText value="Ticker"/>
</h:panelGroup>
</f:facet>
< h:outputText value="#{ticker.tickerName}"/>
</h:column>
<!-- Item Price -->
< h:column>
< f:facet name="header">
< h:panelGroup>
<h:outputText value="Price"/>
</h:panelGroup>
</f:facet>
< h:outputText value="#{ticker.price}"/>
</h:column>
<!-- Trade Date -->
< h:column>
< f:facet name="header">
< h:panelGroup>
<h:outputText value="Trade Date"/>
</h:panelGroup>
</f:facet>
< h:outputText value="#{ticker.tradeDate}"/>
</h:column>
</h:dataTable>
</h:form>
</f:subview>
|
The presentation layer for the telephony part is created using Java ServerPages, which in turn spawns a VoiceXML file. A
VXML file is interpreted by the voice browser. The voice browser collects input from the caller over the phone, validates
the input based upon the defined rules, prompts over error messages and hand over
the collected input to the Controller. JSP based VoiceXML pages also narrate results on the telephone provided by the
controller.
Listing 7. Java ServerPages based voiceXML file
< form id="ticker" scope="dialog"> < block name="block61"> < prompt bargein="true"> Ticker Symbol <%= ticker.getTickerName() %> < break time="500"/> Price < say-as interpret-as="currency">$<%= ticker.getPrice() %></say-as> < break time="500"/> </prompt> </block> </form> </vxml> |
Listing 8. Static grammar files
MAINMENU
[LIB_YES_NO:s { < yesno $s > }]
LIB_YES_NO[
[
yes
yeah
yup
sure
okay
correct
right
(?(?yes that's) [right correct])
(?yes it is)
(you got it)
(yes i do)
(yes i would)
(yes it is correct)
] { return("yes") }
[
no
nope
incorrect
(no way)
(no it isn't)
(?no[it's that's] not [correct right])
(?no it isn't)
(?no it is not)
(?no it's not)
(no i don't)
(no i do not)
(no i wouldn't)
] { return("no") }
]
|
4. Compiling the Source Code
The source code of this demo application can be downloaded here.
source code
There is no copyright on the code. Feel free to modify the code.
Go ahead and compile and the code. The source code also contains ANT files that can be
used to create a war deployment file. After compiling, deploy the application on your favorite Tomcat server.
There are a number of developer sites that offer voiceXML development environment. The demo
application in this example has been deployed using Voxeo Community.
In order to view the application demo, use this link.
5. Accessing the demo
For the web interface, click here. This will take you to the page where you can sign in or sign
up and create your profile.
For the telephony part, the example application can be accessed by dialing the voice server directly at
407-418-1961.
References and Acknowledgements
The demo tutorial is hosted on Voxeo Community site. There are some neat tutorials on that site for those who wish to dig more.
| wrote on Fri May 25, 2007 - 2:54 AM Dude , the color is too annoying, do something |
| Rauf wrote on Sat May 26, 2007 - 3:53 PM I am not sure what color you were referring to. I have modified the background color where I show the code to light yellow. Hope it helps.
BTW, the code of the tutorial is available as well by following the link above!
Thanks
Rauf
|
| Abdul Waheed wrote on Mon Jul 9, 2007 - 5:55 PM Excellent work. |
| sreevinod nair wrote on Fri Aug 10, 2007 - 7:35 AM Thank you for the code.Excellent work.It may help thousands like me |
| Rauf wrote on Fri Aug 10, 2007 - 8:41 AM Sreevinod,
I appreciate the comments and glad that you found the article helpful. |
| santhyaa wrote on Tue Aug 21, 2007 - 12:22 AM need code fa implementation |
| waqas wrote on Wed Sep 26, 2007 - 11:29 AM There is only web-code, i need the VoiceXML code too..
and i have downloaded Voxeo Prophecy Voice Platform, i want to know how to implement my application there, i am using Tomcat as webserver |
| Rauf wrote on Fri Sep 28, 2007 - 9:48 AM There are no, as such, VoiceXML or vxml files in the code. The code provided through the link includes jsp files. The JSP files span the VoiceXML files. The voicexml XML files are dynamically generated when JSP files are processed by the application server (Tomcat, for example). The dynamically generated vxml files are then processed by the voice browser.
Hope it helps. |
| Shan wrote on Tue Nov 27, 2007 - 7:07 AM Hi, I have downloaded the code and when I try to Build and deploy the code. I am having problem it fails while deployment using Netbeans-5.5.1 ...Please help..Thanks |
| Sushant wrote on Mon Dec 3, 2007 - 12:41 AM Hi,
I am new for such type of IVR application development. I want to build an application using VXML or CCXML. Can anyone suggest me ....If I want to use Tomcat 5.5 as my webserver? what are the things I need to be managed......So that I can keep my Voice-server as Voxeo service and my Web-server will be configured on my side. Please Help...Thanks
|
| Rauf wrote on Tue Dec 4, 2007 - 9:22 AM Shan, You may want to post errors that your IDE is producing to Netbeans related forums. I suspect the errors are IDE specific. The code provided in this tutorial is IDE independent and should compile in any IDE.
Hope it helps! |
| Rajesh wrote on Thu Dec 20, 2007 - 10:13 AM Great, thanks for knowledge-sharing. |
| Pradeep Bhat wrote on Fri Jan 25, 2008 - 8:17 PM nice article.
Pradeep Bhat
Bristol |
| Rahul Anand wrote on Sat Feb 23, 2008 - 5:21 PM Hi Rauf,
Hmm..the article looks so impressive and helpful.
Can you please starighten down aspects like like View/VXML caching and other stuffs which can certainly help people in straighten their respective implementation in terms of its performance. |
| Rahul Anand wrote on Sat Feb 23, 2008 - 5:21 PM Hi Rauf,
Hmm..the article looks so impressive and helpful.
Can you please starighten down aspects like like View/VXML caching and other stuffs which can certainly help people in straighten their respective implementation in terms of its performance. |