The REST Demonstration includes the components described below.
Metaswitch Call Notification REST API
The Rhino REST API Framework was used to create the MSW-CN REST API implementation (Creating a REST API). The generated module structure is:
msw-call-notification-rest-apimsw-call-notification-rest-api/msw-call-notification-api/src/api.yaml
msw-call-notification-rest-api/oma-call-notification-api/src/api.properties
msw-call-notification-rest-api/msw-call-notification-api
msw-call-notification-rest-api/msw-call-notification-api-ratype
msw-call-notification-rest-api/msw-call-notification-api-plugin
msw-call-notification-rest-api/msw-call-notification-api-client-sbbpart
-
The MSW-CN group module
-
The MSW-call-notification-api sub-module holds the API specification and a properties file. The other sub-modules have an ivy dependency on this module.
-
An OpenAPI formatted description of the MSW-CN API
-
A properties file that defines java packages, and the names of deployable units
-
Generates a RA type that includes events, provider interfaces and so on for a MSW-CN RA Type that can be deployed in Rhino
-
Generates a library component the Unified REST RA uses to load and drive the MSW-CN API
-
Generates a Jar containing a super-class that developers extend to create an sbb-part, for the client MSW-CN role, in their application. The generated super-class includes all event handlers and slee-annotations required to deploy such an sbb-part in Rhino
|
|
Developers may create multiple REST API modules. A REST RA can support one or more such REST APIs. |
Call Notification Resource Adaptor
The Rhino REST API Framework was used to create the Call Notification Resource Adaptor (Creating a REST Resource Adaptor).
The REST Demonstration REST resource adaptor module is: call-notification-ra
The call-notification-ra is an Ant/Ivy build module that depends on the Unified REST RA Core and the Metaswitch Call Notification REST API.
The call-notification-ra produces a Unified REST RA, that supports MSW-CN, used in Sentinel-SIP.
|
|
Developers may create multiple REST RA modules, each with different sets of APIs to suit different applications. |
Call Notification Client Event Handler
The MSW-CN event handler sbbpart (rest-demo-feature-modules/msw-call-notification-client-event-handler) contains an sbb-part that extends the class generated in msw-call-notification-rest-api/msw-call-notification-api-client-sbbpart.
The MswCallNotificationClientEventHandlerSbbpart class extends the MswCallNotificationApiClientSbbpart class and implements behaviour on receipt of call direction and call event notification response events.
The super class includes the required SBBPart, RATypeBinding and EventMethod slee annotations.
package com.opencloud.openapi.demo;
// msw call notification API
import com.opencloud.openapi.demo.msw_cn.api.*;
// super-class for an sbb-part to process
// msw call notification related response events
import com.opencloud.openapi.demo.msw_cn.impl.*;
// to bind the event handler sbbpart to the sentinel-sip service
@BinderTargets(services="sip")
public class MswCallNotificationClientEventHandlerSbbpart extends MswCallNotificationClientSbbpart {
public MswCallNotificationClientEventHandlerSbbpart(SbbPartContext context) { super(context); }
@Override
protected void handleCallDirectionNotification_200_SuccessResponse(
CallDirectionNotification_200_SuccessResponse response,
ActivityContextInterface aci,
EventContext eventContext) throws IOException
{
submitResponseEventToSentinel(
"CallDirectionNotification", response, response.getStatusCode(),
"SuccessResponse", aci, eventContext);
}
@Override
protected void handleCallDirectionNotificationDefaultResponse(
CallDirectionNotificationDefaultResponse response,
ActivityContextInterface aci,
EventContext eventContext) throws IOException
{
submitResponseEventToSentinel(
"CallDirectionNotification", response, response.getStatusCode(),
"DefaultResponse", aci, eventContext);
}
@Override
protected void handleCallEventNotification_200_SuccessResponse(
CallEventNotification_200_SuccessResponse response,
ActivityContextInterface aci,
EventContext eventContext) throws IOException
{
submitResponseEventToSentinel(
"CallEventNotification", response, response.getStatusCode(),
"SuccessResponse", aci, eventContext);
}
@Override
protected void handleCallEventNotificationDefaultResponse(
CallEventNotificationDefaultResponse response,
ActivityContextInterface aci,
EventContext eventContext) throws IOException
{
submitResponseEventToSentinel(
"CallEventNotification", response, response.getStatusCode(),
"DefaultResponse", aci, eventContext);
}
// Submit response event to sentinel runtime
// for delivery to a feature for processing
private void submitResponseEventToSentinel(
String operation, Object response, int statusCode,
String description,
ActivityContextInterface aci, EventContext eventContext)
{
final SentinelActivityContextInterface sentinelAci =
getSbbPartContext().asSbbPartActivityContextInterface(aci);
final String target = sentinelAci.getTargetFeature();
if (target == null || target.isEmpty()) {
getTracer().fine("No target feature for response, ignore");
return;
}
getTracer().finer("Pass {} {} {} to target feature \"{}\"",
operation, statusCode, description, target);
try {
SentinelEndpoint sentinelEndpoint =
(SentinelEndpoint) getSbbPartContext().getSbbLocalObject();
sentinelEndpoint.processEvent(
target, response, false, true, aci, eventContext);
}
catch (SentinelFireEventException | IllegalArgumentException | NullPointerException e)
{
getTracer().severe("Exception processing {} {} {} event",
operation, statusCode, description, e);
}
}
}
-
override method in
MswCallNotificationClientSbbpartto handle a CallDirectionNotification 200 Success response -
override method in
MswCallNotificationClientSbbpartto handle a CallDirectionNotification default response (i.e all other CallDirectionNotification responses) -
override method in
MswCallNotificationClientSbbpartto handle a CallEventNotification 200 Success response -
override method in
MswCallNotificationClientSbbpartto handle a CallEventNotification default response (i.e all other CallEventNotification responses) -
find the feature the response event should be processed by
-
submit the event to the Sentinel-SIP runtime for processing by the target feature
Set Session Plan Feature
The RestSetSessionPlan feature (rest-demo-feature-modules/msw-set-session-plan-feature) updates the sentinel selection key PlanID field with the REST Demonstration session plan ID.
|
|
Learn about the Sentinel Selection Key and read Features for an introduction to Sentinel features. |
package com.opencloud.openapi.demo;
@SentinelFeature(
featureName = RestSetSessionPlan.NAME,
componentName = "@component.name@",
featureVendor = "@component.vendor@",
featureVersion = "@component.version@",
featureGroup = SentinelFeature.SIP_FEATURE_GROUP,
executionPhases = ExecutionPhase.SipSessionPhase,
usageStatistics = RestSetSessionPlanStats.class
)
// to bind the feature to the sentinel-sip service
@BinderTargets(services = "sip")
public class RestSetSessionPlan
extends BaseFeature<SentinelSipSessionState, SentinelSipMultiLegFeatureEndpoint>
implements InjectFeatureStats<RestSetSessionPlanStats>
{
public static final String NAME = "RestSetSessionPlan";
// ...
@Override
public void startFeature(Object trigger, Object activity, ActivityContextInterface aci) {
final SentinelSelectionKey selectionKey = getSessionState().getSentinelSelectionKey();
// Set the plan ID in the selectionKey to 'rest_demo'
selectionKey.setPlanId(REST_DEMO_SESSION_PLAN);
getTracer().finest("SentinelSelectionKey set: {}", selectionKey);
getSessionState().setSentinelSelectionKey(selectionKey);
getCaller().featureHasFinished();
}
private static final String REST_DEMO_SESSION_PLAN = "rest_demo";
}
The value of the SentinelSelectionKey is used by the Sentinel run-time to resolve feature configuration, and the feature execution scripts that run.
|
|
There are many alternate approaches to deriving a plan ID. For example the plan ID could be derived by analysing headers in the SIP INVITE. |
|
|
Session plans, the Sentinel-SIP session plan and feature execution scripts are explained here: Session Plans, SIP Sessions Initiated via SIP INVITE and Feature Execution Scripts. |
Send Call Notification Feature
The RestSendCallNotification feature (rest-demo-feature-modules/msw-send-call-notification) sends CallDirection and CallEvent notification requests and processes the corresponding responses.
The RestSendCallNotification feature:
-
determines the type of notification event to send, if appropriate, based on the point-in-session and the triggering SIP event.
-
will influence the current SIP session based on CallDirection response events. For example the session may be ended, routed to an alternate destination or allowed to continue.
@Override
public void startFeature(Object trigger,
Object activity,
ActivityContextInterface aci)
{
// Find the active point-in-session
final SipFeatureScriptExecutionPoint executionPoint =
getSessionState().getCurrentSipFeatureExecutionPoint();
if (trigger instanceof IncomingSipRequest
|| trigger instanceof IncomingSipResponse)
{
// Determine the notification type and call event
final CallNotificationTypes notificationType;
final CallEvents callEvent;
if (trigger instanceof IncomingSipRequest) {
final IncomingSipRequest request = (IncomingSipRequest) trigger;
notificationType = determineNotificationType(
executionPoint, request);
callEvent = determineCallEvent(executionPoint, request);
}
else {
final IncomingSipResponse response = (IncomingSipResponse) trigger;
notificationType = determineNotificationType(
executionPoint, response);
callEvent = determineCallEvent(executionPoint, response);
}
if (null == notificationType || null == callEvent) {
getCaller().featureHasFinished();
return;
}
final LegManager lm = getCaller().getLegManager();
final Leg calledPartyLeg = lm.getCallingPartyLeg().getLinkedLeg();
// if there is no linked leg, then this response will have been received
// on a leg associated with an MRF (for an announcement).
// A notification is not required in this case.
if (null == calledPartyLeg) {
final Leg leg = getCaller().getLegManager().getLeg(aci);
getTracer().finest("A notification is not required at: {} for response on leg {}",
executionPoint, leg.getLegName());
getCaller().featureHasFinished();
return;
}
// send a notification
// if the leg is not active, a sip session has not been created yet
// if there is no INVITE request, then an INVITE has not
// been sent on the cld leg yet
if (calledPartyLeg.isActive()
&& null != calledPartyLeg.getInviteRequest())
{
sendNotification(calledPartyLeg, notificationType, callEvent);
}
else {
sendNotification(aci, notificationType, callEvent);
}
}
else {
// Process a notification response event
if (processNotificationResponse(
executionPoint, trigger, activity, aci))
{
getCaller().featureHasFinished();
}
getCaller().detach(aci);
}
}
-
Find the currently execution feature script execution point.
-
Determine the notification type and call event based on the triggering SIP message, and the point-in-session
-
Send a notification request message
-
Process a
CallDirectionorCallEventnotification response event.
Determining the MSW-CN notification event to send
// Determine the notification type from the triggering SIP request
private CallNotificationTypes determineNotificationType(
SipFeatureScriptExecutionPoint executionPoint,
IncomingSipRequest request)
{
switch (executionPoint) {
// the initial trigger
case SipAccess_SessionStart:
return CallNotificationTypes.CALLDIRECTION;
// one of the participants hangs up
case SipMidSession_PartyRequest:
return "BYE".equals(request.getMethod())
? CallNotificationTypes.CALLDIRECTION : null;
// a notification is not required
default: return null;
}
}
// Determine the notification type from the triggering SIP response
private CallNotificationTypes determineNotificationType(
SipFeatureScriptExecutionPoint executionPoint,
IncomingSipResponse response)
{
switch (executionPoint) {
case SipAccess_PartyResponse:
if (! "INVITE".equals(response.getRequest().getMethod()))
return null;
switch (response.getStatus()) {
case SipResponse.SC_OK:
return CallNotificationTypes.CALLEVENT;
// 486 - called party busy
case SipResponse.SC_BUSY_HERE:
// 404 - no route to destination
case SipResponse.SC_NOT_FOUND:
// 408 - no answer
case SipResponse.SC_REQUEST_TIMEOUT:
// 480 - no answer
case SipResponse.SC_TEMPORARLY_UNAVAILABLE:
return CallNotificationTypes.CALLDIRECTION;
// a notification is not required
default: return null;
}
case SipMidSession_PartyResponse:
if (SipResponse.SC_OK == response.getStatus()
&& "BYE".equals(response.getRequest().getMethod()))
return CallNotificationTypes.CALLEVENT;
// otherwise a notification is not required
return null;
// a notification is not required
default: return null;
}
}
-
At
SipAccess_SessionStartSentinel-SIP is processing a triggeringINVITErequest, so send aCallNotificationTypes.CALLDIRECTIONrequest. AtSipMidSession_PartyRequestwe are interested inBYErequests and send aCallNotificationTypes.CALLDIRECTIONrequest. -
At
SipAccess_PartyResponsewe are interested in responses to the triggeringINVITErequest. On answer, send aCallNotificationTypes.CALLEVENTrequest, otherwise send aCallNotificationTypes.CALLDIRECTIONrequest. AtSipMidSession_PartyResponsewe are interested in responses toBYErequests and send aCallNotificationTypes.CALLEVENTrequest.
// Determine the CallEvent from the triggering SIP request
private CallEvents determineCallEvent(
SipFeatureScriptExecutionPoint executionPoint,
IncomingSipRequest request)
{
switch (executionPoint) {
// the initial trigger
case SipAccess_SessionStart:
return CallEvents.CALLEDNUMBER;
// one of the participants hangs up
case SipMidSession_PartyRequest:
return "BYE".equals(request.getMethod())
? CallEvents.DISCONNECTED : null;
// a notification is not required
default: return null;
}
}
// Determine the CallEvent from the triggering SIP response
private CallEvents determineCallEvent(
SipFeatureScriptExecutionPoint executionPoint,
IncomingSipResponse response)
{
switch (executionPoint) {
case SipAccess_PartyResponse:
if (! "INVITE".equals(response.getRequest().getMethod()))
return null;
switch (response.getStatus()) {
case SipResponse.SC_OK:
return CallEvents.ANSWER;
// 486 - called party busy
case SipResponse.SC_BUSY_HERE:
return CallEvents.BUSY;
// 404 - no route to destination
case SipResponse.SC_NOT_FOUND:
return CallEvents.NOTREACHABLE;
// 408 - no answer
case SipResponse.SC_REQUEST_TIMEOUT:
// 480 - no answer
case SipResponse.SC_TEMPORARLY_UNAVAILABLE:
return CallEvents.NOANSWER;
// a notification is not required
default: return null;
}
case SipMidSession_PartyResponse:
if (SipResponse.SC_OK == response.getStatus()
&& "BYE".equals(response.getRequest().getMethod()))
return CallEvents.DISCONNECTED;
// otherwise a notification is not required
return null;
// a notification is not required
default: return null;
}
}
-
At
SipAccess_SessionStartSentinel-SIP is processing a triggeringINVITErequest (aCallEvents.CALLEDNUMBERevent). AtSipMidSession_PartyRequestwe are interested inBYErequests (CallEvents.DISCONNECTEDevents) -
At
SipAccess_PartyResponsewe are interested in responses to the triggeringINVITErequest (CallEvents.ANSWER,CallEvents.BUSYand so on). AtSipMidSession_PartyResponsewe are interested in responses toBYErequests (CallEvents.DISCONNECTEDevents).
Sending CallDirection and CallEvent notification requests
The RestSendCallNotification feature sends CallDirection and CallEvent notification requests in the following way.
// Send a call event notification
private void sendNotification(
String from, String fromDisplayName, String to,
CallNotificationTypes notificationType, CallEvents callEvent)
{
final SendCallNotificationConfig configuration =
configurationReader.getConfiguration(
getSessionState().getSentinelSelectionKey());
final ApiConfiguration apiConfiguration =
ApiConfiguration.standardConfiguration();
apiConfiguration.set(ApiOptions.destinationUrl,
configuration.getDestinationUrl());
final MswCallNotificationRaTypeApiClient client =
cnProvider.getApiClient(apiConfiguration);
// Create a new CallEventNotification object
final CallEventNotification notification = new CallEventNotification();
try {
notification.notificationType(notificationType)
.eventDescription(new EventDescription().callEvent(callEvent))
.calledParticipant(new URI(to))
.callingParticipant(new URI(from))
.callingParticipantName(fromDisplayName);
}
catch (URISyntaxException e) {
getTracer().severe(
"Failed to extract participants from: {}, to: {}", from, to, e);
}
// Create a REST request to send
final NotificationApi nApi = client.getNotificationApi();
final RestRequestBuilder notificationRequest =
notificationType == CallNotificationTypes.CALLEVENT
? nApi.createCallEventNotificationRequest(notification)
: nApi.createCallDirectionNotificationRequest(notification);
try {
// Send the REST request
final OutgoingRestActivity outgoingRestActivity =
notificationApi.sendRequest(notificationRequest);
final ActivityContextInterface newAci =
cnACIFactory.getActivityContextInterface(outgoingRestActivity);
final SentinelActivityContextInterface outgoingAci =
getCaller().asSentinelActivityContextInterface(newAci);
stats.incrementSendCallNotification(1);
// Set the target feature to 'RestSendCallNotification'
outgoingAci.setTargetFeature(NAME);
// Attach to the new activity, wait for the response
outgoingAci.attach(getFacilities().getSbbLocalObject());
getTracer().finest("Notification sent. Waiting for response");
getCaller().featureWaiting(outgoingAci);
}
catch (IOException e) {
getCaller().featureFailedToExecute(
new FeatureError(FeatureError.Cause.unclassified,
"Failed to send notification", e));
}
}
-
The
CallEventNotificationhas:-
a notification type (
CallNotificationTypes.CALLEVENTorCallNotificationTypes.CALLDIRECTION) -
a call event (
CallEvents.CALLEDNUMBER,CallEvents.ANSWERand so on) -
called participant (the
Toaddress), calling participant (theFromaddress) and the calling participant display name
-
-
Create an appropriate REST request message by using the MSW-CN
NotificationApi. -
Send the REST request. The Unified REST RA returns an
OutgoingRestActivityrelated to the sent REST request. -
Set
TargetFeatureon theActivityContextInterfacetoRestSendCallNotificationso Sentinel-SIP will route the corresponding response to the currently executing feature. -
Attach to the activity and tell Sentinel-SIP that we are waiting for a response. Sentinel-SIP pauses executing the active feature execution script until a response event arrives.
Processing CallDirection 200 Success response events
A CallDirectionNotification success response includes instructions that must be followed to effect ongoing call processing.
private boolean processDirectionNotificationSuccessResponse(
SipFeatureScriptExecutionPoint executionPoint,
CallDirectionNotification_200_SuccessResponse trigger,
Object activity)
{
// Determine the action to perform
final Action actionToTake = trigger.getContent();
if (null == actionToTake || null == actionToTake.getActionToPerform()) {
stats.incrementCallDirectionResponseWithNoActionToTake(1);
return true;
}
// Apply the requested Action
switch(actionToTake.getActionToPerform()) {
case ROUTE:
// Get the new destination
final URI newDestination = actionToTake.getRoutingAddress();
if (null == newDestination) {
stats.incrementCallDirectionNoRoutingAddress(1);
return true;
}
final org.jainslee.resources.sip.URI sipDestination =
getSipUri(newDestination);
if (null == sipDestination) {
stats.incrementCallDirectionNonSipRoutingAddress(1);
return true;
}
stats.incrementCallDirectionRoute(1);
// route the call to the new destination
routeToNewDestination(executionPoint, sipDestination);
return true;
case CONTINUE:
// Allow the call to continue without interruption
stats.incrementCallDirectionContinue(1);
return true;
case ENDCALL:
// End the call
stats.incrementCallDirectionEndCall(1);
// Check to see if an annoucement should be played
final String announcementIdStr = actionToTake.getAnnouncementId();
if (null == announcementIdStr) {
getTracer().fine("Action is to ENDCALL");
// defer to the leg manager to end the session
final LegManager lm = getCaller().getLegManager();
lm.endSession(SipResponse.SC_FORBIDDEN);
}
else {
// Schedule an announcement
try {
// announcement ID should be a Long.
final long announcementId = Long.parseLong(announcementIdStr);
getTracer().fine("Action is to ENDCALL after playing announcement: {}",
announcementIdStr);
// tell sentinel-sip about the announcement to be played
final List<SipAnnouncementInformation> announcementQueue =
getSessionState().getEarlyMediaAnnouncementInfoQueue();
announcementQueue.add(SipAnnouncementInformation.getBuilder()
.setAnnouncementId(announcementId).build());
getSessionState().setEarlyMediaAnnouncementInfoQueue(announcementQueue);
// tell sentinel-sip to end the session after the announcement
getSessionState().setEndSessionAfterAnnouncement(SipResponse.SC_FORBIDDEN);
// used in rest-demo feature execution scripts
getSessionState().setPlayAnnouncementThenEndSession(true);
}
catch (NumberFormatException nfe) {
getTracer().fine("Announcement id: {} is not a valid. Ending the call.", announcementIdStr);
// defer to the leg manager to end the session
final LegManager lm = getCaller().getLegManager();
lm.endSession(SipResponse.SC_FORBIDDEN);
}
}
// generate a DISCONNECTED notification
sendNotification(
lm.getCallingPartyLeg(),
CallNotificationTypes.CALLEVENT,
CallEvents.DISCONNECTED);
return false; // sent a notification, feature has not finished
}
return true;
}
-
The
CallDirectionNotification200 success response body contains anActionobject that dictates how the active SIP session should be processed. -
The
ActionToPerformis one ofROUTE,CONTINUEorENDCALL -
If the action to perform is
ROUTEtheactionToTakewill also include a new destination address. -
Route the call to the new adddress.
-
If the action to perform is
CONTINUEallow the call to continue uninterrupted. -
If the action to perform is
ENDCALLdelegate to Sentinel-SIP. -
The
actionToTakemay, optionally, include an announcement ID -
If
actionToTakeincludes an announcement ID, then update session state, so thePlayAnnouncementfeature can play the announcement -
Generate a
DISCONNECTEDnotification
|
|
Learn more about the SIP Play Announcement Feature. |
Feature Execution Scripts
The RestSetSessionPlan feature (rest-demo-feature-modules/msw-set-session-plan-feature) updates the sentinel selection key to with the REST Demonstration session plan.
Sentinel-SIP uses the sentinel-selection-key when resolving the feature execution scripts to run at each point in session.
|
|
For more details on the sentinel selection key, feature execution scripts and Sentinel-SIP feature script execution points see: Sentinel Selection Key Feature Execution Scripts and Sentinel SIP Feature Execution Points |
The following feature execution scripts have been added for the REST Demonstration.
| Point-in-Session | feature Execution Script |
|---|---|
|
featurescript RestSessionStart {
if not LegManager.endSession
and not LegManager.detachAll
and not LegManager.currentLeg.releaseLeg
{
run AcceptSip
run RestSetSessionPlan
run DoNotChargeSipSession
}
}
|
|
featurescript DemoPostSessionStart {
if not LegManager.endSession {
run RecordTimestamps mode "outbound"
run RestSendCallNotification
if session.PlayAnnouncementThenEndSession {
run SipPlayAnnouncement
}
}
run DetermineCauseCode
run DiameterServiceInfo
run DiameterPerLegInfo mode "outbound"
}
|
|
featurescript RestDemoSessionCheck {
if not session.PlayAnnouncementThenEndSession {
run SipSubscriberDetermination
run CallPartiesAddressDetermination
run SessionTracing
}
}
|
|
featurescript RestDemoSubscriberCheck {
if not session.PlayAnnouncementThenEndSession {
run SubscriberDataLookup
run SubscriberValidity
}
}
|
|
featurescript RestSipAccessPartyRequest {
run RestSendCallNotification
if session.PlayAnnouncementThenEndSession {
run SipPlayAnnouncement
}
}
|
|
featurescript RestSipAccessPartyResponse {
run RestSendCallNotification
if session.PlayAnnouncementThenEndSession {
run SipPlayAnnouncement
}
}
|
|
featurescript DemoMidSessionPartyRequest {
run RestSendCallNotification
if session.PlayAnnouncementThenEndSession {
run SipPlayAnnouncement
}
}
|
|
featurescript DemoMidSessionPartyResponse {
run RestSendCallNotification
if session.PlayAnnouncementThenEndSession {
run SipPlayAnnouncement
}
}
|
