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:

  1. Register the CTF listener in the web.xml or sip.xml
  2. Provide the beans.xml file inside WEB-INF or META-INF
  3. 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.

  1. 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>
  2. 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!

  1. Sandeep says:

    Thanks for this great article

  1. There are no trackbacks for this post yet.