Skip Headers
Oracle® Communications Converged Application Server Developer's Guide
Release 5.1

Part Number E27707-01
Go to Documentation Home
Home
Go to Book List
Book List
Go to Table of Contents
Contents
Go to Feedback page
Contact Us

Go to previous page
Previous
Go to next page
Next
PDF · Mobi · ePub

21 Implementing Call Control Services

A key benefit of the Service Foundation Toolkit (SFT) is support for GSM Association's (GSMA) IR.92 specifications for delivering Voice over LTE (VoLTE).

About Converged Application Framework and VoLTE

SFT provides enhanced APIs that you can use to quickly and easily implement applications for delivering IR.92-compliant supplementary services over VoLTE. The APIs provide support for supplementary services such as Call Forwarding, Incoming and Outgoing Call Barring, ID Presentation and Restriction, Multi-Party Conferencing, and Message Waiting Indication (MWI).

GSM Association's (GSMA) IR.92 specifications defines the IP Multimedia Subsystem (IMS) profile for delivering Voice over LTE (VoLTE). The GSMA VoLTE initiative has defined IMS as the common way to deliver voice and messaging services over mobile broadband all-IP networks.

In September 2010, GSMA published the IREG Permanent Reference Document IR.92, which outlines the specifications for migrating 2G and 3G mobile voice, video and messaging services to 4G mobile broadband networks, such as LTE.

This chapter describes the following call control services:

Call Forwarding

Call Forwarding (also referred to as Call Diversion) lets users of the service (the called party, or callee) forward incoming calls to another phone number (the third party). The third party may be a mobile telephone, voice-mail box, or other telephone number where the desired called party is situated.

With Call Forwarding activated:

Converged Application Server supports the following IR.92 defined Call Forwarding modes for VoLTE:

Call Forwarding applications forward calls by removing the callee (the person to whom the call is being made), and adding a new participant (the third party) in the calle's place. How the SFT handles the call signaling depends on what stage the conversation is at. For example, if the Call Forwarding application replaces the callee during the STARTED event, SFT changes the callee to the new participant. However, if the application replaces the callee during the JOINING event, SFT sends a CANCEL event to the original callee before sending a SIP INVITE request to the new participant.

Example 21-1 illustrates the removal of a the callee during the INITILIZATION event, who is then with the participant: alice@example.com.

Example 21-1 Replacing the Callee With A New Participant

...

@CommunicationEvent(type = CommunicationEvent.Type.INITIALIZATION)
  void handleInit() {
      Conversation call = ctx.getCommunication();
      call.removeParticipant(call.getCallee());
      call.addParticipant("alice@example.com");
  }
...

This example creates the handleInit() method which is annotated with @CommunicationEvent, indicating that a communication session is being initialized (as indicated by the Type.INITIALIZATION constant). The method declares the instance variable call, which it initializes using the Conversation class. The Conversation class represents a two-party call, and extends the Communication object which represents the communication session. The call variable retrieves the context of the communication via the @CommunicationContext object (stored in the previously initialized ctx variable) using the getCommunication() method.

After instantiating call, the code calls the variable's removeParticipant() method, which in turn calls the getCallee() method to retrieve the identity of the callee from the Conversation class and remove them. Once the callee is removed from the conversation, the call variable can call the addParticpant() method (which the Conversation class inherits from the Interaction class) and adds the new participant (in this example, alice@example.com) to the communication.

Accessing Call Forwarding History

You can access a user's call forwarding (also referred to a call diversion) history and use it to create a call forwarding policy. For example, you may want to limit the total number of call forwarding diversions by looking at the caller's history.

The SIP History-Info header (defined in RFC 4244) provides a way of capturing any redirection information that may have occurred on a particular call. The SIP History-Info header is generated when a SIP request is re-directed, and can appear in any SIP request not associated with a dialog. The SIP History-Info header is generated by a User Agent (UA) or proxy, and is passed from one entity to another through requests and responses.

Example 21-1 illustrates how to retrieve call history information using the HistoryInformation class. HistoryInformation encapsulates the call forwarding (call diversion) history. In this example:

  • HistoryInformation accesses the call forwarding history. If the number of call diversions is greater than 50, the call is rejected.

  • HistoryElement retrieves the call diversion history from the History-Info header, which is maintained as a TreeSet representing the chronological order of the call diversions (see Table 21-1 for more information). If the number of call diversions is greater than 10, the call is rejected.

  • If the call is rejected due to too many call diversions, the initial participant (the callee) is replaced with the participant: tom@example.com

Example 21-2 Accessing Call Forwarding History

@Context CommunicationContext<Conversation, UserParticipant, ?> ctx;
HistoryInformation hi = ctx.getContextElement(HistoryInformation.class);
TreeSet<HistoryElement> elements = hi.getElements();
int totalDiversions = elements.size();
 
if (totalDiversions > 50) {
//Reject the caller if the number of diversions is greater than 50.
Conversation call = ctx.getCommunication();
call.removeParticipant(call.getCaller());
}
 
//Get the history via elements.
TreeSet<HistoryElement> lastLegElements = (TreeSet) elements.tailSet(elements);
if (lastLegElements.size() > 10) {
//Reject the caller if the number of diversions is greater than 10.
 }
 
//Forward the caller if their call is rejected too many times.
Conversation call = ctx.getCommunication();
call.removeParticipant(call.getCallee());
call.addParticipant("tom@example.com");     

Table 21-1 lists the methods defined by the HistoryInformation interface to access a user's call forwarding history.

Table 21-1 Methods Defined by the HistoryElement interface

Method Description

getElements()

Returns the HistoryElement as a TreeSet. The TreeSet is an index (in chronological order) of the call diversion. An empty TreeSet is returned if there is no prior history.

TreeSet provides an implementation of the Set interface that uses a tree for storage. Objects are stored in sorted, ascending order. Access and retrieval times are very fast, which makes TreeSet an excellent choice when storing large amounts of sorted information that must be found quickly.


Table 21-2 lists the methods defined by the HistoryElement interface to access a user's call forwarding history. The HistoryElement class represents an element of call diversion history, and provides all the information about previous call diversions. SFT injects an instance of this class each time a call is diverted.

Table 21-2 Methods Defined by the HistoryInformation Interface

Methods Description

getAlternateReasonData()

A History element can have an alternate reason, which is embedded in the message. Returns an instance of ReasonData.

getIndexAsArray()

An index of the call history. The History is in the Augmented BNF (ABNF) format. ABNF is a plain-text (non-XML) representation that is similar to traditional BNF grammar. 1*DIGIT *(DOT 1*DIGIT).

getIndexString()

Get the index of this call diversion history element.

getParameters()

Retrieves all parameters as a Set of name-value pairs.

getReasonData()

The reason associated with this call diversion history element. Returns an instance of ReasonData.

getTargetHost()

The host involved in the call diversion.

getTargetUser()

The specific user involved in the call diversion.


Discovering Call Reject Reasons

You can discover the reason a callee rejects a call using the EventReason class to retrieve information about reject events. EventReason implements the getReasonData() method, which returns call reject information stored in the SIP request's Reason header. You can implement Call Forwarding in response to call rejection, and forward the call to a voice mail box or other specified end point. See RFC 3326 to learn more about the Reason header field.

Example 21-3 shows how to discover call reject reasons using the EventReason class.

Example 21-3 Discovering Call Reject Reasons

@ParticipantEvent(type = ParticipantEvent.Type.REJECTED)
    void handleReject() {
        EventReason er = ctx.getContextElement(EventReason.class);
        ReasonData data = er.getReasonData();
        if (data.getReasonType() == Reason.BUSY) {
        //Followed by logic to forward the call if getREasonType() returns BUSY.
        }
    }
...

This example creates the method handleReject(), which uses the @ParticipantEvent annotation to initialize it using the type.REJECTED constant. This method is called if a participant refuses to join the communication.

The er reference variable points to the EventReason class, and is assigned the context of the communication via the @CommunicationContext object (which is stored in the ctx class variable) using the getContextElement(EventReason.class) method. The er variable stores the context of the type.REJECTED communication event.

The data reference variable points to the ReasonData class, and is assigned the call reject information from the SIP Reason header via the er variable's to call the getReasonData() method, which returns the reason for call rejection. If the returned reason code is BUSY, you can implement call forwarding.

Call Forwarding Example

Example 21-4 shows the code for the Java class CallForwardBean.

  • All calls to Bob are unconditionally forwarded to Amy.

  • Calls to Amy are forwarded to Carol when Amy's phone is busy.

  • Calls to Carol are forwarded to Tom when Carol is not available.

Additionally, this example implements the HistoryInformation and HistoryElement interfaces to retrieve the call forwarding history.

Example 21-4 Call Forwarding

package com.oracle.sft.demo;
 
import java.util.Iterator;
import java.util.TreeSet;
 
import com.oracle.sft.api.CommunicationContext;
import com.oracle.sft.api.CommunicationSession;
import com.oracle.sft.api.Context;
import com.oracle.sft.api.Conversation;
import com.oracle.sft.api.Reason;
import com.oracle.sft.api.UserParticipant;
import com.oracle.sft.api.bean.CommunicationBean;
import com.oracle.sft.api.bean.CommunicationEvent;
import com.oracle.sft.api.bean.ParticipantEvent;
import com.oracle.sft.api.context.EventReason;
import com.oracle.sft.api.context.HistoryElement;
import com.oracle.sft.api.context.HistoryInformation;
import com.oracle.sft.api.context.ReasonData;
 
@CommunicationBean
public class CallForwardBean {
 
  @Context CommunicationSession session;
  @Context CommunicationContext<Conversation,UserParticipant,?> ctx;
 
   //Forward all incoming calls for Bob to Amy.
  @CommunicationEvent(type = CommunicationEvent.Type.INITIALIZATION)
  void handleInit() {
      Conversation call = (Conversation) ctx.getCommunication();
      if (call.getCallee().getName().equals("bob@example.com")) {
          call.removeParticipant(call.getCallee());
          call.addParticipant("amy@example.com");  
 
      }
  }
  //Forward Amy's calls to Carol when Amy's phone is busy.
  //If Carol is not availalbe, forward calls to Tom
  @ParticipantEvent(type= ParticipantEvent.Type.REJECTED)
  void handleRejected() {
      Conversation call = (Conversation) ctx.getCommunication();
      EventReason er = ctx.getContextElement(EventReason.class);
      
      HistoryInformation hi = null;
      hi = ctx.getContextElement(HistoryInformation.class);
      printHistoryInformation(hi);
      if (er!=null){
        
        ReasonData rd = er.getReasonData();
        if (rd.getReasonType()==Reason.BUSY){
          if (call.getCallee().getName().equals("amy@example.com")) {
              call.removeParticipant(call.getCallee());
              call.addParticipant("carol@example.com");
 
          }
        }
        else if(rd.getReasonType()==Reason.NOT_FOUND){
            if (call.getCallee().getName().equals("carol@example.com")) {
                call.removeParticipant(call.getCallee());
                call.addParticipant("tom@example.com");
            }
        }
        else if(rd.getReasonType()==Reason.NO_RESPONSE){
          if (call.getCallee().getName().equals("carol@example.com")) {
              call.removeParticipant(call.getCallee());
              call.addParticipant("tom@example.com");
          }
      }
    }
  }
}

Call Barring

Call Barring lets users bar (or restrict) certain or all types of calls to and from their phone. For example, a user can restrict outgoing calls, outgoing international calls, or incoming calls from undesirable callers.

Converged Application Server supports the following IR.92 defined Call Barring modes for VoLTE:

To implement international call barring, the Call Barring application must have access to the phone number of the participant. To obtain the phone number, use the PhoneNumber class, which you can access via the UserParticipant interface. Similarly, to decide the roaming status of the user, the Call Barring application must access the private identity information available in the message. The IdentityInformation class provides this information. Note that IdentityInformation only represents information contained in the SIP Request that causes the current event. The application can also access this information from other sources, such as the Registrar, to determine roaming status. Similarly, the profile of the user—such as country of origin—can be obtained by the application using other interfaces (for example, the Diameter interface).

Table 21-3 lists the methods defined in the PhoneNumber interface to access a user's telephone number information.

Table 21-3 Methods Defined In The PhoneNumber Interface

Method Description

getContext()

Returns any phone-context present in the number as a string value.

getNumber()

Returns the phone number as a string value.

isGlobal()

Returns if the number is a global number or local number. If the number is global, it returns true; if the phone number is local it returns false.


Table 21-4 lists the methods defined in the IdentityInformation interface to access the identity information available in the context of the Event. The identity information is stored in the P headers of the SIP message.

Table 21-4 Methods defined in the IdentityInformation interface

Method Description

getAssertedIdentity

Represents P-Asserted-Id header value.

getServedUserIdentity

Represents P-Served-User header value.

getVisitedNetworkIdentity

Represents P-Visited-Network-Id header value.

getAccessNetworkInformation

Represents P-AccessNetwork-Info header value.


Example 21-5 illustrates the use of the PhoneNumber and IdentityInformation classes to bar international calls when roaming.

Example 21-5 Using The PhoneNumber And IdentityInformation Interfaces

@ Context CommunicationContext<Conversation,UserParticipant> ctx =
 ...
      Conversation call = ctx.getCommunication();
      UserParticipant callee = (UserParticipant) ctx.getCallee();
      UserParticipant caller = (UserParticipant) ctx.getCaller();
 ...
      IdentityInformation ii = ctx.getContextElement(IdentityInformation.class);
      if (isRoaming(ii)) {
        caller.reject(Reason.DECLINE);
      }
 ...
      PhoneNumber ph = callee.getPhoneNumber();
      if (isInternationalCall(ph, caller)) {
        caller.reject(Reason.DECLINE);
      }
...

Example 21-6 shows code for the Java class CallBarBean, which creates rules for barring incoming, outgoing, and roaming calls.

Example 21-6 Call Barring For Outgoing, Incoming, And Roaming Calls

package com.oracle.sft.demo;
import com.oracle.sft.api.CommunicationContext;
import com.oracle.sft.api.Context;
import com.oracle.sft.api.Conversation;
import com.oracle.sft.api.Reason;
import com.oracle.sft.api.UserParticipant;
import com.oracle.sft.api.bean.CommunicationBean;
import com.oracle.sft.api.bean.ParticipantEvent;
import com.oracle.sft.api.context.IdentityInformation;
 
@CommunicationBean
public class CallBarBean {
  @Context CommunicationContext<Conversation,UserParticipant> ctx;
  
@ParticipantEvent(type= ParticipantEvent.Type.JOINING, 
communicationType = Conversation.class)
  public void hanldeStatedEvent() {
    Conversation call = (Conversation) ctx.getCommunication();
    UserParticipant callee = (UserParticipant) call.getCallee();
    UserParticipant caller = (UserParticipant) call.getCaller();   
    if (caller.getName().equals("alice@example.com")) {
        caller.reject(Reason.DECLINE);
    }
    else if (callee.getName().equals("amy@example.com")) {
        caller.reject(Reason.DECLINE);
    }
    else if (callee.getName().equals("tom@example.com")) {
        IdentityInformation ii = ctx.getContextElement(IdentityInformation.class);
        if (isRoaming(ii)) {
          caller.reject(Reason.DECLINE);
        }
    }
  }
  
  boolean isRoaming(IdentityInformation pi) {
    boolean roaming = false;
    if (pi.getVisitedNetworkIdentity()!=null)
      roaming = true;
    return roaming;
  }
}

Communication Hold

Communication Hold (also referred to Call Hold) allows a user to suspend a communication session—the reception of media stream(s) from an established IP multimedia session—and resume the media stream(s) at a later time. Placing a Communication Hold on an ongoing session is achieved by sending a Session Description Protocol (SDP) offer where each of the communications (media streams) to be held are marked with the sendonly attribute if they were previously bidirectional media streams. To resume the session, a new SDP offer is issued in which each of the held media streams is marked with the default sendrecv attribute.

Communication Hold also allows an AS to play music or an announcement to the held party. This is achieved using an AS that acts as a third-party call controller (3PCC), and replaces the existing session of one of the users with a session originating from an application server that plays the announcement or music until the user's session is resumed. See the 3GPP TS 24.628 specification for more information on the playing of announcements during Communication Hold.

Setting the Communication Hold Bandwidth

The 3GPP TS 24.610 specification requires that the AS of the User Equipment (UE) invoking a media stream whose SDP session attribute is recvonly use a lower bandwidth. The SDP specifies a lower bandwidth by setting the bandwidth (the b= line in the SDP) to a lower value. The b= line contains two elements:

  • The bandwidth value expressed in kilo bits per second (kbps).

  • An alphanumeric modifier that indicates the communication session or media stream to which to apply the specified bandwidth value.

The modifiers whose bandwidth values are specified by SFT are:

  • AS—Application Specific Maximum, which specifies the total bandwidth for a single media stream from one source.

  • RS—RTCP bandwidth allocated to active data senders.

  • RR—RTCP bandwidth allocated to other participants (receivers) in the RTP session.

When the bandwidth setting is enabled, SFT sets the default value for the AS bandwidth to zero (b=AS:0). The b=RR: and b=RS: parameters are set to a value of 800 kbps, which is high enough to allow the continuation of the RTCP flow: b=RR:800 and b=RS:800

Example 21-7 Bandwidth Line in the Session Description Protocol

v=0
o=alice 2890844526 2890842807 IN IP4 126.16.64.4
s=SDP Seminar
c=IN IP4 224.2.17.12/127
t=2873397496 2873404696
m=audio 49170 RTP/AVP 0
b=AS:0
b=RR:800
b=RS:800

Note:

The 3GPP TS 24.610 specification recommends that the AS modify the bandwidth for media streams whose SDP session attribute is recvonly. Media streams whose SDP session attribute are inactive, sendonly, and sendrecv are not affected.

While the 3GPP TS 24.610 specification recommends these values to preserve network bandwidth when a communication is placed on hold, you may need to adjust the bandwidth to better suit the requirements of the Communication Hold application. You can specify a network bandwidth for use with Communication Hold using:

  • The enableChangeBandwidth and sdpBandwidthAttributes elements of the sft.xml deployment descriptor.

  • The @ServiceAttributes annotation.

  • The conversation.setBandWidth() method.

Example 21-8 specifies alternate bandwidth settings using the enableChangeBandwidth and sdpBandwidthAttributes elements of the sft.xml deployment descriptor.

Example 21-8 Specifying Bandwidth Using the SFT.XML Deployment Descriptor

<service-attributes>
    <enableChangeBandwidth>true</enableChangeBandwidth>
    <sdpBandwidthAttributes>
      <totalBandwidth>0</totalBandwidth>
      <bandwidthForActiveDataSenders>800</bandwidthForActiveDataSenders>
      <bandwidthForOtherParticipants>1600</bandwidthForOtherParticipants>
    </sdpBandwidthAttributes>
  </service-attributes>

The enableChangeBandwidth element is set to true, which enables the alternate bandwidth settings specified by the sdpBandwidthAttributes element. In this example the bandwidthForActiveDataSenders sub-element specifies a bandwidth of 800 for users sending active media streams, while the bandwidthForOtherParticipants sub-element specifies a bandwidth of 1600 for all other participants.

Example 21-9 specifies alternate bandwidth settings using the @ServiceAttributes annotation. Use this annotation in the CommunicationBean Java class you create to implement the Communication Hold application.

Example 21-9 Specifying Bandwidth Using the @ServiceAttributes Annotation

@ServiceAttributes(enableChangeBandwidth = true, sdpBandwidthAttributes = {0, 800, 1600})

Identity Presentation and Restriction

Converged Application Server supports the following Identity Presentation and Restriction services:

To support the Identity Presentation and Restriction services listed above:

Identity Presentation and Restriction Interfaces

Table 21-5 lists the interfaces used to implement the Identity Presentation and Restriction services.

Table 21-5 Identity and Presentation Interfaces

Interface Description

PrivacyPolicy

Provides information about the privacy policy, and whether it is enabled or disabled. The default value is disabled.

The following classes extend the PrivacyPolicy interface: PrivacyValuePolicy, AnonymizeFromHeaderPolicy, RemovePAIheaderPolicy, RemoveFromChangeTagPolicy and ObscureIdentificationPolicy.

PrivacyValuePolicy

Provides information about privacy values. Enabling this policy modifies or adds the PrivacyValues field to the message's Privacy Header.

  • If this policy is disabled, no change is made to the Privacy Header.

  • If the handling message includes a Privacy header, this policy is enabled by default.

  • If the handling message does not include a Privacy header, the PrivacyValues policy is disabled by default.

  • The policy to enable the Privacy header is not added to the message when ObscureIdentificationPolicy is enabled.

RFC 3323 states that, when a privacy service performs one of the functions corresponding to a privacy level listed in the Privacy header, it should remove the corresponding priv-value from the Privacy header.

AnonymizeFromHeaderPolicy

Represents a PrivacyPolicy that specifies whether or not to anonymize the From header prior to the message being sent. The default value is disabled.

RemovePAIheaderPolicy

Represents a PrivacyPolicy that specifies whether or not to remove the P-Asserted-Identity header prior to the message being sent. The default value is disabled.

RemoveFromChangeTagPolicy

Represents a PrivacyPolicy that specifies whether or not to remove the From-Change tag prior sending the message. The default value is disabled.

ObscureIdentificationPolicy

Represents a PrivacyPolicy that specifies whether or not to obscure identification according to the privacy header. Disabling this policy does not obscure identification contained in the Privacy header prior to the message being sent. The default value is disabled.

RFC 3323, RFC 3325, and RFC 4244 define specific behaviors for each privacy value. RFC 5379 provides guidelines that are specified in RFC 3323, and subsequently extended in RFC 3325 and RFC 4244.

PrivacyInformation

Encapsulates Privacy information. You can use PrivacyInformation methods to return information about the privacy policy.


Privacy Service Behavior

The privacy service role is instantiated by a network intermediary. Typically this function is performed by entities that act as SIP proxy servers. The privacy service is designed to provide privacy functions for SIP messages that cannot otherwise be provided by the UAs themselves. Table 21-6 lists the semantics of each priv-value, and the RFC that defines them.

Table 21-6 Types of Privacy Service Behaviors

Privacy Type Description

user

Request that privacy services provide a user-level privacy function.

See RFC 3323, “A Privacy Mechanism for the Session Initiation Protocol (SIP)” for more information.

header

Request that privacy services modify headers that cannot be set arbitrarily by the user. For example, the Contact and Via headers.

See RFC 3323, “A Privacy Mechanism for the Session Initiation Protocol (SIP)” for more information.

session

Request that privacy services provide privacy for session media.

See RFC 3323, “A Privacy Mechanism for the Session Initiation Protocol (SIP)” for more information.

none

Privacy services must not perform any privacy function.

See RFC 3323, “A Privacy Mechanism for the Session Initiation Protocol (SIP)” for more information.

critical

Privacy service must perform the specified services or fail the request.

See RFC 3323, “A Privacy Mechanism for the Session Initiation Protocol (SIP)” for more information.

id

Privacy requested for Third-Party Asserted Identity.

See RFC 3325, “Private Extensions to the Session Initiation Protocol (SIP) for Asserted Identity within Trusted Networks” for more information.

history

Privacy requested for History-Info headers.

See RFC 4244, “An Extension to the Session Initiation Protocol (SIP) for Request History Information” for more information.


RFC 5379 describes privacy considerations and the recommended treatment of each SIP header that may reveal user-privacy information. Section 5, “Recommended Treatment of User-Privacy-Sensitive Information” of RFC 5379 describes how each header affects privacy, the desired treatment of the value by the user agent and privacy service, and other details needed to ensure privacy.Table 21-7 lists the recommended treatment for each priv-value contained in the SIP header. See “Section 5" of RFC 5379 “Guidelines for Using the Privacy Mechanism for SIP” for more information.

Table 21-7 Treatment of User-Privacy Information in Target SIP Headers

Target SIP Headers Where User Header Session ID History

Call-ID

R

Anonymize

       

Call-Info

Rr

Delete

Not added

     

Contact

R

 

Anonymize

     

From

R

Anonymize

       

History-Info

Rr

 

Delete

Delete

 

Delete

In-Reply-To

R

Delete

       

Organization

Rr

Delete

Not added

     

P-Asserted-Identity

Rr

 

Delete

 

Delete

 

Record-Route

Rr

 

Anonymize

     

Referred-By

R

Anonymize

       

Reply-To

Rr

Delete

       

Server

r

Delete

Not added

     

Subject

R

Delete

       

User-Agent

R

Delete

       

Via

R

 

Anonymize

     

Warning

r

Anonymize

       

Enabling User-Level Privacy

Example 21-10 illustrates how to enable user-level privacy by anonymizing the From header, and removing the Call-Info, In-Reply-To, Organization. Reply-To, Subject, User-Agent, P-Asserted-Identity, and Privacy headers from the Session Initiation Protocol. In this example:

  1. PrivacyInformation—the Java class that encapsulates Privacy information—is stored in the pI variable.

  2. The PrivacyInformation.getPolicy(class) method retrieves the privacy policy information from the removePAIHeaderPolicy, anonymizeFromHeaderPolicy, obscuringIdentificationPolicy, and privacyValuePolicy objects.

  3. To enable user-level privacy, the removePAIHeaderPolicy, anonymizeFromHeaderPolicy, and obscuringIdentificationPolicy objects implement the enable() method inherited from com.oracle.sft.api.context.PrivacyPolicy.

Example 21-10 Providing User-Level Privacy

@CommunicationEvent(type = CommunicationEvent.Type.INITIALIZATION)
  public void handleInitEvent() {

    PrivacyInformation pI = ctx.getContextElement(PrivacyInformation.class);
    
    removePAIHeaderPolicy = pI.getPolicy(RemovePAIHeaderPolicy.class);
    anonymizeFromHeaderPolicy = pI.getPolicy(AnonymizeFromHeaderPolicy.class);
    obscuringIdentificationPolicy = pI.getPolicy(ObscureIdentificationPolicy.class);
    privacyValuePolicy = pI.getPolicy(PrivacyValuePolicy.class);
 
    removePAIHeaderPolicy.enable();
    anonymizeFromHeaderPolicy.enable();
    obscuringIdentificationPolicy.enable();
    
    printPrivacyInformation();
  }

Providing Privacy for the History-Info Header

The History-Info header (defined in RFC 4244) provides a way of capturing any redirection information that may have occurred during a particular call. Without the History-Info header the redirecting information would be lost. The History-Info header is generated when a SIP request is re-directed, and can appear in any SIP request not associated with a dialog. The History-Info header is generated by a User Agent or proxy and is passed from one entity to another through requests and responses.

Example 21-11 adds history to the privacy policy, and then modifies the contents of the PRIVACY header by calling the PrivacyValuePolicy.enable() method. The PrivacyValuePolicy.enable() method adds a Privacy header with PrivacyValues to the message.

Example 21-11 Removing the History-Info Header

privacyValuePolicy.addPrivacyValue(ValueType.HISTORY);
privacyValuePolicy.enable();

Communication Waiting

Communication Waiting (also referred to as Call Waiting) informs a user (or the user equipment) that limited resources are available for incoming calls. Typically this means that the callee is involved in a communication session with another caller, and is not able to answer the incoming call from the second caller. Communication Waiting provides a mechanism by which you can create an application to inform a user that there is a second incoming call. The user then has the choice of accepting, rejecting, or ignoring the waiting call. Converged Application Server supports the 3GPP TS 24.615 and the GSMA IR.92 specifications.

Supporting Network- and Terminal-based Communication Waiting

When using SFT to develop Communication Waiting services, Converged Application Server supports both network- and terminal-based Communication Waiting.

About Network-based Communication Waiting

Network-based Communication Waiting occurs when the AS determines that one of the following conditions has occurred:

  • The SIP INVITE request fulfills the Network Determined User Busy (NDUB) condition for the callee.

  • The caller receives a SIP message 486 Busy Here (indicating that the callee is busy) with a 370 Warning in the SIP header field indicating that there is insufficient bandwidth for the call to complete.

To support network-based Communication Waiting, the AS performs the following functions in response to receiving an appropriate Communication Waiting condition:

  1. Modifies the SIP INVITE request and forwards or re-sends it to User B.

  2. Provides an announcement to User C upon receipt of a 180 Ringing response from User B.

  3. Inserts an Alert-Info header field set to urn:alert:service:call-waiting in the 180 Ringing response and forwards it to User C

  4. Rejects the communication by sending a 486 Busy Here response to User C upon receipt of a 415 Unsupported Media Type response.

About Terminal-based Communication Waiting

Terminal-based Communication Waiting occurs when the AS receives the SIP message 180 Ringing with the Alert-Info header URN Indication Values set to urn:alert:service:call-waiting.

To support terminal-based Communication Waiting, the application server performs the following functions in response to receiving an appropriate Communication Waiting condition:

  1. Sends an announcement to the calling user (the caller).

  2. Sends a 180 Ringing response to the caller.

  3. Initiates the Telephony Application Server-Communication Waiting (TAS-CW) timer. This optional timer specifies the amount of time the network will wait for a response from User B, in response to the communication from User C. The value of the timer is between 0.5 and 2 minutes.

    If the TAS-CW timer expires, the AS sends a CANCEL request to User B with a Reason header field set to “SIP,” and the cause set to 408 Request Time-out, indicating that the user could not be found in the allotted time. A 480 Temporarily Unavailable response is sent to User C, including a Reason header field set to ISUP Cause Code 19, indicating that these was no answer from the callee.

About the Communication Waiting Interfaces

Communication Waiting makes use of the following classes in the com.oracle.sft.api package. For more information on these interfaces and their usage, refer to the Converged Application Server API Reference.

Creating a Communication Waiting Application

The following examples decompose the CallWaitingBean Java class. See "CallWaitingBean Example Code" to view the code in its entirety.

Example 21-12 creates CallWaitingBean, a Java class that responds to Communication Waiting events.

Example 21-12 Creating the CallWaitingBean Java Class

@CommunicationBean
public class CallWaitingBean {

@Context CommunicationContext<Conversation,UserParticipant> ctx;
...

The @CommunicationBean annotation creates CallWaitingBean. The @Context annotation injects an instance of the CommunicationContext object, which in turn calls instances of the Conversation and UserParticipant classes. These interfaces represent a two-party call and a participant within the communication, and are cast in the ctx instance variable.

Creating a Network-based Communication Waiting Event

Example 21-13 illustrates the handleInit() method, which listens for and responds to network-based communication waiting events.

Example 21-13 Network-based Communication Waiting

@CommunicationEvent(type = CommunicationEvent.Type.INITIALIZATION)
  void handleInit() {
    Conversation call = (Conversation) ctx.getCommunication();
    if (call.getCaller().getName().contains("user1")) {
      call.indicateCallWaiting();
    }
  }
...

The @CommunicationEvent annotation instantiates this method with the Type.INITIALIZATION constant. When an initialization event occurs, handleInit() uses the Conversation interface to retrieve the name of the communication.

The getCommunication() method returns the CommunicationContext object (via the ctx instance variable) to the Conversation interface via the call variable. The call variable—which now contains the state of the communication session— invoke the indicateCallWaiting() method which will forward the invite with call waiting indication. The Communication Waiting event will be triggered when a 180 response arrives.

Creating a Terminal-based Communication Waiting Event

Example 21-14 illustrates the use of the hanldeRejectEvent() method to reject events. This method uses the EventReason interface to retrieve the cause of the Communication Waiting reject event, and then triggers Terminal-based Communication Waiting.

Example 21-14 Terminal-based Communication Waiting

...
@ParticipantEvent(type = ParticipantEvent.Type.REJECTED)
  public void hanldeRejectEvent() {
    EventReason eventReason = ctx.getContextElement(EventReason.class);
    List<ReasonData> reasonData = eventReason.getReasonData();
    ReasonData reasonData = reasonData.get();
    Reason reason = reasonData.getReasonType();

    List<WarningData> warningData = eventReason.getWarningData();
    WarningData warningData = warningData.get();
    
    if (reasonData.getReasonCode() == 486 &&
        warningData.getWarningCode() == 370) {
      Conversation call = (Conversation) ctx.getCommunication();
      call.indicateCallWaiting();
    }
  }

The @ParticipantEvent annotation—which specifies events pertaining to a participant within a given communication session—listens for an event type of REJECTED, indicating that the participant has refused to join the communication. The hanldeRejectEvent()method calls the EventReason interface to get the cause of the REJECTED event. List<WarningData> returns the list of WarningData for the event, and the getWarningCode() method returns the warning codes.

If the status code of the response is 486 Busy Here and the warning code is 370 Insufficient Bandwidth, the getCommunication() method returns the CommunicationContext object (via the ctx instance variable) to the Conversation interface via the call variable. The call variable invoke the indicateCallWaiting() method, which re-sends the INVITE. The Communication Waiting event will be triggered when 180 response arrives.

Handling the Communication Waiting Event

Example 21-15 creates the method handleCallWaiting(), which uses the @CommunicationEvent annotation's type.WAITING constant to indicate that a call is waiting. The application receives a notification that an incoming call is waiting, and can play announcements to the caller.

Example 21-15 Handling the Call Waiting Event

@CommunicationEvent(type = CommunicationEvent.Type.WAITING)
  void handleCallWaiting() {

CallWaitingBean Example Code

Example 21-16 shows the code for the previously described CallWaitingBean in its entirety.

Example 21-16 CallWaitingBean Example Java Code

package com.oracle.sft.demo;
import java.util.List;
 
import com.oracle.sft.api.CommunicationContext;
import com.oracle.sft.api.Context;
import com.oracle.sft.api.Conversation;
import com.oracle.sft.api.Reason;
import com.oracle.sft.api.UserParticipant;
import com.oracle.sft.api.bean.CommunicationBean;
import com.oracle.sft.api.bean.CommunicationEvent;
import com.oracle.sft.api.bean.ParticipantEvent;
import com.oracle.sft.api.context.EventReason;
import com.oracle.sft.api.context.ReasonData;
import com.oracle.sft.api.context.WarningData;
 
//Create the CallWaitingBean Java class using the @CommunicationBean annotation.
@CommunicationBean
public class CallWaitingBean {
  // @Context injects an instance of CommunicationContext, 
  // which retrives instances of Conversation and UserParticipant.
  @Context CommunicationContext<Conversation,UserParticipant> ctx;
  
//Annotate handleInit() with @CommunicationEvent. 
  @CommunicationEvent(type = CommunicationEvent.Type.INITIALIZATION)
  void handleInit() {
    Conversation call = (Conversation) ctx.getCommunication();
    if (call.getCaller().getName().contains("user1")) {
      call.indicateCallWaiting();
    }
  }
  
//Annotate hanldeRejectEvent() with @ParticipantEvent.
  @ParticipantEvent(type = ParticipantEvent.Type.REJECTED)
  public void hanldeRejectEvent() {
    EventReason eventReason = ctx.getContextElement(EventReason.class);
    List<ReasonData> reasonDatas = eventReason.getReasonData();
    ReasonData reasonData = reasonDatas.get(0);
    Reason reason = reasonData.getReasonType();
 
//Get the warning data and codes from the EventReason interface.
    List<WarningData> warningDatas = eventReason.getWarningData();
    WarningData warningData = warningDatas.get(0);
    // If the warning code is 486 Busy Here and 370 Insufficient Bandwidth, 
    // resend the INVITE using the indicateCallWaiting() method.
    if (reasonData.getReasonCode() == 486 &&
        warningData.getWarningCode() == 370) {
      Conversation call = (Conversation) ctx.getCommunication();
      call.indicateCallWaiting();
    }
  }
  //
  @CommunicationEvent(type = CommunicationEvent.Type.WAITING)
  void handleCallWaiting() {
  }
  

Message Waiting Indication

Message Waiting Indication (MWI) is a service that informs a user about the status of recorded messages. To use the notification feature, the user must subscribe to a notification service that makes use of the Message Waiting Indication status messages. With the Message Waiting Indication feature you can:

Message Waiting Indication lets the AS notify a subscriber that there is a message waiting for them. The indication is delivered to the subscriber's UE after they have successfully subscribed to the Message Waiting Indication service as defined in the 3GPP TS 24.606 specification.

When Converged Application Server receives a SUBSCRIBE message, SFT notifies the MWI application via a SUBSCRIPTION event. Once notification is received the MWI application calls instances of the MessageObservation and ActivityParticipant interfaces—which identify the current subscriber—using the CommunicationContext interface.

RFC 3842 specifies that a NOTIFY message must be sent when accepting new subscriptions, the subscription has expired, or an unsubscribe event occurs. Converged Application Server's Event Notification Service sends these NOTIFY messages automatically, and application updates the MessageSummary object during the initial SUBSCRIPTION event to ensure that the correct NOTIFY message is sent.

Configuring Message Waiting Indication

To configure Message Waiting Indication, use either the sft.xml deployment descriptor, or the CommunicationBean's @ServiceAttributes annotation. The parameters you specify corollate to the incoming SUBSCRIBE request's Subscription-State Expires header parameter, which contains an expiration time.

  • If the expiration time is less than zero (> 0) but greater than the minimum expiration time, Converged Application Server rejects the SUBSCRIBE request with the 423 Interval Too Brief response code.

  • If the expiration time is greater than zero (< 0), Converged Application Server uses the default expiration time.

  • If the specified expiration time in greater (>) than the maximum expiration time, Converged Application Server uses the maximum expiration time.

  • If the maximum number of subscriptions is reached, the next SUBSCIRBE request is rejected with the 503 Service Unavailable response code.

Example 21-17 illustrates the use of the @ServiceAttributes annotation to configure Message Waiting Indication events. The configuration parameters are listed in the order in which you specify them:

  • Minimum expiration time allowed for subscriptions. The default value is 100.

  • Default expiration time for subscriptions. The default value is 1800.

  • Maximum expiration time allowed for subscriptions. The default value is 3600.

  • Maximum number of subscriptions per resource allowed for the service. The default value is 100.

Example 21-17 Using @ServiceAttribute to Configure MWI Events

...
@ServiceAttributes(messageObservationEventConfig = {100, 1800, 3600, 100})
@CommunicationBean
public class MWIBean
...

About the Message Waiting Indication Interfaces

Message Waiting Indication makes use of the following classes in the com.oracle.sft.api package. For more information on these interfaces and their usage, refer to the Converged Application Server API Reference.

Table 21-8 Message Waiting Indication classes

Class Description

UserActivity

UserActivity extends the Communication class, and encapsulates the participant's activity. UserActivity provides several ActivityParticipant() methods.

MessageObservation

MessageObservation extends the UserActivity class, and encapsulates the resource and subscribers. Using MessageObservation applications can get and notify subscribers (ActivityParticipants), and terminate subscriptions. The getMessageSummary() method returns a MessageSummary, which contains message information that is sent in NOTIFY messages. The application must update MessageSummary before sending a NOTIFY message to subscribers.

MessageSummary

Before sending a notification, the MWI application must update the MessageSummary object, which encapsulates the NOTIFY body according to RFC 3842. MessageSummary is obtained via the MessageObservation class.

MessageSummaryLine

MessageSummaryLine encapsulates the message summary line as defined in RFC 3842. MessageSummaryLine is obtained via MessageSummary.

MessageSummaryExtensionHeader

MessageSummaryExtensionHeader encapsulates a message summary extension header as defined by RFC 3842. MessageSummaryExtensionHeader is obtained via MessageSummary.

ActivityParticipant

ActivityParticipant extends Participant. In the context of the MessageObservation class, ActivityParticipant represents a subscriber. Applications can obtain an instance of ActivityParticipant via MessageObservation.


Creating a Message Waiting Indication Application

The following examples decompose the MWIBean Java class, which responds to Message Waiting Indication events. See "Message Waiting Indication Example" to view the application's Java code in its entirety.

Example 21-19 creates MWIBean, a Java class whose methods handle and respond to Message Waiting Indication events.

Example 21-18 Creating the MWIBean Java Class

@CommunicationBean
@ServiceAttributes(messageObservationEventConfig = {100, 1800, 3600, 100})
public class MWIBean {

  @Context CommunicationContext<MessageObservation,ActivityParticipant> ctx;
  @Context CommunicationService service;
  private String subscriberName;
  private String resourceId;

...

MWIBean uses the @SerivceAttributes annotation to configure the Message Waiting Indication event expiration times. This example configures the service using the default values. The @Context annotation injects an instance of the CommunicationContext, which in turn calls instances of MessageObservation and ActivityParticipant. These interfaces identify the user initiating the subscription request, and are stored in the ctx instance variable.

The @Context annotation injects an instance of CommunicationService, which is referred to with the service instance variable. CommunicationService lets you create objects that are not related to a CommunicationSession, such as groups.

Finally, two private instance variables are created: subscriberName and resourceId.

Updating MessageSummary Before Sending Notifications

Example 21-19 illustrates the hanldeSubscriptionEvent() method. This method is annotated with the @CommunicationEvent annotation's Type.SUBSCRIPTION constant. When Converged Application Server receives a SIP SUBSCRIBE message, the MWI application receives notification via the SUBSCRIPTION event.

A NOTIFY message is sent when accepting new subscriptions. Converged Application Server's Event Notification Service sends these NOTIFY messages automatically, and the MWI application updates the MessageSummary object during the initial SUBSCRIPTION event to ensure that the correct NOTIFY message is sent. Before sending a notification, the MWI application updates the MessageSummary object. MessageSummary is obtained via the MessageObservation interface.

Example 21-19 Updating the MessageSummary Class

...
@CommunicationEvent(type = CommunicationEvent.Type.SUBSCRIPTION)
public void hanldeSubscriptionEvent() {

    MessageObservation messageObservation = (MessageObservation)ctx.getCommunication();
    ActivityParticipant currentSubscriber = (ActivityParticipant)ctx.getParticipant();
 
    MessageSummary messageSummary = messageObservation.getMessageSummary();
    messageSummary.setMessageAccount(URI.create("sip:alice@example.com"));
    messageSummary.setStatusLine(true);
    messageSummary.setMessageSummaryLine(MessageContextClass.VOICEMESSAGE, 1, 2, 3, 4);
 
  }
...

The hanldeSubscriptionEvent() method declares and initializes the following object reference variables:

    • The getCommunication() method returns the CommunicationContext object (via the ctx instance variable) to the MessageObservation interface using the messageObservation variable.

    • The getParticiapnt() method returns subscriber information from the CommunicationContext object (via the messageObservation instance variable). MessageSummary is to the ActivityParticipant interface using the currentSubscriber variable.

    • The getMessageSummary() method returns message summary information from MessageObservation (via the messageObservation object reference variable).

      MessageObservation acts as a resource which receives notifications from the server managing the message account (for example, a voice-mail server). Users (ActivityParticipants) subscribe to the message account. When new messages arrive the state of the MessageSummary object is updated, and notifications are sent to all ActivityParticipants subscribed to the account, alerting them that new messages are waiting.

    The messageSummary object variable, which serves as a reference to the MessageSummary object, makes a series of method calls which updates the NOTIFY body with the following information:

    • The account to which the message-summary body corresponds. This is specified by the Message Waiting Indication application to indicate the resource. For example, the user's voice-mail account: alice@example.comThe setStatusLine(true) method specifies that messages are waiting.

    • The setMessageSummaryLine() method indicates that the waiting message types are voice messages.

      As defined in RFC 3458 and RFC 3938, the following message types can provide notifications: voice-message, video-message, fax-message, pager-message, multimedia-message, text-message, or none (no message).

Sending MWI Notifications to Subscribers

To send MWI notifications to subscribers, first locate the resource (for example, the voice-mail account), and then update MessageSummary with the status of the message account. To do this you use the MessageObservation interface to look up accounts, and the MessageSummary class to send the notifications. Once MessageSummary references the status of the message account, invoke resource.process() to send NOTIFY messages to the subscribers.

Example 21-20 creates the updateResourceInfo(String resourceId) method. This method is invoked when the status of a mail account changes. (How to get this change is beyond the scope of the SFT Message Waiting Indication APIs). This customer defined method updates messageSummary, and sends a NOFITY message to all subscribers.

Note:

This method may be a callback method to the server managing the message account, or another application's APIs that notify MWI application that a message has arrived and is waiting. These parameters are defined by your message server's APIs.

Example 21-20 Sending Notification to all Subscribers

...
void updateResourceInfo(String resourceId) {
    MessageObservation resource = service.findByName(MessageObservation.class, resourceId);
 
    MessageSummary messageSummary = resource.getMessageSummary();
    MessageSummaryLine messageSummaryLine =      
    messageSummary.getMessageSummaryLine(MessageContextClass.VOICEMESSAGE);
    messageSummaryLine.setNewMessageCount(1);
    messageSummaryLine.setOldMessageCount(2);
    resource.process();
  }
...

To begin the method declares and initializes the following object reference variables:

  • The resource variable serves as a container for the list of subscribers (or resources) obtained via the MessageObservation.class.

    MessageObservation resource =
    service.findByName(MessageObservation.class, resourceId);
    
  • The getMessagesummary() method returns the message summary information to the messsageSummary variable via a method call to the MessageObservation class.

    MessageSummary messageSummary = resource.getMessageSummary();
    
  • The getMessageSummaryLine() method returns a reference to a MessageSummaryLine which ContextClass is specified. In this example, the type is VOICEMESSAGE

    MessageSummaryLine messageSummaryLine =
    messageSummary.getMessageSummaryLine(MessageContextClass.VOICEMESSAGE);
    
  • The messageSummaryLine variable, which stores message summary line information from the MessageSummaryLine object, makes method calls to set the old and new message counts using the setNewMessageCount(int count) and setOldMessageCount(int count) methods.

    Finally, the resource variable storing the subscriber list calls MessageObservation.process(), which sends NOTIFY messages containing the updated message summary information to all of the subscribers.

    messageSummaryLine.setNewMessageCount(1);
    messageSummaryLine.setOldMessageCount(2);
    resource.process();
    

Removing a Subscription

Example 21-21 illustrates the use of the UserActivity interface in removing a subscription (which is a type of UserActivity). UserActivity extends the Communication, and provides the removeActivityParticipant() method, which lets you remove the ActivityParticipant from the UserActivity.

This example declares and initializes the variable resource, which serves as a source for the list of subscribers (or resources) obtained via the MessageObservation class. The service session attribute—which was declared earlier in the program—lets you call an instance of CommunicationSession. When responding to an event, the service session attribute injects an instance of CommunicationSession into the CommunicationBean.

Example 21-21 Removing a Subscriber

...
void removeSubscriber(String resourceId, String subscriberName) {
    MessageObservation resource = service.findByName(MessageObservation.class, resourceId);
    resource.removeActivityParticipant(subscriberName);
  }
}

Message Waiting Indication Example

Example 21-22 shows the code for the previously described MWIBean in its entirety.

Example 21-22 Message Waiting Indication Scenarios

package com.oracle.sft.MWIBean;
 
import com.oracle.sft.api.ActivityParticipant;
import com.oracle.sft.api.CommunicationContext;
import com.oracle.sft.api.CommunicationService;
import com.oracle.sft.api.Context;
import com.oracle.sft.api.MessageObservation;
import com.oracle.sft.api.ProtocolMessage;
import com.oracle.sft.api.MessageContextClass;
import com.oracle.sft.api.bean.CommunicationBean;
import com.oracle.sft.api.bean.CommunicationEvent;
import com.oracle.sft.api.bean.ProtocolEvent;
import com.oracle.sft.api.bean.ServiceAttributes;
import com.oracle.sft.api.context.MessageObservationResource;
import com.oracle.sft.api.MessageSummary;
import com.oracle.sft.api.MessageSummaryLine;
 
@CommunicationBean
@ServiceAttributes(messageObservationEventConfig = {31, 61, 91, 2})
public class MWIBean {

  @Context CommunicationContext<MessageObservation,ActivityParticipant,?> ctx;
  @Context CommunicationService service;
  private String subscriberName;
  private String resourceId;
 
//Receiving the SUBSCRIPTION Event
  @CommunicationEvent(type = CommunicationEvent.Type.SUBSCRIPTION)
  
  public void hanldeSubscriptionEvent() {
    MessageObservation messageObservation = (MessageObservation)ctx.getCommunication();
    ActivityParticipant currentSubscriber = (ActivityParticipant)ctx.getParticipant();
    subscriberName = currentSubscriber.getName();
    resourceId = messageObservation.getName();
 
    if (currentSubscriber.getName().contains("subscriberA")) {
      MessageSummary messageSummary = messageObservation.getMessageSummary();
      messageSummary.setMessageAccount(URI.create("sip:alice@example.com"));
      messageSummary.setStatusLine(true);
      messageSummary.setMessageSummaryLine(MessageContextClass.VOICEMESSAGE, 1, 2, 3, 4);
      messageSummary.setMessageSummaryLine(MessageContextClass.FAX, 10, 20, 30, 40);
      messageSummary.setExtensionHeader("messageID", "to", "from", 
                                        "subject", "date", "priority");
    } else {
      //System.out.println("###### Reject the subscription. ######");
      currentSubscriber.reject();
    }
  }

  @CommunicationEvent(type = CommunicationEvent.Type.NOTIFICATION)
    public void hanldeNotificationEvent() {
      System.out.println("###### Receive NOTIFICATION Event ######");
      System.out.println("");
    }
  
  /**
   * Update resource info and send NOTIFY message to all subscribers.
   */

    void updateResourceInfo(String resourceId) {
      MessageObservation resource = service.findByName
                                            (MessageObservation.class, resourceId);
      MessageSummary messageSummary = resource.getMessageSummary();
      messageSummary.setStatusLine(false);
      MessageSummaryLine messageSummaryLine =
      messageSummary.getMessageSummaryLine(MessageContextClass.VOICEMESSAGE);
      if (messageSummaryLine != null) {
        messageSummaryLine.setNewMessageCount(messageSummaryLine.getNewMessageCount() + 1);
        messageSummaryLine.setOldMessageCount(messageSummaryLine.getOldMessageCount() + 1);
      }
      resource.process();
    }

  /**
   * Send NOTIFY message to a specified subscriber to end the Subscription.
   */
  void removeSubscriber(String resourceId, String subscriberName) {
    MessageObservation resource = service.findByName(MessageObservation.class, resourceId);
    System.out.println("###### remove Subscriber " + subscriberName);
    resource.removeActivityParticipant(subscriberName);
  } 
}