CDI-Telco-Framework
With the advent of JSR 299 CDI it was about time to present a framework for Sip Servlets based on CDI and so CDI-Telco-Framework is here. Check the release announcement in the Mobicents google group.
In this article I will try to provide an overview of what CDI-Telco-Framework can do for you and a tutorial for your first application based on CTF.
Why CTF?
CDI provides type safe dependency injection and contextual lifecycle management for stateful objects. CTF is a new framework for developing SipServlets applications based on the JSR-299 CDI specification which brings all the goodies of CDI into the SipServlets context.
CTF mission statement is:
- providing dependency injection and contextual lifecycle management to the SipServlets
- simplify SipServlets development by introducing a clean programming model
- ease of development by making available SIP utilities out of the box
CTF is available for MSS 1.x on Tomcat 6.x or JBoss AS5 and MSS 2.x on Tomcat 7.x
Introduction
One of the most important aspects of the framework is the fact that removes the Sip Servlets programming model. Even though in this first release you will still have to use several SIP elements such as SipServletRequest,SipServletResponse or SipSession, inside your methods in order to apply the business logic, the model is significant simplified. What that means is that there is no need for extends SipServlet, a simple POJO can do the job. No need to override the Sip Servlets methods also, even method names could be any arbitrary name that developer finds suitable for the project, for example handleReg could be the name of a method handling Sip Registration requests. All you need, is to make use of some CDI/CTF annotations in order to declare that a method will process Registration messages for example, or that you will need to get injected the SipSession. Future development will provide more features and will further simplify the ways to get things done.
Using CTF you will make extensive use of the @Inject and @Observes together with several CTF provided annotations.
- @Inject will be used whenever you need to inject a bean in your class. Such a bean could be project specific bean or a CTF provided bean such as SipFactory or SipSession
- @Observes + CTF annotations will be used in method parameters when the method in question needs to be notified for SIP messages, either requests or responses. CTF provides annotations for all the SIP methods that Mobicents Sip Servlets supports. One example is @Observes @Register that will notify your method for Sip Registration requests
Enable CTF in your application
In order to switch on CTF for an application the following requirements must be met:
- Register the CTF listener in the web.xml or sip.xml
- Provide the beans.xml file inside WEB-INF or META-INF
- Provide the appropriate dependencies which would be Weld and CTF
Lets discuss each one of the above in little bit more detail.
CTF Listener
The framework provides a context listener that needs to get registered in your application in order to bootstrap CTF. The listener is:
org.mobicents.servlet.sip.weld.environment.servlet.SipServletsWeldListener
You should define the listener either in the web.xml or in the sip.xml.
beans.xml
The beans.xml file is special marker file that will provide a hint to the container that this application will make use of CDI. Even though you can configure several stuff for CDI within this file, an empty file will also do the job.
<beans></beans>Required dependencies
The dependencies required will be the framework itself and JBoss Weld, the reference implementation of CDI.
I will provide here the dependencies in terms of maven, but it will be easy to download the artifacts and use them as standalone jars in your application in case you don’t want to go with maven.
- For MSS 1.x and MSS 2.x on Tomcat the following are needed
- The JBoss Weld:
<dependency> <groupId>org.jboss.weld.servlet</groupId> <artifactId>weld-servlet</artifactId> <version>1.1.0.Final</version> </dependency>
- The CTF:
<dependency> <groupId>org.mobicents.servlet.sip.weld</groupId> <artifactId>sip-servlets-weld</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency>
- For MSS 1.x on JBoss AS5 the following are needed
- The JBoss Weld:
<dependency> <groupId>org.jboss.weld.servlet</groupId> <artifactId>weld-servlet</artifactId> <version>1.1.0.Final</version> <classifier>jboss5</classifier> </dependency>
- The CTF:
<dependency> <groupId>org.mobicents.servlet.sip.weld</groupId> <artifactId>sip-servlets-weld-jboss5</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency>
You should register for the JBoss public repository in order to be able to retrieve these dependencies:
<repository> <id>jboss-public-repository-group</id> <name>JBoss Public Maven Repository Group</name> <url>https://repository.jboss.org/nexus/content/groups/public</url> <layout>default</layout> <releases> <enabled>true</enabled> <updatePolicy>never</updatePolicy> </releases> <snapshots> <enabled>true</enabled> <updatePolicy>never</updatePolicy> </snapshots> </repository>
CTF injectable beans
The framework will make available to an application several beans that the developer can inject making use of the @Inject annotation.
- SIP utilities
- SipFactory
- SipSessionsUtil
- TimerService
- SipSession
- SipApplicationSession
- ServletContext
So you can use
1 2 3 4 5 | @Inject
SipFactory sipFactory;
@Inject
TimerService |
and you will have available in your class SipFactory and TimerService.
Click2Call example
In the tutorial part of this article I will present the click2call example that comes with the CTF distribution. The application is the classic click2call example but using asynchronous processing of request, a new feature of Java Servlets 3.0, and of course CTF. The application will deploy to MSS 2.x on Tomcat 7.x and you can get the source code from here.
Project Setup
The project for this tutorial will use maven to manage dependencies and build. Here is the pom.xml file that defines the required dependencies and the JBoss repository.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> ... <dependencies> <!-- logging dependency --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.14</version> <scope>provided</scope> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging-api</artifactId> <version>1.0.4</version> <scope>provided</scope> </dependency> <!-- j2ee dependencies --> <dependency> <groupId>javax</groupId> <artifactId>javaee-web-api</artifactId> <version>6.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.mobicents.servlet.sip</groupId> <artifactId>sip-servlets-spec</artifactId> <version>2.0.0-SNAPSHOT</version> <scope>provided</scope> </dependency> <!-- Weld dependencies --> <dependency> <groupId>org.mobicents.servlet.sip.weld</groupId> <artifactId>sip-servlets-weld</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.jboss.weld.servlet</groupId> <artifactId>weld-servlet</artifactId> <version>1.1.0.Final</version> </dependency> </dependencies> <!-- repositories --> <repositories> ... <repository> <id>JbossRepository</id> <name>Jboss Repository</name> <url>http://repository.jboss.org/maven2</url> <snapshots> <enabled>true</enabled> </snapshots> <releases> <enabled>true</enabled> </releases> </repository> <repository> <id>jboss-snapshots</id> <name>JBoss Snapshot Repository</name> <url>http://snapshots.jboss.org/maven2</url> <releases> <enabled>false</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> ... </repositories> </project> |
Next step is to define CTF listener org.mobicents.servlet.sip.weld.environment.servlet.SipServletsWeldListener in the web.xml.
1 2 3 4 5 | <web-app> <listener> <listener-class>org.mobicents.servlet.sip.weld.environment.servlet.SipServletsWeldListener</listener-class> </listener> </web-app> |
Please note the absence of servlet declaration in the web.xml since Java Servlet 3.0 spec allow us to declare them using annotations. There is no sip.xml also since Sip Servlets 1.1 allow us to declare sip servlets using annotations too.
One last requirement implied by the CDI spec is the beans.xml file. This file must reside in WEB-INF or META-INF folder and can be empty since it will be used only as special marker file.
1 | <beans></beans> |
Now we are ready to add the required functionality using CTF. We will skip the web servlet though and we will concentrate on the SIP servlet where CTF comes to assist.
Take a look at the SimpleSipServlet class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | public class SimpleSipServlet { private static final long serialVersionUID = 1L; private static Logger logger = Logger.getLogger(SimpleSipServlet.class); private static final String CONTACT_HEADER = "Contact"; /* * Inject the required beans */ //SipFactory injection @Inject private SipFactory sipFactory; //sipRegistar will process the Registration request @Inject SipRegistarModule sipRegistar; //calls bean will be used to keep track of the active calls @Inject CallStatusContainer calls; //event bean will be used to notify StatusServlet for new events such as a new call is placed @Inject Event<String> event; public SimpleSipServlet() { } /* * The following methods will handle SIP requests and responses. Using CTF, in order to get notified for a SIP request or response you * have to register for the appropriate event by using @Observe @SipEvent. The name of the method is insignificant but here we kept the * names of methods to doMethod, similar to SipServlet methods, for the sake of clarity. */ protected void doInvite(@Observes @Invite SipServletRequest req) throws ServletException, IOException { logger.info("Click2Dial don't handle INVITE. Here's the one we got : " + req.toString()); } protected void doOptions(@Observes @Options SipServletRequest req) throws ServletException, IOException { logger.info("Got : " + req.toString()); req.createResponse(SipServletResponse.SC_OK).send(); } protected void doSuccessResponse(@Observes @SuccessResponse SipServletResponse resp) throws ServletException, IOException { logger.info("Got OK"); SipSession session = resp.getSession(); if (resp.getStatus() == SipServletResponse.SC_OK) { Boolean inviteSent = (Boolean) session.getAttribute("InviteSent"); if (inviteSent != null && inviteSent.booleanValue()) { return; } Address secondPartyAddress = (Address) resp.getSession() .getAttribute("SecondPartyAddress"); if (secondPartyAddress != null) { SipServletRequest invite = sipFactory.createRequest(resp .getApplicationSession(), "INVITE", session .getRemoteParty(), secondPartyAddress); logger.info("Found second party -- sending INVITE to " + secondPartyAddress); String contentType = resp.getContentType(); if (contentType.trim().equals("application/sdp")) { invite.setContent(resp.getContent(), "application/sdp"); } session.setAttribute("LinkedSession", invite.getSession()); invite.getSession().setAttribute("LinkedSession", session); SipServletRequest ack = resp.createAck(); invite.getSession().setAttribute("FirstPartyAck", ack); invite.getSession().setAttribute("FirstPartyContent", resp.getContent()); Call call = (Call) session.getAttribute("call"); // The call links the two sessions, add the new session to the call call.addSession(invite.getSession()); invite.getSession().setAttribute("call", call); invite.send(); session.setAttribute("InviteSent", Boolean.TRUE); } else { String cSeqValue = resp.getHeader("CSeq"); if(cSeqValue.indexOf("INVITE") != -1) { logger.info("Got OK from second party -- sending ACK"); SipServletRequest secondPartyAck = resp.createAck(); SipServletRequest firstPartyAck = (SipServletRequest) resp .getSession().getAttribute("FirstPartyAck"); // if (resp.getContentType() != null && resp.getContentType().equals("application/sdp")) { firstPartyAck.setContent(resp.getContent(), "application/sdp"); secondPartyAck.setContent(resp.getSession().getAttribute("FirstPartyContent"), "application/sdp"); // } firstPartyAck.send(); secondPartyAck.send(); } } } } protected void doErrorResponse(@Observes @ErrorResponse SipServletResponse resp) throws ServletException, IOException { // If someone rejects it remove the call from the table calls.removeCall(resp.getFrom().getURI().toString(), resp.getTo().getURI().toString()); calls.removeCall(resp.getTo().getURI().toString(), resp.getFrom().getURI().toString()); } protected void doBye(@Observes @Bye SipServletRequest request) throws ServletException, IOException { logger.info("Got bye"); SipSession session = request.getSession(); SipSession linkedSession = (SipSession) session .getAttribute("LinkedSession"); if (linkedSession != null) { SipServletRequest bye = linkedSession.createRequest("BYE"); logger.info("Sending bye to " + linkedSession.getRemoteParty()); bye.send(); } calls.removeCall(request.getFrom().getURI().toString(), request.getTo().getURI().toString()); calls.removeCall(request.getTo().getURI().toString(), request.getFrom().getURI().toString()); SipServletResponse ok = request .createResponse(SipServletResponse.SC_OK); ok.send(); event.fire("Received Bye request"); } public void noAckReceived(@Observes @NoAckReceived SipErrorEvent ee) { logger.info("SimpleProxyServlet: Error: noAckReceived."); } public void noPrackReceived(@Observes @NoPrackReceived SipErrorEvent ee) { logger.info("SimpleProxyServlet: Error: noPrackReceived."); } protected void doRegister(@Observes @Register SipServletRequest req) throws ServletException, IOException { sipRegistar.doRegister(req); event.fire("Received Register event"); } } |
As you can see this class is nothing more than a simple POJO making extensive use of annotations. Even though in this class method names are the same as in Sip Servlets, the developer can choose any arbitary name for the methods that fits him.
Things to point for this class:
- the use of @Inject annotation in order to make available in the class SipFactory and several project specific modules. This will be enough to have all we need available in the class.
- several @Observes annotations in the parameters of the methods followed by CTF provided annotations that match SIP messages. The container will produce events for every SIP message will receive, so with the annotations @Observes @SipMessage in the parameters of a method, the method will be notified.
Some parts of the application has been moved to separate modules such as the SipRegistar. The SipRegistar module is responsible to process SIP Registrations. As you can see in lines 15-16, we inject the SipRegistarModule and then at line 151 we use this bean to actually register the user. Here is the SipRegistarModule class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | public class SipRegistarModule { private static Logger logger = Logger.getLogger(SipRegistarModule.class); private static final String CONTACT_HEADER = "Contact"; // Inject the RegisteredUsers module to keep track of registered users. @Inject RegisteredUsers users; public void doRegister(SipServletRequest req) throws ServletException, IOException { logger.info("Received register request: " + req.getTo()); int response = SipServletResponse.SC_OK; SipServletResponse resp = req.createResponse(response); Address address = req.getAddressHeader(CONTACT_HEADER); String fromURI = req.getFrom().getURI().toString(); int expires = address.getExpires(); if(expires < 0) { expires = req.getExpires(); } if(expires == 0) { users.remove(fromURI); logger.info("User " + fromURI + " unregistered"); } else { resp.setAddressHeader(CONTACT_HEADER, address); users.put(fromURI, address.getURI().toString()); logger.info("User " + fromURI + " registered with an Expire time of " + expires); } resp.send(); } } |
Again as you can see this is a simple POJO class. The SipRegistarModule is an example of a reusable module. Even though right now it is a class included in the application, SipRegistarModule could be part of the framework and available for injection using exactly the same way as we did in SimpleSipServlet.
In future releases, CTF will provide out of the box reusable modules that can be injected by developers similar to the SipRegistarModule.
Your feedback and ideas for the framework or for sip resuable modules are valuable and welcomed.
Please leave your comments here or in the Mobicents Google group.
Enjoy!

Thanks for this great article