JBoss.orgCommunity Documentation
The example application is defined by a service descriptor, which refers the Root SBB. The Root SBB does not defines child relations, which means the application is a single SBB.
To obtain the example's complete source code please refer to Section 2.2, “JBoss Communications JAIN SLEE SIP B2BUA Example Source Code”.
The service descriptor is plain simple, it just defines the service ID, the ID of the root SBB and its default priority. The complete XML is:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE service-xml PUBLIC
"-//Sun Microsystems, Inc.//DTD JAIN SLEE Service 1.1//EN"
"http://java.sun.com/dtd/slee-service-xml_1_1.dtd">
<service-xml>
<service>
<service-name>SimpleSip11B2BTestService</service-name>
<service-vendor>org.mobicents</service-vendor>
<service-version>1.0</service-version>
<root-sbb>
<sbb-name>SimpleSip11B2BTestSbb</sbb-name>
<sbb-vendor>org.mobicents</sbb-vendor>
<sbb-version>1.0</sbb-version>
</root-sbb>
<default-priority>0</default-priority>
</service>
</service-xml>
The SIP B2BUA Example's Root SBB is composed by the abstract class and the XML descriptor.
The class org.mobicents.slee.example.sip11.b2b.SimpleSip11B2BTestSbb
includes all the service logic for the example.
The javax.slee.SbbObject
's setSbbContext(SbbContext)
is used by SBBs to store the SBB's context into a class field. The SBB should take the opportunity to also store objects, such as SLEE facilities, which are reused by all service logic entities, a.k.a. SbbEntities, and are stored in the JNDI environment.
The class fields and setSbbContext(SbbContext)
method's and related code:
public void setSbbContext(SbbContext context) {
this.sbbContext = context;
if (tracer == null) {
tracer = sbbContext.getTracer(SimpleSip11B2BTestSbb.class
.getSimpleName());
}
try {
Context ctx = (Context) new InitialContext()
.lookup("java:comp/env");
sipActivityContextInterfaceFactory = (SipActivityContextInterfaceFactory) ctx
.lookup("slee/resources/jainsip/1.2/acifactory");
sipProvider = (SleeSipProvider) ctx
.lookup("slee/resources/jainsip/1.2/provider");
} catch (NamingException e) {
tracer.severe(e.getMessage(), e);
}
}
For each CMP field, which will hold the service logic instance data, the application defines two abstract methods, the getter and the setter. SLEE is responsible for the implementation of those methods.
The application uses two CMP field, where it stores each session leg SIP Dialog, the accessors code is:
public abstract void setIncomingDialog(ActivityContextInterface aci);
public abstract ActivityContextInterface getIncomingDialog();
public abstract void setOutgoingDialog(ActivityContextInterface aci);
public abstract ActivityContextInterface getOutgoingDialog();
The SIP INVITE is the starting point of each instance of the service logic, its responsibility is:
Create the incoming and outgoing leg SIP Dialog activities and attach the SbbEntity, to receive further SIP messages on each leg.
Store each SIP Dialog related ActivityContextInterface in a CMP field.
Forward the INVITE SIP request to the outgoing session leg.
The event handler code:
// Initial request
public void onInviteEvent(RequestEvent event, ActivityContextInterface aci) {
// ACI is the server transaction activity
final ServerTransaction st = event.getServerTransaction();
try {
// Create the dialogs representing the incoming and outgoing call
// legs.
final DialogActivity incomingDialog = (DialogActivity) sipProvider
.getNewDialog(st);
final DialogActivity outgoingDialog = sipProvider.getNewDialog(
incomingDialog, true);
// Obtain the dialog activity contexts and attach to them
final ActivityContextInterface outgoingDialogACI = sipActivityContextInterfaceFactory
.getActivityContextInterface(outgoingDialog);
final ActivityContextInterface incomingDialogACI = sipActivityContextInterfaceFactory
.getActivityContextInterface(incomingDialog);
final SbbLocalObject sbbLocalObject = sbbContext
.getSbbLocalObject();
incomingDialogACI.attach(sbbLocalObject);
outgoingDialogACI.attach(sbbLocalObject);
// Record which dialog is which, so we can find the peer dialog
// when forwarding messages between dialogs.
setIncomingDialog(incomingDialogACI);
setOutgoingDialog(outgoingDialogACI);
forwardRequest(st, outgoingDialog);
} catch (Throwable e) {
tracer.severe("Failed to process incoming INVITE.", e);
replyToRequestEvent(event, Response.SERVICE_UNAVAILABLE);
}
}
Non final responses can be received from the outgoing session leg, and all besides 100 TRYING are processed and forwarded to the incoming leg.
The event handler method's code:
public void on1xxResponse(ResponseEvent event, ActivityContextInterface aci) {
if (event.getResponse().getStatusCode() == Response.TRYING) {
// those are not forwarded to the other dialog
return;
}
processResponse(event, aci);
}
Successful final responses can be received on both session legs: as a response to the INVITE sent to the outgoing leg, or as a response to a BYE sent to any of the session legs.
If it is a response to the INVITE the application sends an ACK immediately, to minimize the response retransmissions, and then forwards the message to the incoming leg.
In case of a BYE response, the application does not forwards it to the other leg, because the other leg's BYE was already replied.
The event handler method's code:
public void on2xxResponse(ResponseEvent event, ActivityContextInterface aci) {
final CSeqHeader cseq = (CSeqHeader) event.getResponse().getHeader(
CSeqHeader.NAME);
if (cseq.getMethod().equals(Request.INVITE)) {
// lets ack it ourselves to avoid UAS retransmissions due to
// forwarding of this response and further UAC Ack
// note that the app does not handles UAC ACKs
try {
final Request ack = event.getDialog().createAck(
cseq.getSeqNumber());
event.getDialog().sendAck(ack);
} catch (Exception e) {
tracer.severe("Unable to ack INVITE's 200 ok from UAS", e);
}
} else if (cseq.getMethod().equals(Request.BYE)
|| cseq.getMethod().equals(Request.CANCEL)) {
// not forwarded to the other dialog
return;
}
processResponse(event, aci);
}
The BYE request is received when a party wants to end the session, in such scenario the application immediately replies with 200 OK, minimizing the request retransmissions, and then forwards the BYE to the other session leg.
The event handler method's code:
public void onBye(RequestEvent event, ActivityContextInterface aci) {
// send back 200 ok for this dialog right away, to avoid retransmissions
replyToRequestEvent(event, Response.OK);
// forward to the other dialog
processMidDialogRequest(event, aci);
}
The CANCEL request is received when a party wants to cancel the session, in such scenario the application first needs to check the state of the other leg, and if it is already established, a BYE request is sent instead of forwarding the CANCEL message,.
The event handler method's code:
public void onCancel(CancelRequestEvent event, ActivityContextInterface aci) {
if (tracer.isInfoEnabled()) {
tracer.info("Got a CANCEL request.");
}
try {
this.sipProvider.acceptCancel(event, false);
final ActivityContextInterface peerDialogACI = getPeerDialog(aci);
final DialogActivity peerDialog = (DialogActivity) peerDialogACI
.getActivity();
final DialogState peerDialogState = peerDialog.getState();
if (peerDialogState == null || peerDialogState == DialogState.EARLY) {
peerDialog.sendCancel();
} else {
peerDialog.sendRequest(peerDialog.createRequest(Request.BYE));
}
} catch (Exception e) {
tracer.severe("Failed to process cancel request", e);
}
}
The application defines a few helper methods to deal with message forwarding, providing example and optimal usage of the JAIN SIP RA APIs:
private void replyToRequestEvent(RequestEvent event, int status) {
try {
event.getServerTransaction().sendResponse(
sipProvider.getMessageFactory().createResponse(status,
event.getRequest()));
} catch (Throwable e) {
tracer.severe("Failed to reply to request event:\n" + event, e);
}
}
private void processMidDialogRequest(RequestEvent event,
ActivityContextInterface dialogACI) {
try {
// Find the dialog to forward the request on
ActivityContextInterface peerACI = getPeerDialog(dialogACI);
forwardRequest(event.getServerTransaction(),
(DialogActivity) peerACI.getActivity());
} catch (SipException e) {
tracer.severe(e.getMessage(), e);
replyToRequestEvent(event, Response.SERVICE_UNAVAILABLE);
}
}
private void processResponse(ResponseEvent event,
ActivityContextInterface aci) {
try {
// Find the dialog to forward the response on
ActivityContextInterface peerACI = getPeerDialog(aci);
forwardResponse((DialogActivity) aci.getActivity(),
(DialogActivity) peerACI.getActivity(), event
.getClientTransaction(), event.getResponse());
} catch (SipException e) {
tracer.severe(e.getMessage(), e);
}
}
private ActivityContextInterface getPeerDialog(ActivityContextInterface aci)
throws SipException {
final ActivityContextInterface incomingDialogAci = getIncomingDialog();
if (aci.equals(incomingDialogAci)) {
return getOutgoingDialog();
}
if (aci.equals(getOutgoingDialog())) {
return incomingDialogAci;
}
throw new SipException("could not find peer dialog");
}
private void forwardRequest(ServerTransaction st, DialogActivity out)
throws SipException {
final Request incomingRequest = st.getRequest();
if (tracer.isInfoEnabled()) {
tracer.info("Forwarding request " + incomingRequest.getMethod()
+ " to dialog " + out);
}
// Copies the request, setting the appropriate headers for the dialog.
Request outgoingRequest = out.createRequest(incomingRequest);
// Send the request on the dialog activity
final ClientTransaction ct = out.sendRequest(outgoingRequest);
// Record an association with the original server transaction,
// so we can retrieve it when forwarding the response.
out.associateServerTransaction(ct, st);
}
private void forwardResponse(DialogActivity in, DialogActivity out,
ClientTransaction ct, Response receivedResponse)
throws SipException {
// Find the original server transaction that this response
// should be forwarded on.
final ServerTransaction st = in.getAssociatedServerTransaction(ct);
// could be null
if (st == null)
throw new SipException(
"could not find associated server transaction");
if (tracer.isInfoEnabled()) {
tracer.info("Forwarding response "
+ receivedResponse.getStatusCode() + " to dialog " + out);
}
// Copy the response across, setting the appropriate headers for the
// dialog
final Response outgoingResponse = out.createResponse(st,
receivedResponse);
// Forward response upstream.
try {
st.sendResponse(outgoingResponse);
} catch (InvalidArgumentException e) {
tracer.severe("Failed to send response:\n" + outgoingResponse, e);
throw new SipException("invalid response", e);
}
}
The Root SBB XML Descriptor has to be provided and match the abstract class code.
First relevant part is the declaration of the sbb-classes
element, where the sbb class abstract name must be specified, along with the cmp fields:
<sbb-classes>
<sbb-abstract-class>
<sbb-abstract-class-name>org.mobicents.slee.example.sip11.b2b.SimpleSip11B2BTestSbb</sbb-abstract-class-name>
<cmp-field>
<cmp-field-name>incomingDialog</cmp-field-name>
</cmp-field>
<cmp-field>
<cmp-field-name>outgoingDialog</cmp-field-name>
</cmp-field>
</sbb-abstract-class>
</sbb-classes>
Then the events handled by the SBB must be specified too:
<!-- INITIALS EVENT, OUT OF DIALOG -->
<event event-direction="Receive" initial-event="True">
<event-name>InviteEvent</event-name>
<event-type-ref>
<event-type-name>javax.sip.message.Request.INVITE</event-type-name>
<event-type-vendor>net.java.slee</event-type-vendor>
<event-type-version>1.2</event-type-version>
</event-type-ref>
<initial-event-select variable="ActivityContext" />
</event>
<!-- EVERYTHING ELSE HAPPENS IN DIALOG -->
<event event-direction="Receive" initial-event="False">
<event-name>1xxResponse</event-name>
<event-type-ref>
<event-type-name>javax.sip.message.Response.PROVISIONAL</event-type-name>
<event-type-vendor>net.java.slee</event-type-vendor>
<event-type-version>1.2</event-type-version>
</event-type-ref>
</event>
<event event-direction="Receive" initial-event="False">
<event-name>2xxResponse</event-name>
<event-type-ref>
<event-type-name>javax.sip.message.Response.SUCCESS</event-type-name>
<event-type-vendor>net.java.slee</event-type-vendor>
<event-type-version>1.2</event-type-version>
</event-type-ref>
</event>
<event event-direction="Receive" initial-event="False">
<event-name>Bye</event-name>
<event-type-ref>
<event-type-name>javax.sip.Dialog.BYE</event-type-name>
<event-type-vendor>net.java.slee</event-type-vendor>
<event-type-version>1.2</event-type-version>
</event-type-ref>
</event>
<event event-direction="Receive" initial-event="False">
<event-name>Cancel</event-name>
<event-type-ref>
<event-type-name>javax.sip.message.Request.CANCEL</event-type-name>
<event-type-vendor>net.java.slee</event-type-vendor>
<event-type-version>1.2</event-type-version>
</event-type-ref>
</event>
Note that there is a single event defined as initial, which triggers the sbb logic, remaining events all happen in activities that the service instance is already attached, abstracting the application from calculating which session it handles.
Finally, the SIP11 Resource Adaptor must be specified also, otherwise SLEE won't put its SBB Interface in the SBB's JNDI Context:
<resource-adaptor-type-binding>
<resource-adaptor-type-ref>
<resource-adaptor-type-name>
JAIN SIP
</resource-adaptor-type-name>
<resource-adaptor-type-vendor>
javax.sip
</resource-adaptor-type-vendor>
<resource-adaptor-type-version>
1.2
</resource-adaptor-type-version>
</resource-adaptor-type-ref>
<activity-context-interface-factory-name>
slee/resources/jainsip/1.2/acifactory
</activity-context-interface-factory-name>
<resource-adaptor-entity-binding>
<resource-adaptor-object-name>
slee/resources/jainsip/1.2/provider
</resource-adaptor-object-name>
<resource-adaptor-entity-link>
SipRA
</resource-adaptor-entity-link>
</resource-adaptor-entity-binding>
</resource-adaptor-type-binding>