File: System\Data\Services\UpdateTracker.cs
Project: ndp\fx\src\DataWeb\Server\System.Data.Services.csproj (System.Data.Services)
//---------------------------------------------------------------------
// <copyright file="UpdateTracker.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <summary>
//      Provides a class used to track updates for callbacks.
// </summary>
//
// @owner  Microsoft
//---------------------------------------------------------------------
 
namespace System.Data.Services
{
    using System.Collections.Generic;
    using System.Data.Services.Providers;
    using System.Diagnostics;
    using System.Reflection;
 
    /// <summary>Provides a class used to track updates for callbacks.</summary>
    internal class UpdateTracker
    {
        #region Private fields.
 
        /// <summary>
        /// A dictionary of containers mapping to the changes on those 
        /// containers, each of which consists of an element and the
        /// action taken on it.
        /// </summary>
        private Dictionary<ResourceSetWrapper, Dictionary<object, UpdateOperations>> items;
 
        /// <summary>Underlying data service instance.</summary>
        private IDataService service;
 
        #endregion Private fields.
 
        /// <summary>Initializes a new <see cref="UpdateTracker"/> instance.</summary>
        /// <param name="service">underlying data source instance.</param>
        private UpdateTracker(IDataService service)
        {
            this.service = service;
            this.items = new Dictionary<ResourceSetWrapper, Dictionary<object, UpdateOperations>>(ReferenceEqualityComparer<ResourceSetWrapper>.Instance);
        }
 
        /// <summary>Fires the notification for a single action.</summary>
        /// <param name="service">Service on which methods should be invoked.</param>
        /// <param name="target">Object to be tracked.</param>
        /// <param name="container">Container in which object is changed.</param>
        /// <param name="action">Action affecting target.</param>
        internal static void FireNotification(IDataService service, object target, ResourceSetWrapper container, UpdateOperations action)
        {
            Debug.Assert(service != null, "service != null");
            AssertActionValues(target, container);
 
            MethodInfo[] methods = container.ChangeInterceptors;
            if (methods != null)
            {
                object[] parameters = new object[2];
                parameters[0] = target;
                parameters[1] = action;
                for (int i = 0; i < methods.Length; i++)
                {
                    try
                    {
                        methods[i].Invoke(service.Instance, parameters);
                    }
                    catch (TargetInvocationException exception)
                    {
                        ErrorHandler.HandleTargetInvocationException(exception);
                        throw;
                    }
                }
            }
        }
 
        /// <summary>
        /// Create a new instance of update tracker
        /// </summary>
        /// <param name="service">underlying data service.</param>
        /// <returns>
        /// Returns a new instance of UpdateTracker.
        /// </returns>
        internal static UpdateTracker CreateUpdateTracker(IDataService service)
        {
            return new UpdateTracker(service);
        }
 
        /// <summary>Fires all notifications</summary>
        internal void FireNotifications()
        {
            object[] parameters = new object[2];
            foreach (var item in this.items)
            {
                MethodInfo[] methods = item.Key.ChangeInterceptors;
                Debug.Assert(methods != null, "methods != null - should not have been tracking changes to the container otherwise.");
                foreach (var element in item.Value)
                {
                    parameters[0] = this.service.Updatable.ResolveResource(element.Key);
                    parameters[1] = element.Value;
                    for (int i = 0; i < methods.Length; i++)
                    {
                        try
                        {
                            methods[i].Invoke(this.service.Instance, parameters);
                        }
                        catch (TargetInvocationException exception)
                        {
                            ErrorHandler.HandleTargetInvocationException(exception);
                            throw;
                        }
                    }
                }
 
                // Make elements elegible for garbage collection.
                item.Value.Clear();
            }
 
            // Make dictionary elegible for garbage collection.
            this.items = null;
        }
 
        /// <summary>
        /// Tracks the specified <paramref name="target"/> for a 
        /// given <paramref name="action "/> on the <paramref name="container"/>.
        /// </summary>
        /// <param name="target">Object to be tracked.</param>
        /// <param name="container">Container in which object is changed.</param>
        /// <param name="action">Action affecting target.</param>
        /// <remarks>
        /// If <paramref name="target"/> was already being tracked, the actions are OR'ed together.
        /// </remarks>
        internal void TrackAction(object target, ResourceSetWrapper container, UpdateOperations action)
        {
            AssertActionValues(target, container);
            Debug.Assert(this.items != null, "this.items != null - otherwise FireNotification has already been called");
 
            // If it won't be necessary for us to fire authorizatio methods,
            // skip tracking altogether.
            if (container.ChangeInterceptors == null)
            {
                return;
            }
 
            // Get the container for which the change has taken place.
            Dictionary<object, UpdateOperations> changedItems;
            if (!this.items.TryGetValue(container, out changedItems))
            {
                // In order to mantain backwards compatibility, we are going to use default comparer for V1
                // providers. However, for V2 providers we are going to do reference equality comparisons.
                if (this.service.Provider.IsV1Provider)
                {
                    changedItems = new Dictionary<object, UpdateOperations>(EqualityComparer<object>.Default);
                }
                else
                {
                    changedItems = new Dictionary<object, UpdateOperations>(ReferenceEqualityComparer<object>.Instance);
                }
 
                this.items.Add(container, changedItems);
            }
 
            UpdateOperations existingAction;
            if (changedItems.TryGetValue(target, out existingAction))
            {
                if ((action | existingAction) != existingAction)
                {
                    changedItems[target] = action | existingAction;
                }
            }
            else
            {
                changedItems.Add(target, action);
            }
        }
 
        /// <summary>Asserts valid value for tracking update actions.</summary>
        /// <param name="target">Object to be tracked.</param>
        /// <param name="container">Container in which object is changed.</param>
        [Conditional("DEBUG")]
        private static void AssertActionValues(object target, ResourceSetWrapper container)
        {
            Debug.Assert(target != null, "target != null");
            Debug.Assert(container != null, "container != null");
        }
    }
}