Jiplet Container Home
Jiplet advanced
developer guide
Jiplet reference application
howto
Jiplet API (Javadoc)
|
Jiplet Developer Guide
Table
of Content
This guide explains how to develop your own jiplet applications. Jiplet
applications are server-side SIP applications that handle SIP messages
and events from SIP User Agents (SIP phones and other SIP servers).
Examples of such
applications include SIP registrar, SIP proxy server, SIP call center,
etc. Using the jiplet container, you can write and deploy your own
custom
applications.
Conventions
- We have used the term "directory" to specify a file
location. This is a common Unix convention. In the Windows environment,
the term "folder" is used to mean the same thing.
- We have used the Unix directory naming convention in this
document. In the Unix environment, a directory hierarchy is specified
by the "/" separator. In the Windows environment, the "\" separator is
used. In addition, Unix system do not use drive letters as in Windows.
If you are using Windows, you will need to modify the commands
accordingly. For example, if we stated $JIPLET_HOME/bin, if you are
using Windows, it may translate to C:\jiplet-standalone\bin.
- We have used $JIPLET_HOME or similar names to specify
environmental variables. While installing/configuring, you will need to
replace these
variables with the actual values for your machine. For example, in this
document, the
variable $JIPLET_HOME has been used to specify the directory where the
jiplet container code binary is installed. We have commonly used the
following variables:
- $JAVA_HOME - directory where the Java Runtime Environment
(JRE) is installed.
- $JIPLET_HOME - directory where the Jiplet Container
software is installed.
- $JBOSS_HOME - directory where JBOSS is installed.
- $TOMCAT_HOME - directory where Tomcat is installed.
- $HOST - host name/IP address of the system where the
jiplet container is installed.
- $RUN - the JBOSS run mode (default, minimal, all, etc.).
- $ANT_HOME - the directory where Ant is installed.
- $GWT_HOME - the directory where GWT (Google Web Toolkit)
is installed.
- Commands are specified using bold. You need to enter the command
by typing/pasting the command and pressing the Enter/Return key.
Although in the Unix world this may seem natural, in the Windows
environment, lots of users are lost when it comes to entering a
command. Also, the prompts "#" or "C:\>" are shown, do not enter
them.
Technologies you need to know
Before starting to develop jiplet applications, you need to understand
the jiplet features and read all the
howtos related to installation and
configuration.
You'll also need to know the
following technologies.
- Java Programming Language and Java 1.4+ API.
- SIP.
- JAIN-SIP
API.
- Jiplet API.
- JMX.
- GWT
(Google Web Toolkit version 1.4.10).
Optionally, you may also need to understand the following:
- Java Servlet technology: Jiplets are very similar to
servlets. Experience with servlets will enable you to pick up jiplet
concepts very quickly.
- J2EE technologies: If the application you are planning to
develop requires J2EE components like EJBs, you will need to have
experience in J2EE, specifically with the JBOSS J2EE container (for
now - support for others to follow).
In order to develop effectively, your understanding of the following
technologies is required although not essential.
- Ant
- An IDE: You can use free open-source IDEs like Eclipse or
Netbeans.
Pre-requisite software
Before you are ready to start working on the project, you also need to
download and install the following software packages:
- JDK 1.5.0 or above from http://www.javasoft.com.
- Ant 1.6.2 or higher from http://ant.apache.org.
- Jiplet Container (stand-alone or JBOSS) and Jiplet Console
binary distributions
from CafeSip.org
download site.
- Jiplet Developer package from CafeSip.org
download site.
- Example Jiplet Application source distribution
from CafeSip.org
download site.
Running
your first jiplet application
We think that you will understand the concepts better if you install
the reference jiplet application we have created. Please read all about
it in the jiplet reference
application howto.
A jiplet application can be deployed into the container in two forms:
- As a SIP Archive (SPR): The jiplets that you have developed
along with supporting classes, required libraries and a deployment
descriptor can be packaged into a single zip file and deployed. This is
explained in more details below. The concept is very similar to a web
archive (WAR) for HTTP servlets.
- In "exploded" form: It is same as the above but instead of
zipping the content, a directory containing the required classes,
libraries and deployment descriptor can also be deployed. This form of
deployment is not supported for the jiplet container running as a JBOSS
service.
- As an Enterprise SIP archive (ESR): When running as a JBOSS
service, you can bundle all the required J2EE components like EJBs, web
application(s). jiplet applications, etc. into a single archive and
deploy it. Conceptually, this is equivalent to the Enterprise Archive
(EAR) specified by the J2EE spec. The only difference is that an ESR
can support standard J2EE components that the EAR support as well as
jiplet applications (SPR and SRR).
Jiplet Container
architecture
Before we go any further, let us understand how the jiplet container
works. This section may contain some information that may seem as
"internals" to you but we think that will help you gain a better
understanding.
The following class diagram shows the organization of
some of the objects.
Initialization
Even though the jiplet container can run either as a standalone
Java program or as a JBOSS service, most of the classes are actually
common between the two. The classes shown above all belong to the
package org.cafesip.jiplet except the ones that have a prefix (for
example standalone.StandaloneJipletContainer, above, is located in the
package org.cafesip.jiplet.standalone package). When deployed as a
standalone application, the "main()" program instantiates the class
standalone.boot.Main that instantiates other core
classes. When running as a JBOSS service, JBOSS starts up the
jboss.JBossService that instantiates the core classes.
One of the most important classes that is instantiated by the startup
objects is the JipletContainer. This is a singleton class that
instantiates other classes and manages the startup, shutdown and
deployment of jiplet applications. On startup, the
JipletContainer reads the configuration file server.xml (see the installation and configuration howtos for more
detail) and instantiates one or more SipConnector objects depending on
the number of connectors configured. It also loads the realm
classes that encapsulates the authentication and authorization
databases. It also scans through the
deployment directory in order to figure out how many jiplet contexts
are already deployed. When each context was originally deployed, the
JipletContainer exploded the content of the SIP archive (SPR)
file (if
deployed as a SPR) or copied the deployment directory (if deployed in
exploded format) under its deployment directory. On startup, it scans
through the directory in order to determine what contexts to startup.
Based on that it instantiates zero or more JipletContext objects that
handle context startup.
On instantiation, each of the SipConnector objects initializes its SIP
stack and registers itself as a SIP
listener for receiving SIP request
messages, SIP response messages and SIP timeout events. It also creates
a pool of threads to propagate the events to the jiplets mapped to
receive the event (explained below).
On instantiation, each JipletContext object is responsible for starting
up the context and managing its functionality. It reads the deployment
descriptor - jip.xml descriptor
file
from the JIP-INF directory located under the exploded context
directory. The jip.xml contains a list of jiplets that must be started
for the context. The JipletContext object creates a custom class loader
for the context that includes all the classes located under the
JIP-INF/classes directory and all the jar files located in JIP-INF/lib
directory. Then it instantiates each jiplet class specified in the file
and calls its init() method. The jiplet is a Java class that must be
present
in the path of the custom class loader. A jiplet class extends
the Jiplet class and overrides one or more of its methods to implement
custom SIP message/event handling logic.
The jip.xml deployment descriptor file also includes sections on jiplet
and context mapping.
The context mapping specifies what SIP request messages a jiplet
context is interested in. The configuration file can specify the
selection criteria. An example of a selection criteria is an
expression stating that if a SIP request message has an URL that
matches "*@cafesip.org", this context is interested in the message.
Selection criteria may contain multiple such expressions logically
associated using logical operators. The context mapping is
registered with respective SipConnector objects on initialization of
the context. Similarly, a jiplet mapping specifies the condition under
which the message is passed on to a specific jiplet object residing
within the context. The jiplet context maintains a mapping of the
jiplet routing criteria.
SIP message handling
When a SIP request message is received by the SIP stack, a SipConnector
is notified by the stack by invoking a callback listener method. On
receiving the request event, the SipConnector applies the selection
criteria to select a jiplet context that is registered to receive the
message. Once a jiplet context is selected, the SipConnector calls
appropriate methods in the JipletContext object to obtain a list of
jiplet objects that match the selection criteria. The request
message is then passed on to the jiplet objects by invoking the
jiplet's processRequest() method. An important aspect of the context
selection is that for a given message, only one context (the first one
to get started) is selected. If two contexts have registered criteria
and a request message is received that matches both the criteria, only
the first context is selected. This is done to prevent SIP messages
meant for one application/provider to accidentally reach another
application. However, as far as jiplet mapping is concerned, multiple
jiplets may receive the same message based on the selection criteria.
Once a SIP request message is received by a jiplet, it can use the
JAIN-SIP API to access the content of the message.
In the process of handling call sessions, a jiplet may send a response
to
a SIP message and/or send a SIP request message to another SIP network
element (SIP phone or another SIP server). To send a request or
response message, the jiplet
uses the JAIN-SIP API to formulate the message and send it. Jiplets can
also create SIP transactions and dialogs. When a SIP request message is
sent by the jiplet, it may register with the SIPConnector to
listen
for the response(s). The connector passes the response message back to
the jiplet by invoking the jiplet's processResponse() method. If there
is a time-out waiting for a response, the timeout event is passed on to
the jiplet by invoking the processTimeout() method.
In addition, an API is provided using which jiplets can start timers.
When a timer expires, the event is passed on to the jiplet that
started the timer by calling its processTimer() method.
The SIP messages, timeout events and the timer events are delivered to
the jiplet by using a thread pool. When a SIP connector is started up,
it creates a number of "permanent" threads (the number is configurable
from the file server.xml). When a SIP message is received or a SIP
timeout event or a jiplet generated timer expires, the SipConnector
object grabs one of the threads that is not being used from the thread
pool and passes on the event to the thread which calls the appropriate
method on the jiplet object. If all permanent threads are used,
temporary threads are created and the event is passed on to the newly
created temporary thread. The maximum number of temporary thread is
also configurable from the file server.xml. If all temporary/permanent
threads are used up, the SipConnector object drops the SIP
message/event. The thread pool ensures that while a SIP message/event
is being processed by a jiplet, the SipConnector is still receiving and
processing SIP messages and events. That is, it is not blocked.
Permanent threads are started during initialization and therefore do
not suffer from the overhead of thread creation as is the case with
temporary threads. However, permanent threads run all the time and
therefore use up operating system resources. You should configure these
parameters depending on the needs of the system.
From the thread pool discussion above, it is clear that the jiplet
object's process...() methods can be called by any thread in the thread
pool. A jiplet is not associated with a single thread. Therefore,
from the jiplet class, if you call the Thread.currentThread() method,
you may not receive the reference to the same thread every time. Also,
only a single instance of the jiplet class is created and its methods
are called by the threads depending on the SIP message/event received.
Since the thread pool makes the event handling asynchronous, it is
possible for the same process...() method on the jiplet object to be
called
concurrently. Therefore, the jiplet methods are NOT thread-safe. It is
the responsibility of the jiplet application to handle synchronization.
For one, you may not want to use attributes (class variables) inside a
jiplet without synchronized access. We recommend that you use the
scoped variables supported by the jiplet container instead.
Jiplet context
deployment and undeployment
As explained above, during startup, the JipletContainer looks at the
deployment directory to determine which contexts are to be started.
If it finds an exploded directory with a jiplet context
signature, it starts it up. Similarly, if it finds a SPR file (a file
with .spr extension) that matches the jiplet context signature, it
explodes the SPR file and starts it up. Whether a SPR file or an
exploded directory is a jiplet context is determined by the following
signature.
- A directory under the exploded directory called JIP-INF.
- A deployment descriptor file located under the JIP-INF
directory. The file is called jip.xml.
- Optional "lib" and "classes" directories under the
JIP-INF directory.
Therefore, to deploy a jiplet application, all one has to do is to copy
the SPR file or the exploded jiplet context directory under the
deployment directory and re-start the jiplet container.
Additionally, the JipletContainer object also exposes a JMX MBEAN that
can handle requests from management applications to deploy and undeploy
jiplet contexts. Java Management
Extensions (JMX) is a standard mechanism by which an
application/service can expose a management interface (called MBEAN)
for management applications to interact with it. When running as a
standalone jiplet container, the jiplet console
uses this interface to dynamically add and remove jiplet contexts. If
you use this method, there is no need for a container startup. In
addition, the
jiplet console also allows you to upload a jiplet context SPR file from
any system on the network and deploy the context.
In addition to the above, when the jiplet container runs as a JBOSS
service, it
registers a special deployer object called SprDeployer (see the diagram
above) with the JBOSS server. Whenever JBOSS detects a new
file/directory in its deployment area, it notifies the SprDeployer
object. The deployer compares the signature of the deployment and if it
determines that it is a valid jiplet context, it starts up the context.
Similarly, if the file is removed, the SprDeployer is notified and it
undeploys the context. The SprDeployer currently support SPR files only
- not exploded entries.
IF YOU ARE RUNNING THE JIPLET CONTAINER AS A JBOSS SERVICE, WE
RECOMMEND THAT YOU USE JBOSS'S DEPLOYMENT MECHANISM INSTEAD OF THE
ALTERNATIVES
DESCRIBED ABOVE OR USE THE JIPLET CONSOLE. IT PROVIDES THE EQUIVALENT
FUNCTIONS.
Shutdown
When the jiplet container is being shutdown either as a JBOSS service
or the standalone JVM process is being killed, the JipletContainer
object notifies the JipletContext objects that they are being
destroyed, which in turn call the destroy() method on deployed jiplet
objects to notify them of the end of processing. The SipConnector
objects are also
notified and the connectors unregister from the SIP stack.
The jiplet
programming API
We will use the reference jiplet application as an example. Please make
sure you have installed the source code and have done the build as
described in the reference
application howto.
The reference
application contains a number of jiplets that handles the tasks of
registration, proxying and presence monitoring and notifications. For
this guide, we will use only three of the jiplets as example. They are:
- org.cafesip.reference.jiplet.SipRegistrar: This jiplet
receives REGISTER message from SIP phones and stores the user's
location.
- org.cafesip.reference.jiplet.SipProxy: This jiplet receives
all SIP request messages except for the REGISTER and SUBSCRIBE messages
and forwards
the messages to the called user's current location. Similarly, it
routes
the response back to the user sending the request. Note: Since Jiplet Container r0.0.3a, the
software provides an API for proxying SIP messages as well as a
standard proxy jiplet for automatically handling SIP proxy functions.
Therefore, this jiplet is no longer used in the default setting.
However, it is still useful as an example to demonstrate how a jiplet
interacts with the JAIN-SIP proxy. If you would like to use this jiplet
in the reference application, instead of the standard jiplet, you can
do so by modifying the deployment descriptor explained below.
- org.cafesip.reference.jiplet.SipRecorder: This is a simple
jiplet that receives every SIP request and response messages and
stores the content
of received messages into the database.
You will find their source code under the source directory "src".
The "deploy" directory is the staging area for building the SPR file
and it shows you all the files that are needed to be package into a
SPR.
The jiplet class
Open the source code for the SipRecorder jiplet. As
you can see from the source code, the SipRecorder extends the call
Jiplet (org.cafesip.jiplet.Jiplet).
public
class SipRecorder extends Jiplet
All jiplets must extend this class and override one or more methods to
provide the business logic. Some of the methods that may be overridden
are:
- public void
init(JipletConfig config) : The jiplet container invokes this
method when the jiplet is being initialized. You can override this
method to provide your own custom initialization logic. The config
parameter passed to this method can be used to access jiplet
information including init parameters stored in the deployment
descriptor (jip.xml) file.
- public void destroy()
: This method is invoked by the jiplet container when the jiplet
context is being taken out of service. You can override this method to
provide your custom cleanup logic.
- public void
processRequest(JipletRequest request) : This method is invoked
by
the container when a SIP request message is received and the selection
criteria mandates that the request is passed on to the jiplet. You can
provide you own custom logic to handle the request message.
Alternatively, you can override one of the do...() methods if you want
to handle custom logic for certain SIP request messages. The examples
of do...() methods are doRegister(), doInvite(), doBye(), etc. The
request parameter contains information on the type of message and the
content of the message itself.
- public void
processResponse(JipletResponse response) : This method is
invoked by the container to notify of a SIP response. You can override
this method to provide your own response handling. The response
parameter contains more information on the event including the response
message itself.
- public void
processTimeout(JipletTimeout timeout) : This method is invoked
by the container to notify that a timeout has occurred. A timeout
occurs
when a SIP request message has been sent and no response has been
received within a certain time and after a certain number of retries.
You can override this method to provide your own timeout handling
logic.
The timeout parameter contains further information on the timeout.
- public void
processTimer(JipletTimer timer) : A jiplet application can
start its own timers (explained below) and when the timer expires the
jiplet container notifies the jiplet by calling this method. You must
override this method to provide your own timer handling logic.
Classpath
Although a jiplet is a single class, it may need to instantiate other
classes and invoke their methods. Those classes may, in turn,
instantiate other classes. Therefore, there is a need for a CLASSPATH
to specify all the classes that a jiplet application needs. The jiplet
application is automatically granted access to the jiplet classes
(org.cafesip.jiplet package and org.cafesip.jiplet.config packages) as
well as the JAIN-SIP class library (javax.sip and sub-packages).
Additional classes may be added to the jiplet context's classpath by
adding them under the WEB-INF/classes and WEB-INF/lib directories in
the deployment package (SPR or exploded). Following standard Java
conventions, the WEB-INF/lib directory contains class libraries in jar
format and the WEB-INF/classes contains class files stored in the
directory structure as per the package hierarchy. Therefore, you can
add classes and jars to the classpath by simply adding them to these
directories.
For more details on the jiplet container class loaders, refer to this section.
Accessing init
parameters
Very often you will need to pass parameters to your jiplet. Such
parameters cannot be hard-coded inside your code because they may have
to be changed. Examples of such parameters include SQL database user
name, password, etc. Each init-param consists of a key-value pair that
you can iterate through and access the parameter name (key) and its
corresponding value. You can specify these parameters inside the
jiplet descriptor file as describe here. To
access the
parameters from the init() method of the jiplet, use the
config.getInitParams() as shown in the SipRecorder jiplet below:
InitParams init = config.getInitParams();
Iterator iter =
init.getInitParam().iterator();
String jdbc = null;
String url = null;
String user = null;
String password = null;
while (iter.hasNext() ==
true)
{
InitParam parm = (InitParam) iter.next();
if
(parm.getParamName().equals("jdbcDriver") == true)
{
jdbc = parm.getParamValue();
}
else
if (parm.getParamName().equals("dbUrl") == true)
{
url = parm.getParamValue();
}
else
if (parm.getParamName().equals("dbUser") == true)
{
user = parm.getParamValue();
}
else
if (parm.getParamName().equals("dbPassword") == true)
{
password = parm.getParamValue();
}
if
(isDebugEnabled() == true)
{
debug("Init param (" + parm.getParamName() + ", "
+ parm.getParamValue() + ")");
}
}
..........
Accessing scoped
variables
The scoped variables are key-value pairs that the jiplets can create,
access and delete. Each variable has a name (key) and an associated
object as a value. Jiplets can use the variables to store information
required afterwards. Each variable has a lifetime (or scope) after
which the jiplet container automatically removes them. The jiplet
container supports the following types of scoped variables:
- Application: can be accessed by any jiplets inside a
context. These variables remain visible to all the jiplets as long as
the context is in service.
- Session: a session identifies a SIP end point based on
the SIP CALLID headers field in the message. The session
variables can
be accessed by the jiplets handling the same session. For example, if a
jiplet sets a session-scope variable when a SIP INVITE message is
received, this jiplet or another jiplet belonging to the same context
will be able to access this variable when another message is
received that is associated with the same CALLID.
- Event: can be accessed by all jiplets that handles a SIP
request, response, time-out and timer events. When either of these
events occur, if a jiplet sets up a event-scope variable, it will be
accessible at any time to this and other jiplets while the event is
being handled. It is very similar to a local variable except that if a
jiplet forwards the event to another jiplet, the event-scope variables
set by the first jiplet are still visible to the second.
- Transaction: a transaction scope variables are visible to
jiplets during the lifetime of a SIP transaction. For example, if a
jiplet creates a transaction, sets a transaction scope variable and
sends a SIP INVITE message to an end-point, the variable will be
visible to the jiplet(s) handling the response(s) from the SIP
end-point.
- Dialog: a dialog scope variables are visible to jiplets
during the lifetime of a SIP dialog.This is similar to a
transaction-scope variable except that this variable remains visible
during the life of a SIP dialog.
In the jiplet container, each type of the above scoped variables are
handled by specific classes. The classes handling scoped-variables
extend the class
org.cafesip.jiplet.ScopedVariables. See the javadoc for this class to
find out more about the operations supported by all scoped variables.
The following classes implemented the scoped variables:
- org.cafesip.jiplet.JipletContext:
This class handles application scope variables. To access this class
from the jiplets, you can use the method:
org.cafesip.jiplet.Jiplet.getJipletContext()
method.
- org.cafesip.jiplet.JipletSession:
This class handles the session scope variables. To access this class
from the jiplets, you need to use the objects of the types org.cafesip.jiplet.JipletRequest, org.cafesip.jiplet.JipletResponse, org.cafesip.jiplet.JipletTimeout or org.cafesip.jiplet.JipletTimer
that are passed on to the jiplet as parameters by the jiplet container
in the message/event handling methods - process...() or do...(). Use
the method getSession() in these objects to get the JipletSession
object. NOTE: The jiplet container
has no way of automatically timing out a SIP session and freeing up the
resources (unlike Java servlets). Therefore, you will have to manually
free up the resources by calling the JipletSession.invalidate() method.
- org.cafesip.jiplet.JipletRequest,
org.cafesip.jiplet.JipletResponse,
org.cafesip.jiplet.JipletTimeout
or org.cafesip.jiplet.JipletTimer: These
classes handle the event-scope variables. The jiplet container passes
objects of these types in to the process...() or do...() methods when a
message/event is received.
- org.cafesip.jiplet.JipletTransaction:
This class handles the transaction-scope variables. To access this
class from the jiplets, call the org.cafesip.jiplet.Jiplet.getTransaction()
method. The transaction-scope variables currently work only when the
jiplet application initiates an outgoing SIP request message.
- org.cafesip.jiplet.JipletDialog:
This class handles the dialog-scope variables. To access the class from
the jiplets, call the method org.cafesip.jiplet.Jiplet.getDialog()
method. Note: The JipletDialog class uses the JAIN-SIP
javax.sip.Dialog.getApplicationData() to store the JipletDialog object.
Therefore, you should not use this method to store your own
application-defined object.
The following code segments demonstrate how to use scoped variables.
To set application scope variables from any jiplet, use the following
code segment:
getJipletContext().setAttribute("myVariable",
myObj);
creates a new variable "myVariable" (or sets a new value if the
variable exists) and associates an object myObj to it. At any time,
this or another jiplet belonging to the jiplet context can access the
application scope variable using the following code segment:
Object myObj =
getJipletContext().getAttribute("myVariable");
Jiplets can remove the application-scope variable by calling the method:
getJipletContext().removeAttribute("myVariable");
Jiplets can also get a list of all attribute names in the application
scope by calling the method:
java.util.Enumeration enum = getJipletContext().getAttributeNames();
The following example of using session-scope variable is lifted from
the SipProxy jiplet:
JipletSession session = null;
if
(req_msg.getMethod().equals("INVITE") == true)
{
session = requestEvent.getSession(true);
synchronized (callIdLock)
{
callId++;
info ("SIP session " + callId + " created.");
session.setAttribute("callId", new Integer(callId));
}
}
else
{
session = requestEvent.getSession(false);
if (session != null)
{
Integer c = (Integer) session.getAttribute("callId");
if (c != null)
{
info("SIP request message received for session " + c);
}
}
if (req_msg.getMethod().equals("BYE") == true)
{
if (session != null)
{
session.invalidate();
}
}
}
.......
Handling
SIP requests, responses and timeouts and JAIN-SIP
interaction
As explained above, the jiplet's processRequest() method is called by
the jiplet container when a SIP request message is received that meets
the selection criteria for the jiplet. The JipletRequest object passed
on to this method contains more information on the event. Use the
method getRequestEvent() to
send the obtain the javax.sip.RequestEvent to obtain the parsed request
message and other information. If your application needs to send a
response message, you can use the JAIN-SIP API to formulate the
response and send them.
Similarly, when a SIP response message is received, the jiplet
container invokes the processResponse() method to notify the jiplet
that sent the SIP request message. The JipletResponse object passed on
to this method contains more information on the event. Use the method getResponseEvent() to send the
obtain the javax.sip.ResponseEvent to obtain the parsed response
message and other information.
When there is a SIP timeout, as explained above, the jiplet
container invokes the processTimeout() method to notify the jiplet of
the timeout. The JipletTimeout object passed on to this method contains
more information on the timeout. Use the method getTimeoutEvent() to send the
obtain the javax.sip.TimeoutEvent to obtain further information
including information on the SIP request.
In all the above cases, note the interaction between the jiplet
container and JAIN-SIP. As a jiplet developer, you will have access to
the entire JAIN-SIP API for parsing and formatting of SIP messages,
sending them and managing them. The jiplet container is providing you
an application development framework, the work of messaging is still
handled by the JAIN-SIP API and the SIP stack.
The following code segment is lifted from the SipRegistrar jiplet that
shows you how the jiplet API and the JAIN-SIP API are closely
associated with each other:
public void
doRegister(JipletRequest requestEvent)
{
....
try
{
RequestEvent req_event = requestEvent.getRequestEvent();
SipProvider provider = (SipProvider) req_event.getSource();
Request req_msg = req_event.getRequest();
FromHeader from = (FromHeader) req_msg.getHeader(FromHeader.NAME);
long
expires = 60L * 60L * 1000L; // default 60
minutes
ExpiresHeader ehead = (ExpiresHeader)
req_msg
.getHeader(ExpiresHeader.NAME);
if
(ehead != null)
{
expires = ehead.getExpires() * 1000L;
}
String user = requestEvent.getUserPrincipal().getName();
String uri = from.getAddress().getURI().toString();
if
(expires == 0)
{
info("User " + user + " logged out");
LocationDatabase.getInstance().remove(uri);
}
else
{
String priv = requestEvent.isUserInRole("user") == true? "user":
"other";
info("User " + user + " logged in"
+ " from URI: " + uri
+ " (" + priv + ")");
ContactHeader contact =
(ContactHeader) req_msg
.getHeader(ContactHeader.NAME);
ContactInfo c = new ContactInfo(contact,
new Date( (new Date()).getTime() + expires) );
LocationDatabase.getInstance().put(uri, c);
}
// send a
OK response
sendAuthSuccess(req_msg, provider);
debug("Sent a OK response to the registration message");
....
Further, as a convenience for jiplet developers, the
following methods are provided in the org.cafesip.jiplet.Jiplet class:
- getMessageFactory()
returns the JAIN-SIP message factory object.
- getHeaderFactory()
returns the JAIN-SIP header factory.
- getAddressFactory()
returns the JAIN-SIP address factory.
- getListeningPointDefault()
returns the JAIN-SIP listening point that has been
designated as the default listening point for this jiplet's SIP
connector. The designation is done in the server.xml configuration
file. This listening point can be used for sending requests not already
associated with a dialog.
- getSipProvider(ListeningPoint
lp) returns the JAIN-SIP SIP provider corresponding to the
given listening point.
- getSipProvider(String
address, int port) returns the JAIN-SIP SIP provider associated
with the given host address and port.
- getListeningPoints()
returns all of the JAIN-SIP listening points that have been configured
for this jiplet's SIP connector.
- hasAddress(String host,
int port) indicates if this jiplet's SIP connector is handling
the given host/port or not.
Developers can use the get...Factory() methods to formulate SIP request
and
response messages, and the listening point and provider methods for
sending out-of-dialog or dialog-establishing requests (note, the SIP
provider to use once a dialog is established is already available to
the jiplet through the JAIN-SIP API when each event is reported to the
jiplet).
As an example, take a look at the following method in
the SipRegistrar jiplet. The method sends an OK response to a
SIP register request:
private void
sendAuthSuccess(Request request, SipProvider provider)
throws ParseException, SipException
{
Response response =
getMessageFactory().createResponse(Response.OK,
request);
provider.sendResponse(response);
}
Initiating a
SIP request message
Some jiplet applications may require the server to send a SIP request
message to another SIP network element. You need to use the JAIN-SIP
API for this purpose because the jiplet container does not provide any
messaging API. Use the JAIN-SIP API to create client transactions,
server transactions, dialogs, SIP request messages, etc. As explained
above, the get...Factory() and other methods are provided as
convenience methods
to help you create and send SIP messages.
The jiplet container does not play any role here EXCEPT one thing. After you have
sent a SIP request message and you are expecting a SIP response, you
must call the jiplet's registerForResponse()
method to register for a SIP response. If
you do NOT do so, the jiplet container will not route the response to
the jiplet. At any time, you can unregister from listening for
the response by calling the jiplet's cancelResponse()
method.
The following code segment from the example SipProxy jiplet
demonstrates the
concepts:
private void
forwardRequestMessage(Request request, ContactHeader contact,
ListeningPoint listeningPoint) throws ParseException,
InvalidArgumentException, SipException
{
Request req = (Request)
request.clone();
// replace the URI with the
contact URI
Address address =
contact.getAddress();
req.setRequestURI(address.getURI());
// add the VIA header
ViaHeader via =
getHeaderFactory().createViaHeader(
listeningPoint.getIPAddress(), listeningPoint.getPort(),
listeningPoint.getTransport(),
ProxyUtilities.generateBranchId());
req.addHeader(via);
// decrement the hop count
int num_forwards = 70;
MaxForwardsHeader forwards =
(MaxForwardsHeader) request
.getHeader(MaxForwardsHeader.NAME);
if (forwards != null)
{
num_forwards = forwards.getMaxForwards() - 1;
if
(num_forwards <= 0)
{
// oops, we are not supposed to forward further, but since we
// are not a real proxy server,
// we will do it anyways. Hopefully, the next guy will catch it.
num_forwards = 1;
}
}
forwards =
getHeaderFactory().createMaxForwardsHeader(num_forwards);
req.setHeader(forwards);
debug("Forwarding the
request message to " + contact.getAddress());
getSipProvider(listeningPoint).sendRequest(req);
// register for response for
requests for which responses are expected.
String method =
req.getMethod();
// TODO add other methods
if
(method.equals(Request.ACK) == false)
{
registerForResponse(req, 30000L);
}
}
Proxying SIP messages
Many server-side SIP applications need to proxy SIP messages. Proxying
is a term for forwarding a received request or response message to one
or more destinations (next hop). The jiplet container provides a proxy
object that can be used to forward request and responses and handle
time-outs. So, to proxy a message, it requires one method call from the
processRequest(), processResponse() and processTimeout() methods of
your jiplet object. The jiplet container provides a class for such SIP
communicatons. It is called org.cafesip.sip.SipCommunicator. Using this
object, you can perform proxy operations (other SIP operations as well
in the future). To obtain this object, from the processRequest()
method,
you need to do the following:
public void
processRequest(JipletRequest requestEvent)
{
SipCommunicator sip = requestEvent.getSipCommunicator();
....
Similarly, you can obtain the SipCommunicator object from the
processResponse()
and processTimeout() methods. For details, read the javadocs for the
org.cafesip.jiplet.sip.SipCommunicator class. Once you have this
object,
you can call the methods, proxyRequest(), proxyResponse() and
handleProxyTimeout() methods to proxy the message and/or handle
timeouts. When proxying a request, you will need to provide a list of
SIP URIs that you want the message forwarded to. If this list is empty,
a TEMPORARILY UNAVAILABLE response message is sent to the sender. A
common
approach is to obtain this list from the location register. The proxy
object supports a stateful as well as stateless proxying. It also
allows you to add record routes. The proxy function performs all the
required header manipulations, and routing operations as specified in
RFC 3261.
The SipCommunicator class allows you to easily add the proxy
functionality
to your jiplets programatically. You have all the flexibility that you
need to invoke its methods in any way you want and, if necessary,
interact with low-level JAIN-API. However, if your proxying
requirements are straightforward, we have an easier solution. We have
created a jiplet called org.cafesip.jiplet.sip.ProxyJiplet as a part
of the standard jiplet container package that you can use for routine
proxy operations. That way, you do not have to write your own jiplet.
You can add this jiplet into your jiplet application by adding a deployment descriptor entry for this jiplet. From
the deployment descriptor, you can configure whether you want a
stateful or a stateless proxy, specify your location database using an
interface as well as another jiplet to which the
request/response/timeout is forwarded to after completing the proxy
operations. For more details, see the javadocs for the
org.cafesip.jiplet.sip.ProxyJiplet class.
The reference application is configured to use the ProxyJiplet for the
proxying operation. See the deployment descriptor
for details on how the proxy jiplet is configured. Also, note that
since the proxy jiplet is just another jiplet, you can setup jiplet
mappings and security constraints similar to any other jiplet.
Notes on the proxy functionality provided by the Jiplet Container:
- In a multi-homed environment, the IP networking (IP routing
tables) on the system running the Jiplet Container may need to be
modified so that the system does IP forwarding between the interfaces
used for SIP messaging. Otherwise, messages may not be able to be
proxied across the different network interfaces.
- When forwarding a request during proxying, the Jiplet
Container uses the default listening point to send the proxied request.
- When using proxying in a multi-homed environment, specify
that record routes be added during proxying. This will ensure that
messages going back and forth in a dialog take the same path across the
server interfaces. Otherwise, the container may not be able to
correlate a received request (such as ACK) with the appropriate
response context and as a result, some proxied calls will not complete.
Starting timers
Some jiplet applications may need to start a timer when an event
occurs and when the timer expires, perform some operations. The
jiplet container provides an API for jiplets to start timers and when
the timer expires, the jiplet container notifies the jiplet that
started the timer. Multiple timers can be started by a jiplet. To start
a timer, use the org.cafesip.jiplet.Jiplet.startTimer() methods. By
passing parameters to these functions, you can specify the
following:
- Start a timer that expires after a specified amount of time.
- Start a timer that expires at a specified absolute time.
- Start a timer that expires repeatedly after specified
periods of time.
To cancel a timer at any time, call the cancelTimer() method.
When the timer expires, the jiplet container invokes the jiplet's
processTimer() method. An object of type org.cafesip.jiplet.JipletTimer
is returned that contains additional information on the timer. Jiplets
must override this method to provide their own timer handling logic.
The following example is lifted from the SipRegistrar jiplet:
timer =
startTimer(60000L, true, requestEvent, null);
and here is the overridden processTimer() method.
public void
processTimer(JipletTimer arg0)
{
debug ("The timer has
expired. Checking for expired registration");
long d = (new
Date()).getTime();
Iterator iter =
LocationDatabase.getInstance().entrySet().iterator();
while (iter.hasNext() ==
true)
{
Map.Entry entry = (Map.Entry)iter.next();
String key = (String)entry.getKey();
ContactInfo value = (ContactInfo)entry.getValue();
if
(d >= value.getExpiryTime().getTime())
{
info ("Entry " + key + " has expired. retiring the user.");
iter.remove();
}
}
Getting user
authentication and authorization information
The jiplet can, optionally, use a security-constraint to let the jiplet
container manage the authentication and authorization on behalf of the
jiplet (see this section for details). This
feature is referred to as CMAA. When a security-constraint is
specified, only authenticated SIP request messages are delivered to the
jiplet. From the processRequest() or any of the do...() methods, you
can find out the user information. Two methods are provided as a part
of the org.cafesip.jiplet.JipletRequest class. They are:
- getUserPrincipal(): used to find out if the user name
information.
- is userInRole(): use to find out if the user has been
configured with a particular role (or privilege level).
The following code segment is lifted from the SipRegistrar jiplet that
shows you how the methods can be used:
public void
doRegister(JipletRequest requestEvent)
{
....
try
{
......
String user = requestEvent.getUserPrincipal().getName();
String uri = from.getAddress().getURI().toString();
if
(expires == 0)
{
info("User " + user + " logged out");
LocationDatabase.getInstance().remove(uri);
}
else
{
String priv = requestEvent.isUserInRole("user") == true? "user":
"other";
info("User " + user + " logged in"
+ " from URI: " + uri
+ " (" + priv + ")");
......
}
....
Forwarding
an event from one jiplet to another
At any time, from the processRequest(), do...() methods,
processResponse(), processTimeout() and processTimer() method, a jiplet
can forward the event to another jiplet. This is typically done when
the jiplet decides that another jiplet must handle the event. It is
also done when jiplets share the business logic with each jiplet doing
a particular set of tasks and then forwarding it to another jiplet for
completing other tasks. To forward, use the forward() method. The
following code segment is taken from the SipProxy jiplet class.
forward(requestEvent,
"ExampleSIPRecorderJiplet");
As a parameter to this method, enter the name of the jiplet as it
appears in the deployment descriptor. One note is that, the event is
forwarded
to the forwarded jiplet after the current jiplet returns from the
current method. Another note is that event-scope variable remain within
scope during the process of forwarding. So, one jiplet can
set an event-scope variable and the forwarded jiplet can access the
variable.
Printing log messages
Jiplet applications may print log messages. The jiplet container
has extensive support for logging and debug tracing. The standalone
jiplet container uses LOG4J for printing log messages. The default
settings store the logs into a rotating log files. However, with LOG4J,
you can do more. You can specify different "appenders" that may send
certain types of log messages via an email, etc. When run as a JBOSS
service, the logs are integrated with the JBOSS log service which has
similar capabilities.
If the jiplets uses the logging methods provided by the jiplet, the
logs are also be handled similarly. If you use the jiplet logging
mechanism, you will be able to save quite a bit of work that is needed
to print and manage logs. To print log messages, use the
org.cafesip.jiplet.Jiplet class's info(),
warn(), error() and fatal() methods to print log messages
with the respective severity. In order to reduce debug tracing
overhead, you can also use the isDebugEnabled()
method to check if the debug logging is turned on before formulating
and printing a debug message.
Finding the
deployment directory
Since the jiplet application may be exploded or copied to any arbitrary
location, if a jiplet application needed to find out the path where it
is located, you can use the org.cafesip.jiplet.JipletContext.getRealPath()
method to get the absolute path where the jiplet applications working
copy location. This will come in handy if you have additional data
files that you have placed in your jiplet SPR or exploded directory and
would like to access them during the runtime. You can also find the
context name by calling org.cafesip.jiplet.JipletContext.getContext()
method.
Interfacing with
a management application
Some more complex jiplet applications may have to be managed by an
external management application. The jiplet container provides a
javax.management.MBeanServer to the jiplet classes for them to register
MBEANs with the server and expose manageable attributes and methods. To
obtain the MBeanServer object from the jiplet, use the following
combination of methods:
MBeanServer
server = getJipletContext().getJmxAgent();
// next, register your MBEAN
with the server, see the JMX javadocs for details.
You can verify the functionality of the MBEAN using the JBOSS JMX agent
if you are using JBOSS or by using a HTTP interface provided by the
standalone jiplet container.
Packaging your
application
Compiling
Once you have created the jiplet classes and associated helper classes,
you will need to compile them and finally package the classes and
descriptors into a deployable jiplet context. To build the application,
you will need the following libraries:
- jiplet.jar
- JainSipApi1.2.jar
All these files can be found in the "lib" directory of the reference
application. Please check the ant build script provided with the
reference application. The compile target is shown below:
<target
name="compile" depends="init"
description="Compile jiplet container reference application">
<javac srcdir="${project.src.root}"
destdir="${project.deploy.root}/JIP-INF/classes"
debug="${project.debug.mode}"
deprecation="${project.deprecation.mode}"
includes="org/cafesip/reference/jiplet/*.java">
<classpath>
<fileset dir="${project.lib.root}"
includes="*.jar"/>
<fileset dir="${project.deploy.root}/JIP-INF/lib"
includes="*.jar"/>
</classpath>
</javac>
</target>
In the above example, we have created a staging area, where all the
compiled classes are stored. The staging area is the "deploy" directory
(specified by the property ${project.deploy.root} above. The deploy
directory can either be deployed as an exploded application or it can
be "zipped" up into a SPR FILE. We have done the latter in the ant
script.
The jiplet application must have the following directory structure:
The boxes in yellow are
directories. The class files must be stored in the classes directory
and you must create sub-directories as per the java packaging rules. If
you decide to package the classes in one or more jar
files, they must be placed under the lib directory. In the above
example, we are placing the class files under the classes directory.
If you have any other data files or other types of files, you can place
them anywhere except under the classes and lib directories above.
The jip.xml is a deployment descriptor file that defines the jiplets,
their
configurations and their init parameters. It also contains the jiplet
and context mapping information. This file must be included for the
jiplet container to deploy the context.
Deployment descriptor jip.xml
The jip.xml is a deployment descriptor file that defines the jiplets,
their
configurations and their init parameters. It also contains the jiplet
and context mapping information. In addition, you can also specify the
security-constraint parameter for each jiplet. You must create this
file using a
standard text or an XML editor. More details on the jip.xml file can be
found inside the file by reading the comments. You can view an online
version here.
Creating a SIP archive
(SPR)
The jiplet container supports packaging of the jiplet applications you
have developed into a SIP archive (SPR). A SPR is a zip
file that contains all the classes, jars, and the deployment
descriptor. Basically, the deployment directory is zipped into a single
zip file. The file has an extension ".spr".
If you decide to package the application into a SIP archive (SPR) file
(required for
JBOSS and generally recommended), you can use the "spr" ant task
include with the jiplet container. The following code shows an ant code
segment:
<target
name="build" depends="init, clean, compile"
description="build the spr file ready to deploy">
<taskdef name="spr"
classname="org.cafesip.jiplet.ant.SprTask">
<classpath>
<fileset dir="${project.lib.root}"
includes="jiplet.jar"/>
</classpath>
</taskdef>
<spr destfile="my-jiplets.spr" basedir="${project.deploy.root}"
includes="**/*"/>
</target>
The spr task basically extends the Ant "jar" task and supports the same
set of attributes. The task will create a spr file called
my-jiplets.spr and therefore the context name is going to be my-jiplets
unless you specify a different one from the jiplet console application.
Advanced Topics
For more advanced jiplet concepts like the classloader architecture,
how to create realms, and enterprise archive, please refer to the Jiplet Advanced Developer
Guide.
Getting further help
If you need further help and clarification, please use the following
resources:
- Download the jiplet container source code and figure it out
yourself. Although it may seem like a difficult task, it is actually
quite easy to understand. This will give you the most in-depth
understanding of the subject.
- Read all the documentation, howtos and FAQ. They are
inter-related and to get better understanding, you need to read all of
them.
- Use the
support resources to get help.
- Use the bug database to understand outstanding issues and
write bug/enhancement request.
|