Jiplet Container Home
Jiplet Developer Guide
Jiplet reference application
howto
Jiplet API (Javadoc)
|
Jiplet Advanced Developer Guide
This is a continuation of the Jiplet
Developer Guide. Before you read this document, please make sure
that you have read the document and and its prerequisites. This
document also uses the same conventions used in the Jiplet Developer
Guide.
Table
of Content
Using naming context
The jiplet container running as a JBOSS service provides a JNDI InitialContext
implementation
instance to jiplet applications running under it, in a manner that is
similar to those provided by a Java2 Enterprise
Edition application server. Entries in this InitialContext
may be referenced by the following elements in the jip application
deployment
descriptor (/JIP-INF/jip.xml) of your jiplet application:
<env-entry> -
Environment entry, a single-value parameter that can be used to
configure how the application will operate.
<resource-ref> -
Resource reference, which is typically to an object factory for
resources such as a JDBC DataSource, a JavaMail Session,
or custom object factories configured into Tomcat 4.
<resource-env-ref> -
Resource environment reference, a new variation of resource-ref
added in Servlet 2.3 that is simpler to configure for resources that do
not require authentication information.
- <ejb-ref> - EJB reference for
referring to EJBs that you jiplet application needs to access.
- <ejb-local-ref> - Ejb local reference
for referring to the local interface of the EJB.
For more details on the above entries, please refer
to the comments in the jip.xml deployment
descriptor. Optionally, you can add another server-specific deployment
descriptor where you can map the resource names for the above entries
to the JNDI names. This file must be names jboss-jip.xml
and must be placed under the /JIP-INF directory of the context. If you
do not have the jboss-jip.xml descriptor file or do not have matching
entries between the two files, the jiplet container will assume that
the name is the same as the JNDI name. The following example shows an
example of the entries in the jip.xml and jboss-jip.xml descriptor
files:
jip.xml (part)
|
jboss-jip.xml
(part)
|
<resource-ref>
<res-ref-name>mail/SipExchange/Mail</res-ref-name>
<res-type>javax.mail.Session</res-type>
<res-auth>Container</res-auth>
</resource-ref>
<ejb-ref>
<ejb-ref-name>ejb/SipExchange/RoleRegistrar</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<home>org.cafesip.sipexchange.ejbs.subscriber.RoleRegistrarHome</home>
<remote>org.cafesip.sipexchange.ejbs.subscriber.RoleRegistrar</remote>
<ejb-link>RoleRegistrar</ejb-link>
</ejb-ref>
|
<resource-ref>
<res-ref-name>mail/SipExchange/Mail</res-ref-name>
<jndi-name>java:/SipExchangeMail</jndi-name>
</resource-ref>
<ejb-ref>
<ejb-ref-name>ejb/SipExchange/RoleRegistrar</ejb-ref-name>
<jndi-name>ejb/SipExchange/RoleRegistrar</jndi-name>
</ejb-ref>
|
Once you have defined the entries in the jiplet deployment descriptors,
you can access them from your jiplet application using the standard
Java Naming API. For example:
Context ctx = new
InitialContext(System.getProperties());
Session session =
(Session)ctx.lookup("java:comp/env/mail/SipExchange/Mail");
LocationRegistrarHome
loc_home =
(LocationRegistrarHome)ctx.lookup("java:comp/env/ejb/SipExchange/RoleRegistrar");
Note the use of "java:comp/env" sub-context.
The SipExchange project uses
this feature. For more details on usage, download the source
distribution and look at the org.cafesip.sipexchange.jiplets package.
Notes:
- We are still working on this feature and
therefore some of the features that you may find in a standard J2EE
applications may be missing including authentication.
- This feature does not currently work with
the standalone jiplet container. In the standalone jiplet container,
the above entries are ignored by the jiplet container.
Handling
external events
A jiplet application may need to interact with external entities that
are either running outside the jiplet container's JVM or running as a
separate context. For example, a jiplet may need to communicate with
another application using TCP sockets. Sending TCP messages is quite
straight forward as Java already provides classes for that. The
question is - how does a jiplet get notified when the external
application sends a TCP message?
The jiplet container supports "signals" from external entities. A
signal is a way for external entities to notify a jiplet of certain
events. When an external entity sends a signal to a jiplet, the
jiplet's processSignal() method is invoked. The jiplet applications
needs to put in the business logic of handling the signal in the body
of the method processSignal(). A signal is a generic mechanism and can
handle any type of external events - a message from a socket, a JMS
message, native events, etc. However, your jiplet application must
provide the core functionality of handling external events.
The reference jiplet application that you can download separately
includes an example of signal handling. The signal handling code can be
found in the class org.cafesip.reference.jiplet.SipRegistrar class.
When the SipRegistrar jiplet is being initialized by the jiplet
container, the init() method is called. Inside the init() method, this
jiplet starts a new thread that listens for TCP connections. When a
connection is received, it opens a socket and listens for messages. A
message is a single line text message. When a message is received, the
message is "signaled" to the SipRegistrar jiplet. The jiplet prints a
log message.
SipRegistrar.init() method:
......
try
{
// start an external entity so that it can send events to the jiplet
when a TCP message is
// received from an external application. This illustrates how to use
signaling.
entity = new
ExternalEntity(getJipletContext(), getName(), -1);
entity.start(); // start the thread
}
catch (IOException e)
{
error("Could not start the external entity : " + e.getMessage()
+ "\n" + JipletLogger.getStackTrace(e));
}
The ExternalEntity class, above, handles the TCP connection and receves
message from sockets.
ExternalEntity.run() method:
public void run()
{
try
{
while (isInterrupted() == false)
{
Socket s = socket.accept();
JipletLogger.info("New socket connection received from " +
s.getRemoteSocketAddress());
BufferedReader reader = new BufferedReader(new
InputStreamReader(s.getInputStream()));
String line = "";
while (line != null)
{
line = reader.readLine();
if (line == null)
{
JipletLogger.info("Connection closed from the remote socket");
break; // accept another connection
}
line = line.trim();
if (line.length() == 0)
{
// ignore blank line
continue;
}
if (line.equals("quit") == true)
{
JipletLogger.info("A quit command is received. Going to close the
connection");
reader.close();
s.close();
break; // accept another connection
}
// pass on the received message
as a signal to the jiplet
JipletSignal signal = new JipletSignal();
signal.setEventObject(line);
if (context.sendSignal(name, signal) == false)
{
JipletLogger.error("Error sending signal to the jiplet");
}
}
}
}
catch (Exception e)
{
JipletLogger.fatal("Exception " + e.getClass().getName() + " : "
+ e.getMessage() + "\n" + JipletLogger.getStackTrace(e));
}
}
When a message is received, and the message is not "quit", the message
is passed on to the jiplet using
org.cafesip.jiplet.JipletContext.sendSignal() method. To pass the
message itself, the org.cafesip.jiplet.JipletSignal class is used as
shown in the above code segment.
SipRegistrar.processSignal()
method:
public void processSignal(JipletSignal signal)
{
super.processSignal(signal); // print the debug message
String message = (String)signal.getEventObject();
JipletLogger.info("Received a signal with message " + message);
}
The processSignal() method shown above work in a manner
similar to the method processRequest(), processResponse(), etc. You can
set scoped variables, and forward the signal to another jiplet.
For more details, see the javadocs for the classes
org.cafesip.jiplet.JipletSignal, org.cafesip.jiplet.JipletContext and
org.cafesip.jiplet.Jiplet.
To try out the above example, install the reference application as
explained here. Open a command
prompt (Windows) or terminal (Linux/Unix), run the following commands:
> telnet localhost 9998
Type in some text
You should see the text printed by the jiplet. If you type quit, the
connection will be closed.
Classloader
architecture
The jiplet container uses a different classloaders than the one
provided by the Java virtual machine (the system classloader). There
are two reasons for it.
- Using different class loaders for every jiplet context
makes the
applications isolated. It makes it impossible for applications to
access each others classes.
- The custom class loader also allows us to load classes and
jar files not specified in the CLASSPATH.
Boot classloader
The boot classloaders is
the classloader that is used to start up the jiplet container.
Standalone
When running in standalone mode, the startup script creates a CLASSPATH
that loads all the classes
in the directory $JIPLET_HOME/boot and all the jar files in the
$JIPLET_HOME/lib directory. The main class is
org.cafesip.jiplet.standalone.boot.Main. This class creates a custom
classloader that loads all the classes from the $JIPLET_HOME/class,
$JIPLET_HOME/common/classes and jar files from $JIPLET_HOME/common/lib
directories. The $JIPLET_HOME/common directory is used to load
user-defined classes and jar files.
JBOSS
When running as a JBOSS service, the jiplet container starts up
uses
the classloader the JBOSS server provides for the jiplet container
service.
The CLASSPATH includes all the jar files and classes packaged inside
the jiplet.sar. If you want to load some classes that you want to be
available to all the contexts, you can copy the jar files into JBOSS
server lib directory as explained below.
Context classloaders
For every jiplet context that the jiplet container creates, it creates
a new classloader and loads the context classes using this classloader.
This classloader's parent classloader is the boot classloader. That is,
this classloader inherits the CLASSPATH of the boot classloader. The
context classloaders load the classes and jar files from the "classes"
and "lib" directories respectively under the deployment directory (or
archive = SPR) of the context.
Therefore, the contexts are isolated from each other as far as the
classloading is concerned.
Realm classloaders
For every deployable realm (explained below), a new classloader is
created and the realm classes are instantiated using this classloader.
This classloader's parent classloader is the boot classloader. That is,
this classloader inherits the CLASSPATH of the boot classloader. The
context classloaders load the classes and jar files from the "classes"
and "lib" directories respectively under the deployment directory (or
archive = SRR) of the realm archive.
Loading classes
common to all contexts/realms
Sometimes, you will need to add classes or a package that are
accessible from multiple contexts or realms. For instance, you want to
add the Oracle JDBC driver classes to be accessible from multiple
jiplet applications. In order for you applications to use these
classes, you will need to include them into the CLASSPATH of the boot
classloader.
For the JBOSS environment. copy the classes packaged into a jars in
the $JBOSS_HOME/server/$RUN/lib/ directory.
For the standalone environment, copy the classes into
$JIPLET_HOME/common/classes directory and the jar files into
$JIPLET_HOME/common/lib
directory.
There are many uses of
this. For example:
- You are using the JDBC realm provided by the jiplet
container for
CMAA and want to use an Oracle database instead of the MySQL database,
you can copy the Oracle JDBC driver jar file into the directory and
make appropriate modification to the configuration file - server.xml.
The class loader will automatically load the classes for the Oracle
driver.
- If you are creating your own realm, you can load the realm
class and any other supporting classes in this directory for the system
to load it during
initialization.
- If you want to replace the NIST JAIN-SIP stack provided by
us,
you can get a new stack classes from another vendor, copy them into
this directory (delete the old JAIN-SIP jar files) and make appropriate
modifications to the server.xml file.
Creating
your own realm implementation
The jiplet container supports container-managed authentication and
authorization (CMAA). Realms or authentication databases can be
configured using which the
jiplet container authenticates the users and verifies their roles (or
privileges). When a SIP request message is received by the container,
it can perform authentication as specified in RFC 2617 before handing
the message over to a jiplet. By default, the jiplet container provides
a memory realm
and a JDBC realm. This is explained in details in the features FAQ, installation and configuration howtos. You may be
able to use the realms provided by us as your user database. But you
may already have a subscriber database and want to use it instead. It
is possible to add your own realm to the jiplet container. You will
need to develop some Java code for this. Before doing that, you
will need to understand a little more about realms.
There are two types of realms. They are:
- System realms: System realms are either bundled with the
jiplet container package or they are defined in the server.xml file. These realms are typically
shared by multiple jiplet contexts.
- Deployable realms: Sometimes a jiplet context requires to
install a realm for authentication and authorization for the jiplet
context only. These realms can be deployed and undeployed in a manner
similar to the jiplet contexts. The deployable realms are packaged into
an SRR archive (similar to SPR archive for a jiplet context) or
directory and deployed. These realm can also be deployed and undeployed
from the jiplet console.
System
realm development and deployment
To develop and deploy system realms, the steps are:
- Create a Java class (MyRealm, for example) that implements
org.cafesip.jiplet.Realm interface. Read the javadocs for this class for details on
the abstract methods that you have to implement.
- Alternatively, create a Java class (MyRealm, for example)
that extends org.cafesip.jiplet.realms.BaseRealm class. Read the javadocs for this class for details on
the abstract methods that you have to implement as well as other
assumptions. This way of creating a realm is easier than the former
method because the BaseRealm class is doing most of the work for you.
- Compile the java classes.
- This step is optional. Package the class and other helper
class that you have created into a jar file.
- Copy the packaged jar file or the appropriate classes into
the CLASSPATH of the boot classloader (Click here for more details).
- Modify the server.xml file to configure the realm. See the
documentation in the server.xml file for more
details.
- Re-start the jiplet container for the change to take effect.
- Create/Modify the security-constraint element for the
jiplet application to use this realm. The
modification is required in the jip.xml file for
your application. See the comments on this file for detailed
information on how to setup security constraints.
- Deploy the jiplet application and test it out.
- To undeploy, remove the realm definition from the
server.xml file.
Deployable
realm development and deployment
Developing a deployable realm
It involves the following steps:
- Create a Java class (MyRealm, for example) that implements
org.cafesip.jiplet.Realm interface. Read the javadocs for this class for details on
the abstract methods that you have to implement.
- Alternatively, create a Java class (MyRealm, for example)
that extends org.cafesip.jiplet.realms.BaseRealm class. Read the javadocs for this class for details on
the abstract methods that you have to implement as well as other
assumptions. This way of creating a realm is easier than the former
method because the BaseRealm class is doing most of the work for you.
- Copy the java classes.
Packaging the realm
Please follow the steps below:
- Create a deployment directory (lets call it my-realm).
- Create directories META-INF,
classes and lib under the deployment directory.
- Copy the realm classes (.class file) and any other helper
classes under the classes directory.
Please make sure that the directory hierarchy required for the java
package is maintained. For example, if the class is in the package
org.cafesip.realms.MyRealm, you must create sub-directories org/cafesip/jiplet/realms under the
classes directory
and place the MyRealm.class under that directory.
- If the class require any jar files, copy them under the lib directory.
- Create a deployment descriptor file under the META-INF directory. The file must
be called realm.xml and looks
something like the following:
<realm
name="subscribers.sipexchange.cafesip.org"
classname="org.cafesip.sipexchange.realms.SipExchangeRealm"
default="false">
<realm-params>
<realm-param>
<realm-param-name>nonce-private-key</realm-param-name>
<realm-param-value>spice</realm-param-value>
</realm-param>
<realm-param>
<realm-param-name>domain-uri</realm-param-name>
<realm-param-value>sip:cafesip.org</realm-param-value>
</realm-param>
<realm-param>
<realm-param-name>subscriber-registrar-ejb-ref</realm-param-name>
<realm-param-value>ejb/SipExchange/SubscriberRegistrar</realm-param-value>
</realm-param>
<realm-param>
<realm-param-name>dir-url</realm-param-name>
<realm-param-value>jnp://localhost:1099</realm-param-value>
</realm-param>
</realm-params>
</realm>
Basically, the content of realm.xml is the same as the <realm>
definition found in the server.xml file. Please see the comments on
this file for details.
- Optionally, you can create an archive file. The archive
file zips the above deployment directory into a single archive file.
The file must have an extension ".srr" and is referred to as a SRR
file. An example of an archive file name is sipex.srr. We have provide a
custom ant task to create a SRR file. The ant task extends the "jar"
ant task and has the same attributes. The following ant code segment
demonstrates the usage of the ant task:
<taskdef name="srr"
classname="org.cafesip.jiplet.ant.SrrTask">
<classpath>
<fileset dir="${project.lib.root}"
includes="jiplet.jar"/>
</classpath>
</taskdef>
<srr destfile="${project.home}/sipex-realm.srr"
basedir="${project.deploy.root}/sipex-realms"
includes="**/*"/>
Note that it is not necessary to create a SR archive but it does have
certain advantages especially when deploying the realm using the jiplet
console.
Deploying the realm
Deploying a realm is very similar to deploying a jiplet context. You
just have to copy the realm deployment directory or the archive you
created above into the jiplet container's deployment directory.
Standalone jiplet container:
- Copy the realm directory or the SRR file into the
$JIPLET_HOME/deploy directory. Re-start the jiplet container and the
jiplet container will start this realm on initialization.
- Alternatively, you can use the jiplet console to add the
new realm. In this case, you do not have to re-start the container. You
can also upload the SRR file from a client machine using the upload
feature.
Jiplet Container running as a JBOSS service:
- Copy the realm directory or the SRR file into the JBOSS
deployment directory. JBOSS will automatically deploy the realm.
- Alternatively, you can use the jiplet console to add the
new realm. With this approach, you can also upload the SRR file
from a client machine using the upload feature.
Packaging a
jiplet context into an enterprise archive (EAR)
If you are running the jiplet container as a JBOSS service, you also
have the additional option of bundling all the J2EE components that
your application needs into a enterprise archive (EAR in the J2EE
terminology). The enterprise
archive is called Enterprise SIP Archive (ESR). The EAR supports
bundling of all
the J2EE components like EJB jars, web archive(s) (WAR) into a single
archive. However it does not support the SPR and SRR archives yet :-).
So, we
have extended the EAR to include the SPR archive as well. This is what
we are calling the ESR. In an ESR, you can bundle all the standard J2EE
components supported by an EAR as well as one or more SPR archives and
a SRR archive.
To
create an EAR, you follow the same procedures that you would follow for
a EAR. Namely:
- Create a directory where you will be staging the ESR
deployment.
- Have all the individual archives (like war,ejb-jar spr and
srr)
built and copied to the staging area.
- Create a deployment descriptor called application.xml and
place
it under the META-INF directory under the staging area - just like you
would do for an EAR.
- Create the archive from the staging area using the ANT task
"ear" or using the "jar" utility. The
file name extension for an ESR must be ".esr".
The following shows an example of the application.xml deployment
descriptor:
<application
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/application_1_4.xsd"
version="1.4">
<display-name>SipExchange</display-name>
<module>
<ejb>sipex-location.jar</ejb>
</module>
<module>
<ejb>sipex-subscriber.jar</ejb>
</module>
<module>
<java>sipex-realm.srr</java>
</module>
<module>
<java>sipex-jiplets.spr</java>
</module>
</application>
Note the similarity with the EAR equivalent and how the SPR and SRR
archives are bundled
included in the above example.
Ordering of modules
One of the interesting issues is the order in which you want the
modules inside EAR to start. For example, in the above application.xml
deployment descriptor, you may want the EJB jars to start first
followed by the realm (SRR) and then the SIP archive (SPR) because one
module may be depending on another module. By default, JBOSS determines
the ordering. It starts a service archive (sar) first, followed by EJB
jars, followed by the web archives (war). Since it does not understand
what kind of services SRR and SPR provides, it starts them last. The
problem is it may start the SPR first followed by the SPR and that may
not be your intent and your application may fail because of that. In
order to overcome this problem, you have to make some changes to how
JBOSS starts the modules. Fortunantely, this is configurable.
Basically, you will have to change the URL Comparator of JBOSS. In
order to do this follow the steps outline below:
- With a text editor of your choice, open the file
$JBOSS_HOME/server/$RUN/config/jboss-service.xml
- Search for a XML element that looks like:
<mbean code="org.jboss.deployment.scanner.URLDeploymentScanner"
name="jboss.deployment:type=DeploymentScanner,flavor=URL">
- Read the detailed comments JBOSS has provided to understand
the JBOSS's deployment scanner logic.
- Replace the element
<attribute
name="URLComparator">org.jboss.deployment.DeploymentSorter</attribute>
With
<attribute
name="URLComparator">org.jboss.deployment.scanner.PrefixDeploymentSorter</attribute>
- Now, use a prefix in front of the module names to dictate
the startup order. A modified version of the application.xml may look
as follows:
<module>
<ejb>sipex-params.jar</ejb>
</module>
<module>
<ejb>sipex-location.jar</ejb>
</module>
<module>
<ejb>sipex-cdr.jar</ejb>
</module>
<module>
<ejb>sipex-subscriber.jar</ejb>
</module>
<module>
<java>01sipex-realm.srr</java>
</module>
<module>
<web>
<web-uri>sipconsole.war</web-uri>
<context-root>sipconsole</context-root>
</web>
</module>
<module>
<java>02sipex-jiplets.spr</java>
</module>
Note the use of 01sipex-realm.srr
and 02sipex-jiplets.spr. Make
sure that the srr and spr files have the same names.
- Restart JBOSS. On deployment of the ESR, the SRR module
will be deployed before the SPR module.
|