Voxeo Prism is a converged application platform where you can mingle Web (HTTP) functionality with real-time communication (SIP or XMPP) functionality in a single programming model and application.
A classic example of converged application is Click-To-Dial, where the user can enter two telephone numbers in a web page and the web application calls each phone and connect them together.
In this blog, I will show how to build a simple Click-To-Dial application on Voxeo Prism that can connect two SIP softphones together.
Voxeo Prism supports both Java Servlet 2.5 API and JSP 2.1. It also supports SIP Servlet 1.1 API. So I built this application using JSP and SIP Servlet. Here is how the application, a.k.a. WAR, looks like.
Tutorial.4
|-- WEB-INF
| |-- sip.xml
| |-- web.xml
| |-- xmpp.xml
| |-- sipmethod-application.xml
| |-- classes
| |-- com/voxeo/prism/tutorial/four/Call.class
| |-- com/voxeo/prism/tutorial/four/ClickToDialServlet.class
|-- call.jsp
|-- index.jsp
|-- status.jsp
|-- terminate.jsp
|-- phone.jpg
Once the application is deployed on Voxeo Prism, go to http://localhost:8080/Tutorial.4 to open home page (index.jsp). It will look like the following.

Once you enter two reachable SIP addresses, e.g. sip:john@somedomain.com and sip:doe@otherdomain.com, and click the Call button. The call.jsp will be invoked to make the call to sip:john@somedomain.com and sip:doe@otherdomain.com respectively and join them together when both have answered.
The following diagram shows how the JSPs, Java objects, and SIP servlet interact with each other.

The logic of making outbound calls are in the Call.call() method, as shown below. “_factory” is the SipFactory object. “_left” and “_right” are the URIs submitted from the index.jsp. “_servlet” is the name of the ClickToDialServlet defined in its annotation or deployment descriptor. By setting the servlet as the handler of the session, the servlet will receive all messages for that session.
_inviteL = _factory.createRequest(_session, "INVITE", _left, _right);
_inviteR = _factory.createRequest(_session, "INVITE", _right, _left);
_inviteL.setRequestURI(_right);
_inviteR.setRequestURI(_left);
_inviteL.getSession().setHandler(_servlet);
_inviteL.getSession().setAttribute(Call.class.getName(), this);
_inviteR.getSession().setHandler(_servlet);
_inviteR.getSession().setAttribute(Call.class.getName(), this);
_inviteL.send();
_inviteR.send();
Once both phones have responded with 2xx answers, Call.connect() will generate ACK for each phone with the SDP from the other phone. If one of the phones returns an error response, the call is terminated. Here is the logic in Call.connect() method.
synchronized void connect(SipServletResponse response) {
SipServletResponse otherResponse = null;
int code = response.getStatus();
if (code < 200) {
return;
}
if (_inviteL.getSession().equals(response.getSession())) {
_responseL = response;
otherResponse = _responseR;
}
else if (_inviteR.getSession().equals(response.getSession())) {
_responseR = response;
otherResponse = _responseL;
}
//TODO: handle 3xx for redirection
if(code >= 300) {
terminate();
return;
}
if (otherResponse != null) {
try {
if (response.getStatus() >= 200 && response.getStatus() < 300 && otherResponse.getStatus() >= 200 && otherResponse.getStatus() < 300) {
SipServletRequest ack = response.createAck();
ack.setContent(otherResponse.getRawContent(), otherResponse.getContentType());
SipServletRequest otherAck = otherResponse.createAck();
otherAck.setContent(response.getRawContent(), response.getContentType());
ack.send();
otherAck.send();
_state = State.CONNECTED;
}
// container automatically sends ACK in other cases.
}
catch (Exception e) {
terminate();
}
}
}
When the call is terminated, we have to send either CANCEL or BYE to each telephone. Based on RFC 3261, CANCEL should be sent before receiving the final response. The follow is the code to terminate the call to one telephone.
private void terminate(SipServletRequest invite, SipServletResponse response) {
try {
if (invite != null) {
if (invite.isCommitted()) {
if (response == null) {
invite.createCancel().send();
}
else {
invite.getSession().createRequest("BYE").send();
}
}
}
}
catch (Exception e) {
// nothing we can do at this point
}
}
To try it out, download and deploy the application to <prism>/apps. Open the home page at http://localhost:8080/Tutorial.4. Make sure you type in valid and reachable SIP addresses. By default, PSTN telephone number won’t work unless VoIP gateway or ENUM are configured, which I will show you in the future blog.