Posted by: rcosic | 29/03/2012

Designing UCMA application endpoint classes (Part 2)

We are continuing our discussion about creating UCMA wrapper classes for usage on server-side appliacation development.

As term call is used in presenting different kinds of Lync modality in UCMA applications (instead of AVModality, IM modality, and so on), we are today extending UCMA call class for usage for instant messaging.

This is primarily of great advantage if we want to support a certain modality (in this case, an IM modality), and on the same time, extend its behavior and functionality. For instance, I wanted to be able to include wait handles (ManualResetEvent objects) to be able to track call behavior in synchronous way. Also, I’ll include a logging capability on call level, and influence on the underlying IM call flow. The call flow is actually the underlying stream that starts open when two or more parties are communicating. If you track down the flow events, you can see what’s going on ‘on the wire’. In case of IM, when message is being composed or sent.

So, for start, we’ll declare object underneith, such as ApplicationEndpoint and InstantMessagingCall and other variables necessary. I’ll expose also recipient’s SIP uri and call state for consumers of the class:

// underlying UCMA objects used
private ApplicationEndpoint m_appEndpoint;
private InstantMessagingCall m_imCall;

private string m_strDestinationSipUri;

private ILogger m_logger;

// a wait handles for startup and shutdown
private ManualResetEvent m_startupWaitHandle = new ManualResetEvent(false);
private ManualResetEvent m_shutdownWaitHandle = new ManualResetEvent(false);

/// <summary>
/// Gets the SIP address of call destination (another participant).
/// </summary>
internal string DestinationSipUri
{
get
{
return m_strDestinationSipUri;
}
}

/// <summary>
/// Gets the current state of IM call.
/// </summary>
internal CallState CallState
{
get
{
return m_imCall.State;
}
}

Next, I will use methods to establish a new call (i.e. start communicating), stop existing call (conversation) and send IM message to recipient. I will also provide blocking methods for start and shutdown.

Note that in the first method (for establishing the call), it will be all reveiled to you how it is done. ConversationSettings object can be used to create a Conversation object, which will also be initialized with application endpoint reference. Next, new InstantMessagingCall instance is initialized with the created conversation object, and then, all events subscription are created. Conversation (chat dialog) will begin with a ToastMessage (I’m initializing it with a corresponding text), and then establishing of the call is started with all gathered data. In case any errors occur, a RealTimeException is thrown, so you should catch it in all cases of UCMA method calls:

/// <summary>
/// Establishes a new call by initializing a new conversation and instant messaging call.
/// </summary>
/// <param name=”strToastMessage”>toast message</param>

internal void EstablishCall(string strToastMessage)
{
// create a new conversation

ConversationSettings conversationSettings = new ConversationSettings();
conversationSettings.Subject = “Ratko Ćosić”;

Conversation conversation = new Conversation(m_appEndpoint);

//conversation.Impersonate(“sip:ghost@ebtest.local”, null, “Michael Green”);

m_imCall = new InstantMessagingCall(conversation);

// subscribe to IM call events
m_imCall.AutoAcceptNeeded += new EventHandler(_imCall_AutoAcceptNeeded);
m_imCall.ConversationChanged += new EventHandler(_imCall_ConversationChanged);
m_imCall.InstantMessagingFlowConfigurationRequested += new EventHandler(_imCall_InstantMessagingFlowConfigurationRequested);
m_imCall.ProvisionalResponseReceived += new EventHandler(_imCall_ProvisionalResponseReceived);
m_imCall.RemoteParticipantChanged += new EventHandler(_imCall_RemoteParticipantChanged);
m_imCall.RouteSetStatusChanged += new EventHandler(_imCall_RouteSetStatusChanged);
m_imCall.StateChanged += new EventHandler(_imCall_StateChanged);

try
{
ToastMessage toast = new ToastMessage(strToastMessage);

// establish the IM call
m_imCall.BeginEstablish(m_strDestinationSipUri, toast,
new CallEstablishOptions(), result =>
{
try
{
m_imCall.EndEstablish(result);
}
catch (RealTimeException ex)
{
m_logger.Log(“Failed establishing IM call”, ex);
}
},
null);
}
catch (ArgumentException ae)
{
throw new CommunicationException(m_logger, “Failed establishing IM call”, ae);
}
catch (InvalidOperationException ioe)
{
throw new CommunicationException(m_logger, “Failed establishing IM call”, ioe);
}
}

For stop/shutdown, I’m using two overloads: one to stop the current call, and one to stop the call provided in parameter. Blocking methods are also included:

/// <summary>
/// Stops IM conversation with provided call.
/// </summary>
/// <param name=”call”></param>
/// <param name=”logger”></param>
internal static void Stop(InstantMessagingCall call, ILogger logger)
{
if (call.State != CallState.Terminating && call.State != CallState.Terminated && call.State != CallState.Idle)
{
try
{
call.BeginTerminate(result =>
{
try
{
call.EndTerminate(result);
}
catch (RealTimeException ex)
{
logger.Log(“Failed terminating IM call”, ex);
}
},
null);
}
catch (InvalidOperationException ex)
{
logger.Log(“Failed terminating IM call”, ex);
}
}
}

/// <summary>
/// Stops IM conversation with this call.
/// </summary>
internal void Stop()
{
if (m_imCall.State != CallState.Terminating && m_imCall.State != CallState.Terminated && m_imCall.State != CallState.Idle)
{
try
{
m_imCall.BeginTerminate(result =>
{
try
{
m_imCall.EndTerminate(result);

m_shutdownWaitHandle.Set();
}
catch (RealTimeException ex)
{
m_logger.Log(“Failed terminating IM call”, ex);
}
},
null);
}
catch (InvalidOperationException ex)
{
m_logger.Log(“Failed terminating IM call”, ex);
}
}
}

/// <summary>
/// Waits for startup to finish.
/// </summary>
internal void WaitForStartup()
{
m_startupWaitHandle.WaitOne();
}

/// <summary>
/// Waits for shutdown to finish.
/// </summary>
internal void WaitForShutdown()
{
m_shutdownWaitHandle.WaitOne();
}

For sending messages, I’m also using two overloads: one for sending message to the current call, and one to the provided call. Sending is performed by using Flow object inside the call, by calling appropriate SendInstantMessage method. It is quite easy:

/// <summary>
/// Sends IM message to a provided call.
/// </summary>
/// <param name=”call”></param>
/// <param name=”message”></param>
/// <param name=”logger”></param>
internal static void SendMessage(InstantMessagingCall call, string message, ILogger logger)
{
try
{
if (call.Flow == null)
{
logger.Log(“IM call flow is null.”);
return;
}

call.Flow.BeginSendInstantMessage(message,
result =>
{
try
{
call.Flow.EndSendInstantMessage(result);
}
catch (RealTimeException ex)
{
logger.Log(“Failed sending IM message.”, ex);
}
}, null);
}
catch (InvalidOperationException ex)
{
logger.Log(“Failed sending IM message.”, ex);
}
}

/// <summary>
/// Sends IM message to this call.
/// </summary>
/// <param name=”message”></param>
internal void SendMessage(string message)
{
SendMessage(m_imCall, message, m_logger);
}

Now, from various events that I’ve subscribed before, two of them are of the most importance: InstantMessagingFlowConfigurationRequested and StateChanged. On first event, you should subscribe to the event of the underlying call flow – not before that! Because, the flow will not be available until you receive this event changed. On second event, you can monitor and notify about call state further. Both event handlers are presented in the following lines:

/// <summary>
/// Occurs when IM flow is created.
/// This is also the place to register for flow related event handlers and set flow configuration.
/// </summary>         /// <param name=”sender”></param>
/// <param name=”e”></param>
private void _imCall_InstantMessagingFlowConfigurationRequested(object sender, InstantMessagingFlowConfigurationRequestedEventArgs e)
{
m_logger.Log(“IM CALL: requests its flow configuration.”);

m_imCall.Flow.DeliveryNotificationReceived += new EventHandler<DeliveryNotificationReceivedEventArgs>(Flow_DeliveryNotificationReceived);
m_imCall.Flow.MessageReceived += new EventHandler<InstantMessageReceivedEventArgs>(Flow_MessageReceived);
m_imCall.Flow.PropertiesChanged += new EventHandler<InstantMessagingFlowPropertiesChangedEventArgs>(Flow_PropertiesChanged);
m_imCall.Flow.RemoteComposingStateChanged += new EventHandler<ComposingStateChangedEventArgs>(Flow_RemoteComposingStateChanged);
m_imCall.Flow.StateChanged += new EventHandler<MediaFlowStateChangedEventArgs>(Flow_StateChanged);
}

/// <summary>
/// Occurs when the state of the call changes.
/// </summary>
/// <param name=”sender”></param>
/// <param name=”e”></param>
private void _imCall_StateChanged(object sender, CallStateChangedEventArgs e)
{
m_logger.Log(string.Format(“IM CALL: has changed state from {0} to {1} (reason: {2}).”, e.PreviousState, e.State, e.TransitionReason));

// wait for IM call to be established before sending messages
if (e.State == CallState.Established)
{
if (OnCallEstablished != null)
{
OnCallEstablished(this);
}

m_startupWaitHandle.Set();
}
else if (e.State == CallState.Terminated)
{
m_startupWaitHandle.Set();

if (OnCallTerminated != null)
{
OnCallTerminated(this);
}
}
}

Events and corresponding delegates for call state changes are the following:

internal delegate void CallEstablished(OutgoingInstantMessagingCall call);
internal delegate void CallTerminated(OutgoingInstantMessagingCall call);

/// <summary>
/// Occurs when call gets established.
/// </summary>
internal event CallTerminated OnCallEstablished;

/// <summary>
/// Occurs when call gets terminated.
/// </summary>
internal event CallTerminated OnCallTerminated;

And now, the rest is to write some event handlers for IM flow, which I think is quite straightforward, if you follow the delegate signature. Interesting ones are RemoteComposingStateChanged and StateChanged, which correspond to ‘user is typing’ and ‘user sent message’ events on the communicator.

I hope it was interesting, and see you next time on wrapping up the UCMA app endpoint project!

Kind regards,
Ratko.

Advertisements

Responses

  1. Hi Ratko, great to see another Lync development blog! I blog at CodeLync.com, and I’m trying to put together a list of all active Lync development blogs – can I include yours? and would you be able to contribute a mini-bio (just a couple of sentences) on what your blog offers the development community? You can get me at paul at codelync.com. Thanks!


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Categories

%d bloggers like this: