Wednesday, March 11, 2009

My JRuby-Rails app on JBoss and Mobicents can make Phone calls !

Following on the previous blog, I'll describe the steps to create a multi language JRuby-Rails application that utilize the power of the Sip Servlets 1.1 specification to make phone calls.

It will be bundled as a war and will be deployed on top of Mobicents Sip Servlets.

So the application will allow one to file complaints and every time a complaint is filed, a confirmation call is made to your phone saying that is has been taken into account and has been routed to a sales representative.

You can download the prebuilt application, if you're not interested in build it yourself and just want to test things out.
Note also that the source code for this app is available here
In any case, make sure you have JRuby correctly setup as explained in my previous post

Deploy the war to your favorite Mobicents Sip Servlets container. Currently only the current trunk (0.9-SNAPSHOT) is able to work correctly with a JRuby/Rails - Sip Servlets app, you can find the corresping binary snapshots of the trunk on our hudson job

Copy the war into your $JBOSS_HOME/server/default/deploy directory($JBOSS_HOME points to the location where you extracted Mobicents Sip Servlets zip) and then starts the jboss container as usual with
$ sh $JBOSS_HOME/bin/run.sh
When started, go to http://localhost:8080/sip-servlets-management and remove all configured applications in clicking on all the Delete buttons then click on Save.

You're ready to test the application. Starts your favorite Sip Phone (wengo phone, linphone, ekiga, sip communicator, ...) then go to http://localhost:8080/jruby-demo-sip-servlet-1.0-SNAPSHOT/complaints

Create a new complaint and make sure that in the sip uri field you put the address of the sip phone as shown here


Now enjoy your first JRuby Rails Sip-Servlets application making a call to your sip phone.
Note that with some hacking and a VoIP provider such as http://www.callwithus.com, it could call real land-line phones or cell phones.

The next step now is to integrate with the JBoss Rails Deployer and add to it the ability to recognize those converged telco applications, so that you don't need to recreate the war everytime you change the rails part of the app and benefit from the rails features of live modification and also of the JBoss enterprise features in your Rails application !

Also if you want to help us and contribute check our Google Summer of Code project ideas for Mobicents

For the hackers that want to create it themselves here are the steps :
(Note that the prebuilt application is integrated with Mobicents Media Server and as such has the media features of playing the audio but we will not see that below, it will just showcase the call setup.)

So let's create the application skeleton :
$ jruby -S rails jruby-sips-demo -d mysql

Go into the “jruby-sips-demo” directory, then modify the config/database.yml.
Adjust the adapter name, and instead of ‘mysql’ put ‘jdbcmysql’. You might also want to delete the lines starting with “socket:” or set it to tmp dir.

Here’s a simple example for the development environment:
development:
adapter: jdbcmysql
encoding: utf8
database: blog_development
pool: 5
username: root
password:
socket: /tmp/mysqld.sock

Also edit the config/environment.rb to specify the gem dependency we have on the jdbcmysql adapter (this step is mandatory for freezing the dependencies in your app later on)

Rails::Initializer.run do |config|
...
config.gem "activerecord-jdbcmysql-adapter", :version => '0.9', :lib => 'active_record/connection_adapters/jdbcmysql_adapter'
...
end

Now, it’s time to create our database:

$ jruby -S rake db:create:all

The next step is to create some minimal scaffolding to create the complaint system

$ jruby script/generate scaffold Complaint customer_name:string company:string complaint:text sip_uri:string

$ jruby -S rake db:migrate

now, we will add the logic to make the phone call once a complaint has been created, to do that edit app/controllers/complaints_controller.rb and the create function should look like this :


def create
@complaint = Complaint.new(params[:complaint])

respond_to do |format|
if @complaint.save
# get the sip factory from the servlet context
@sip_factory = $servlet_context.get_attribute('javax.servlet.sip.SipFactory')
# create a new sip application session
@app_session = request.env['java.servlet_request'].get_session().get_application_session();
# create a new sip servlet request to start a call to the sip phone with from header equals to "sip:my_jruby_app_rocks@mobicents.org" and the to header equals to the sip_uri from the complaint
@sip_request = @sip_factory.create_request(@app_session, 'INVITE', 'sip:my_jruby_app_rocks@mobicents.org', @complaint.sip_uri);
# actually sending the request out to the sip phone
@sip_request.send();

flash[:notice] = 'Complaint was successfully created.'
format.html { redirect_to(@complaint) }
format.xml { render :xml => @complaint, :status => :created, :location => @complaint }
else
format.html { render :action => "new" }
format.xml { render :xml => @complaint.errors, :status => :unprocessable_entity }
end
end
Ok we are done with the rails, let's create the war so that we can deploy it on a Java EE compliant container such as JBoss, for that we need to install Warbler :
$ jruby -S gem install -y jruby-openssl warbler
and set it up for our application with :
$ jruby -S warble config
Using jdbcmysql adapter, don't forget to uncomment this line in config/warble.rb:
config.gems += ["activerecord-jdbcmysql-adapter"]
Create the .war :
$ jruby -S warble war
Ok we are done with the rails app, now we need to create the java Sip Servlets code that will handle SIP related requests and responses and package it with the war.

So let's create the directory structure for the java classes :
$ mkdir -p src/main/java/org/mobicents/servlet/sip/demo/jruby
mkdir -p src/main/sipapp/WEB-INF
Now let's add the Sip Servlet class that will handle the SIP calls in src/main/java/org/mobicents/servlet/sip/demo/jruby :


public class JRubySipServlet extends SipServlet {

@Override
protected void doSuccessResponse(SipServletResponse resp) throws ServletException, IOException {
//acknowledge that the call is accepted by the phone
if (resp.getStatus() == SipServletResponse.SC_OK) {
SipServletRequest ack = resp.createAck();
ack.send();
}
}

@Override
protected void doBye(SipServletRequest request) throws ServletException, IOException {
//respond to the hangup request
SipServletResponse ok = request.createResponse(SipServletResponse.SC_OK);
ok.send();
}
}

Now let's create the sip.xml deployment descriptor in src/main/sipapp/WEB-INF :

<?xml version="1.0" encoding="UTF-8"?>
<sip-app>
<app-name>org.mobicents.servlet.sip.demo.jruby.JRubySipServletApplication</app-name>

<servlet>
<servlet-name>JRubySipServlet</servlet-name>
<display-name>JRubySipServlet</display-name>
<description>JRuby SIP servlet</description>
<servlet-class>
org.mobicents.servlet.sip.demo.jruby.JRubySipServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
</sip-app>

Now let's create the web.xml deployment descriptor in src/main/sipapp/web.xml so that the application uses the development database


<web-app>
<context-param>
<param-name>rails.env</param-name>
<param-value>development</param-value>
</context-param>

<context-param>
<param-name>public.root</param-name>
<param-value>/</param-value>
</context-param>

<context-param>
<param-name>jruby.max.runtimes</param-name>
<param-value>1</param-value>
</context-param>

<filter>
<filter-name>RackFilter</filter-name>
<filter-class>org.jruby.rack.RackFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>RackFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<listener>
<listener-class>org.jruby.rack.rails.RailsServletContextListener</listener-class>
</listener>
</web-app>

Now let's tie everything together by creating a maven pom.xml to bundle the jruby app and the sip servlets code together in a single war so create the pom.xml at the root of your project

<project xmlns="http://maven.apache.org/POM/4.0.0" xsi="http://www.w3.org/2001/XMLSchema-instance" schemalocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelversion>4.0.0</modelversion>
<parent>
<groupid>org.mobicents.servlet.sip.example</groupid>
<artifactid>sip-servlets-examples-parent</artifactid>
<version>1.2</version>
<relativepath>../pom.xml</relativepath>
</parent>
<groupid>org.mobicents.servlet.sip.example</groupid>
<artifactid>jruby-demo-sip-servlet</artifactid>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>JRuby Sip Servlet Demo Application</name>
<url>http://www.mobicents.org/jruby-sip-servlets.html</url>

<build>
<plugins>
<plugin>
<artifactid>maven-compiler-plugin</artifactid>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
<plugin>
<artifactid>maven-war-plugin</artifactid>
<configuration>
<warsourcedirectory>
${basedir}/src/main/sipapp
</warsourcedirectory>
<webresources>
<resource>
<directory>tmp/war</directory>
<excludes>
<exclude>**/web.xml</exclude>
</excludes>
</resource>
<resource>
<directory>log</directory>
<!-- override the destination directory for this resource -->
<targetpath>WEB-INF/log</targetpath>
</resource>
</webresources>
</configuration>
</plugin>
</plugins>
</build>
</project>

Create the converged jruby sip servlets war with
$ mvn clean install

6 comments:

  1. Wow, extensive walkthrough! Congrats on getting it all to work :) You should post to JRuby ML about your experience or add something to wiki.jruby.org!

    ReplyDelete
  2. Hello,

    please consider adding your site at www.sweebs.com where other people can find you among the best sites found on the internet.

    regards
    Kris

    ReplyDelete
  3. Nice! Elegant solution to bridging Ruby with a powerful and scalable telephony platform.

    ReplyDelete
  4. @Charles Oliver Nutter : Thx ! I just did that. I added the link to the list of Related Web Sites section. I also added a "JRuby on Rails on JBoss" to the JRUby on Rails section.

    ReplyDelete
  5. This comment has been removed by a blog administrator.

    ReplyDelete