private void ProcessNotificationLinkConfirmation(
            string objectUrl,
            string linkID,
            NotificationLinkConfirmation notificationLinkConfirmation)
        {
            if (2 != Interlocked.Increment(ref notificationLinkConfirmation.NumItemsRecieved))
                // Not ready for processing
                return;

            // Remove old NotificationLinkConfirmation from RAM
            ThreadPool.QueueUserWorkItem(delegate(object state)
            {
                try
                {
                    NotificationLinkConfirmationLock.EnterUpgradeableReadLock();

                    try
                    {
                        Dictionary<string, NotificationLinkConfirmation> objectUrlDictionary;
                        if (NotificationLinkConfirmationsByObjectUrlByLinkID.TryGetValue(objectUrl, out objectUrlDictionary))
                            lock (objectUrlDictionary)
                            {
                                objectUrlDictionary.Remove(linkID);

                                if (0 == objectUrlDictionary.Count)
                                {
                                    NotificationLinkConfirmationLock.EnterWriteLock();

                                    try
                                    {
                                        NotificationLinkConfirmationsByObjectUrlByLinkID.Remove(objectUrl);
                                    }
                                    finally
                                    {
                                        NotificationLinkConfirmationLock.ExitWriteLock();
                                    }
                                }
                            }
                    }
                    finally
                    {
                        NotificationLinkConfirmationLock.ExitUpgradeableReadLock();
                    }
                }
                catch (Exception e)
                {
                    log.Error("Exception cleaning " + objectUrl + " from the cached link notifications", e);
                }
            });

            if (!notificationLinkConfirmation.NotificationLinkInfo.Equals(
                notificationLinkConfirmation.ConfirmationLinkInfo))
            {
                log.Warn("Notification confirmation mismatch on a link: " + JsonWriter.Serialize(notificationLinkConfirmation));
                return;
            }

            HashSet<string> recipients = new HashSet<string>(notificationLinkConfirmation.NotificationLinkInfo.RecipientIdentities);
            recipients.IntersectWith(notificationLinkConfirmation.ConfirmationLinkInfo.RecipientIdentities);

            // Start handling the result for each recipient
            foreach (string recipient in recipients)
                ThreadPool.QueueUserWorkItem(delegate(object recipientObj)
                {
                    try
                    {
                        ReceiveNotification(
                            notificationLinkConfirmation.SenderIdentity,
                            recipientObj.ToString(),
                            objectUrl,
                            notificationLinkConfirmation.SummaryView,
                            notificationLinkConfirmation.DocumentType,
                            "link",
                            notificationLinkConfirmation.ChangeData,
                            notificationLinkConfirmation.ConfirmationLinkInfo.OwnerIdentity);
                    }
                    catch (Exception e)
                    {
                        log.Warn("Exception processing a confirmed link notification", e);
                    }
                }, recipient);
        }
        private NotificationLinkConfirmation GetNotificationLinkConfirmation(string objectUrl, string linkID)
        {
            // Make sure the timer is created
            if (null == NotificationLinkConfirmationTimer)
            {
                Timer timer = new Timer(NotificationLinkConfirmationCleanup, null, 600000, 600000);
                if (null != Interlocked.CompareExchange<Timer>(ref NotificationLinkConfirmationTimer, timer, null))
                    timer.Dispose();
            }

            // Get the dictionary for the the object url
            Dictionary<string, NotificationLinkConfirmation> forObjectUrl;

            NotificationLinkConfirmationLock.EnterUpgradeableReadLock();

            try
            {
                // If there's no dictionary, aquire a write lock, re-check, and then create it
                if (!NotificationLinkConfirmationsByObjectUrlByLinkID.TryGetValue(objectUrl, out forObjectUrl))
                {
                    NotificationLinkConfirmationLock.EnterWriteLock();

                    try
                    {
                        if (!NotificationLinkConfirmationsByObjectUrlByLinkID.TryGetValue(objectUrl, out forObjectUrl))
                        {
                            forObjectUrl = new Dictionary<string, NotificationLinkConfirmation>();
                            NotificationLinkConfirmationsByObjectUrlByLinkID[objectUrl] = forObjectUrl;
                        }
                    }
                    finally
                    {
                        NotificationLinkConfirmationLock.ExitWriteLock();
                    }
                }

                // Manipulation of the dictionary needs to happen within the read lock because NotificationLinkConfirmationsByObjectUrlByLinkID
                // is also cleaned within a lock
                lock (forObjectUrl)
                {
                    NotificationLinkConfirmation toReturn;

                    if (!forObjectUrl.TryGetValue(linkID, out toReturn))
                    {
                        toReturn = new NotificationLinkConfirmation();
                        forObjectUrl[linkID] = toReturn;
                    }

                    return toReturn;
                }
            }
            finally
            {
                NotificationLinkConfirmationLock.ExitUpgradeableReadLock();
            }
        }