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

  1. 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.
  2. 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.
  3. 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:
    1. $JAVA_HOME – directory where the Java Runtime Environment (JRE) is installed.
    2. $JIPLET_HOME – directory where the Jiplet Container software is installed.
    3. $JBOSS_HOME – directory where JBOSS is installed.
    4. $TOMCAT_HOME – directory where Tomcat is installed.
    5. $HOST – host name/IP address of the system where the jiplet container is installed.
    6. $RUN – the JBOSS run mode (default, minimal, all, etc.).
    7. $ANT_HOME – the directory where Ant is installed.
    8. $GWT_HOME – the directory where GWT (Google Web Toolkit) is installed.
  4. 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.

  1. Java Programming Language and Java 1.4+ API.
  2. SIP.
  3. JAIN-SIP API.
  4. Jiplet API.
  5. JMX.
  6. GWT (Google Web Toolkit version 1.4.10).

Optionally, you may also need to understand the following:

  1. Java Servlet technology: Jiplets are very similar to servlets. Experience with servlets will enable you to pick up jiplet concepts very quickly.
  2. 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.

  1. Ant
  2. 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:

  1. JDK 1.5.0 or above from http://www.javasoft.com.
  2. Ant 1.6.2 or higher from http://ant.apache.org.
  3. Jiplet Container (stand-alone or JBOSS) and Jiplet Console binary distributions from the Jiplet project download page.
  4. Jiplet Developer package from the Jiplet project download page.
  5. Example Jiplet Application source distribution from the Jiplet project download page.

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 various forms:

  1. 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.
  2. 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.
  3. As an Enterprise Archive (EAR): 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.

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.

  1. A directory under the exploded directory called JIP-INF.
  2. A deployment descriptor file located under the JIP-INF directory. The file is called jip.xml.
  3. 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:

  1. org.cafesip.reference.jiplet.SipRegistrar: This jiplet receives REGISTER message from SIP phones and stores the user’s location.
  2. 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.
  3. 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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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:

  1. getMessageFactory() returns the JAIN-SIP message factory object.
  2. getHeaderFactory() returns the JAIN-SIP header factory.
  3. getAddressFactory() returns the JAIN-SIP address factory.
  4. 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.
  5. getSipProvider(ListeningPoint lp) returns the JAIN-SIP SIP provider corresponding to the given listening point.
  6. getSipProvider(String address, int port) returns the JAIN-SIP SIP provider associated with the given host address and port.
  7. getListeningPoints() returns all of the JAIN-SIP listening points that have been configured for this jiplet’s SIP connector.
  8. 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:

  1. 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.
  2. When forwarding a request during proxying, the Jiplet Container uses the default listening point to send the proxied request.
  3. 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:

  1. Start a timer that expires after a specified amount of time.
  2. Start a timer that expires at a specified absolute time.
  3. 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:

  1. getUserPrincipal(): used to find out if the user name information.
  2. 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:

  1. jiplet.jar
  2. 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:

  1. 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.
  2. Read all the documentation, howtos and FAQ. They are inter-related and to get better understanding, you need to read all of them.
  3. Use the support resources to get help.
  4. Use the bug database to understand outstanding issues and write bug/enhancement request.

 

 

 

VN:R_U [1.9.20_1166]
Rating: 0.0/10 (0 votes cast)

Leave a Reply