File: System\Activities\DurableInstancing\SerializationUtilities.cs
Project: ndp\cdf\src\NetFx40\System.Activities.DurableInstancing\System.Activities.DurableInstancing.csproj (System.Activities.DurableInstancing)
//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//-----------------------------------------------------------------------------
 
namespace System.Activities.DurableInstancing
{
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Globalization;
    using System.IO;
    using System.IO.Compression;
    using System.Runtime;
    using System.Runtime.DurableInstancing;
    using System.Runtime.Serialization;
    using System.Linq;
    using System.Xml.Linq;
    using System.Text;
    using System.Xml;
    using System.Xml.Serialization;
 
    static class SerializationUtilities
    {
        public static byte[] CreateKeyBinaryBlob(List<CorrelationKey> correlationKeys)
        {
            long memoryRequired = correlationKeys.Sum(i => i.BinaryData.Count);
            byte[] concatenatedBlob = null;
 
            if (memoryRequired > 0)
            {
                concatenatedBlob = new byte[memoryRequired];
                long insertLocation = 0;
 
                foreach (CorrelationKey correlationKey in correlationKeys)
                {
                    Buffer.BlockCopy(correlationKey.BinaryData.Array, 0, concatenatedBlob, Convert.ToInt32(insertLocation), Convert.ToInt32(correlationKey.BinaryData.Count));
                    correlationKey.StartPosition = insertLocation;
                    insertLocation += correlationKey.BinaryData.Count;
                }
            }
 
            return concatenatedBlob;
        }
 
        public static object CreateCorrelationKeyXmlBlob(List<CorrelationKey> correlationKeys)
        {
            if (correlationKeys == null || correlationKeys.Count == 0)
            {
                return DBNull.Value;
            }
 
            StringBuilder stringBuilder = new StringBuilder(SqlWorkflowInstanceStoreConstants.DefaultStringBuilderCapacity);
 
            using (XmlWriter xmlWriter = XmlWriter.Create(stringBuilder))
            {
                xmlWriter.WriteStartElement("CorrelationKeys");
 
                foreach (CorrelationKey correlationKey in correlationKeys)
                {
                    correlationKey.SerializeToXmlElement(xmlWriter);
                }
 
                xmlWriter.WriteEndElement();
            }
 
            return stringBuilder.ToString();
        }
 
        public static bool IsPropertyTypeSqlVariantCompatible(InstanceValue value)
        {
            if ((value.IsDeletedValue) ||
                (value.Value == null) ||
                (value.Value is string && ((string)value.Value).Length <= 4000) ||
                (value.Value is Guid) ||
                (value.Value is DateTime) ||
                (value.Value is int) ||
                (value.Value is double) ||
                (value.Value is float) ||
                (value.Value is long) ||
                (value.Value is short) ||
                (value.Value is byte) ||
                (value.Value is decimal && CanDecimalBeStoredAsSqlVariant((decimal)value.Value)))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
 
        public static Dictionary<XName, InstanceValue> DeserializeMetadataPropertyBag(byte[] serializedMetadataProperties, InstanceEncodingOption instanceEncodingOption)
        {
            Dictionary<XName, InstanceValue> metadataProperties = new Dictionary<XName, InstanceValue>();
 
            if (serializedMetadataProperties != null)
            {
                IObjectSerializer serializer = ObjectSerializerFactory.GetObjectSerializer(instanceEncodingOption);
                Dictionary<XName, object> propertyBag = serializer.DeserializePropertyBag(serializedMetadataProperties);
 
                foreach (KeyValuePair<XName, object> property in propertyBag)
                {
                    metadataProperties.Add(property.Key, new InstanceValue(property.Value));
                }
            }
 
            return metadataProperties;
        }
 
        public static ArraySegment<byte> SerializeMetadataPropertyBag(SaveWorkflowCommand saveWorkflowCommand,
            InstancePersistenceContext context, InstanceEncodingOption instanceEncodingOption)
        {
            IObjectSerializer serializer = ObjectSerializerFactory.GetObjectSerializer(instanceEncodingOption);
            Dictionary<XName, object> propertyBagToSerialize = new Dictionary<XName, object>();
 
            if (context.InstanceView.InstanceMetadataConsistency == InstanceValueConsistency.None)
            {
                foreach (KeyValuePair<XName, InstanceValue> metadataProperty in context.InstanceView.InstanceMetadata)
                {
                    if ((metadataProperty.Value.Options & InstanceValueOptions.WriteOnly) == 0)
                    {
                        propertyBagToSerialize.Add(metadataProperty.Key, metadataProperty.Value.Value);
                    }
                }
            }
 
            foreach (KeyValuePair<XName, InstanceValue> metadataChange in saveWorkflowCommand.InstanceMetadataChanges)
            {
                if (metadataChange.Value.IsDeletedValue)
                {
                    if (context.InstanceView.InstanceMetadataConsistency == InstanceValueConsistency.None)
                    {
                        propertyBagToSerialize.Remove(metadataChange.Key);
                    }
                    else
                    {
                        propertyBagToSerialize[metadataChange.Key] = new DeletedMetadataValue();
                    }
                }
                else if ((metadataChange.Value.Options & InstanceValueOptions.WriteOnly) == 0)
                {
                    propertyBagToSerialize[metadataChange.Key] = metadataChange.Value.Value;
                }
            }
 
            if (propertyBagToSerialize.Count > 0)
            {
                return serializer.SerializePropertyBag(propertyBagToSerialize);
            }
 
            return new ArraySegment<byte>();
        }
 
        public static ArraySegment<byte>[] SerializePropertyBag(IDictionary<XName, InstanceValue> properties, InstanceEncodingOption encodingOption)
        {
            ArraySegment<byte>[] dataArrays = new ArraySegment<byte>[4];
 
            if (properties.Count > 0)
            {
                IObjectSerializer serializer = ObjectSerializerFactory.GetObjectSerializer(encodingOption);
                XmlPropertyBag primitiveProperties = new XmlPropertyBag();
                XmlPropertyBag primitiveWriteOnlyProperties = new XmlPropertyBag();
                Dictionary<XName, object> complexProperties = new Dictionary<XName, object>();
                Dictionary<XName, object> complexWriteOnlyProperties = new Dictionary<XName, object>();
                Dictionary<XName, object>[] propertyBags = new Dictionary<XName, object>[] { primitiveProperties, complexProperties,
                    primitiveWriteOnlyProperties, complexWriteOnlyProperties };
 
                foreach (KeyValuePair<XName, InstanceValue> property in properties)
                {
                    bool isComplex = (XmlPropertyBag.GetPrimitiveType(property.Value.Value) == PrimitiveType.Unavailable);
                    bool isWriteOnly = (property.Value.Options & InstanceValueOptions.WriteOnly) == InstanceValueOptions.WriteOnly;
                    int index = (isWriteOnly ? 2 : 0) + (isComplex ? 1 : 0);
                    propertyBags[index].Add(property.Key, property.Value.Value);
                }
 
                // Remove the properties that are already stored as individual columns from the serialized blob
                primitiveWriteOnlyProperties.Remove(SqlWorkflowInstanceStoreConstants.StatusPropertyName);
                primitiveWriteOnlyProperties.Remove(SqlWorkflowInstanceStoreConstants.LastUpdatePropertyName);
                primitiveWriteOnlyProperties.Remove(SqlWorkflowInstanceStoreConstants.PendingTimerExpirationPropertyName);
 
                complexWriteOnlyProperties.Remove(SqlWorkflowInstanceStoreConstants.BinaryBlockingBookmarksPropertyName);
 
                for (int i = 0; i < propertyBags.Length; i++)
                {
                    if (propertyBags[i].Count > 0)
                    {
                        if (propertyBags[i] is XmlPropertyBag)
                        {
                            dataArrays[i] = serializer.SerializeValue(propertyBags[i]);
                        }
                        else
                        {
                            dataArrays[i] = serializer.SerializePropertyBag(propertyBags[i]);
                        }
                    }
                }
            }
 
            return dataArrays;
        }
 
        public static ArraySegment<byte> SerializeKeyMetadata(IDictionary<XName, InstanceValue> metadataProperties, InstanceEncodingOption encodingOption)
        {
            if (metadataProperties != null && metadataProperties.Count > 0)
            {
                Dictionary<XName, object> propertyBag = new Dictionary<XName, object>();
 
                foreach (KeyValuePair<XName, InstanceValue> property in metadataProperties)
                {
                    if ((property.Value.Options & InstanceValueOptions.WriteOnly) != InstanceValueOptions.WriteOnly)
                    {
                        propertyBag.Add(property.Key, property.Value.Value);
                    }
                }
 
                IObjectSerializer serializer = ObjectSerializerFactory.GetObjectSerializer(encodingOption);
                return serializer.SerializePropertyBag(propertyBag);
            }
 
            return new ArraySegment<byte>();
        }
 
        public static Dictionary<XName, InstanceValue> DeserializeKeyMetadata(byte[] serializedKeyMetadata, InstanceEncodingOption encodingOption)
        {
            return DeserializeMetadataPropertyBag(serializedKeyMetadata, encodingOption);
        }
 
        public static Dictionary<XName, InstanceValue> DeserializePropertyBag(byte[] primitiveDataProperties, byte[] complexDataProperties, InstanceEncodingOption encodingOption)
        {
            IObjectSerializer serializer = ObjectSerializerFactory.GetObjectSerializer(encodingOption);
            Dictionary<XName, InstanceValue> properties = new Dictionary<XName, InstanceValue>();
            Dictionary<XName, object>[] propertyBags = new Dictionary<XName, object>[2];
 
            if (primitiveDataProperties != null)
            {
                propertyBags[0] = (Dictionary<XName, object>)serializer.DeserializeValue(primitiveDataProperties);
            }
 
            if (complexDataProperties != null)
            {
                propertyBags[1] = serializer.DeserializePropertyBag(complexDataProperties);
            }
 
            foreach (Dictionary<XName, object> propertyBag in propertyBags)
            {
                if (propertyBag != null)
                {
                    foreach (KeyValuePair<XName, object> property in propertyBag)
                    {
                        properties.Add(property.Key, new InstanceValue(property.Value));
                    }
                }
            }
 
            return properties;
        }
 
        static bool CanDecimalBeStoredAsSqlVariant(decimal value)
        {
            string decimalAsString = value.ToString("G", CultureInfo.InvariantCulture);
            return ((decimalAsString.Length - decimalAsString.IndexOf(".", StringComparison.Ordinal)) - 1 <= 18);
        }
 
        static Guid GetIdentityHash(WorkflowIdentity id)
        {
            byte[] identityHashBuffer = Encoding.Unicode.GetBytes(id.ToString());
            return new Guid(HashHelper.ComputeHash(identityHashBuffer));
        }
 
        static Guid GetIdentityAnyRevisionFilterHash(WorkflowIdentity id)
        {
            if (id.Version != null)
            {
                Version version;
                if (id.Version.Build >= 0)
                {
                    version = new Version(id.Version.Major, id.Version.Minor, id.Version.Build);
                }
                else
                {
                    version = new Version(id.Version.Minor, id.Version.Minor);
                }
 
                return GetIdentityHash(new WorkflowIdentity(id.Name, version, id.Package));
            }
            else
            {
                return GetIdentityHash(id);
            }
        }
 
        public static string GetIdentityMetadataXml(InstancePersistenceCommand command)
        {
            StringBuilder stringBuilder = new StringBuilder(512);
            Guid idHash = Guid.Empty;
            Guid idAnyRevisionHash = Guid.Empty;
            int workflowIdentityFilter = (int)WorkflowIdentityFilter.Exact;
 
            IList<WorkflowIdentity> identityCollection = null;
 
            if (command is CreateWorkflowOwnerWithIdentityCommand)
            {
                InstanceValue instanceValueIdentityCollection = null;
                CreateWorkflowOwnerWithIdentityCommand ownerCommand = command as CreateWorkflowOwnerWithIdentityCommand;
 
                if (ownerCommand.InstanceOwnerMetadata.TryGetValue(Workflow45Namespace.DefinitionIdentities, out instanceValueIdentityCollection))
                {
                    if (instanceValueIdentityCollection.Value != null)
                    {
                        identityCollection = instanceValueIdentityCollection.Value as IList<WorkflowIdentity>;
                        if (identityCollection == null)
                        {
                            string typeName = typeof(IList<>).Name.Replace("`1", "<" + typeof(WorkflowIdentity).Name + ">");
                            throw FxTrace.Exception.AsError(new InstancePersistenceCommandException(SR.InvalidMetadataValue(Workflow45Namespace.DefinitionIdentities, typeName)));
                        }
                    }
                }
 
                InstanceValue instanceValue = null;
                if (ownerCommand.InstanceOwnerMetadata.TryGetValue(Workflow45Namespace.DefinitionIdentityFilter, out instanceValue))
                {
                    if (instanceValue.Value != null)
                    {
                        if (instanceValue.Value is WorkflowIdentityFilter)
                        {
                            workflowIdentityFilter = (int)instanceValue.Value;
                        }
                        else
                        {
                            workflowIdentityFilter = -1;
                        }
 
                        if (workflowIdentityFilter != (int)WorkflowIdentityFilter.Exact &&
                            workflowIdentityFilter != (int)WorkflowIdentityFilter.Any &&
                            workflowIdentityFilter != (int)WorkflowIdentityFilter.AnyRevision)
                        {
                            throw FxTrace.Exception.AsError(new InstancePersistenceCommandException(SR.InvalidMetadataValue(Workflow45Namespace.DefinitionIdentityFilter, typeof(WorkflowIdentityFilter).Name)));
                        }
                    }
                }
            }
            else if (command is SaveWorkflowCommand)
            {
                InstanceValue instanceValue = null;
                SaveWorkflowCommand saveCommand = command as SaveWorkflowCommand;
                if (saveCommand.InstanceMetadataChanges.TryGetValue(Workflow45Namespace.DefinitionIdentity, out instanceValue))
                {
                    if (!instanceValue.IsDeletedValue && instanceValue.Value != null)
                    {
                        identityCollection = new Collection<WorkflowIdentity>();
                        if (!(instanceValue.Value is WorkflowIdentity))
                        {
                            throw FxTrace.Exception.AsError(new InstancePersistenceCommandException(SR.InvalidMetadataValue(Workflow45Namespace.DefinitionIdentity, typeof(WorkflowIdentity).Name)));
                        }
 
                        identityCollection.Add((WorkflowIdentity)instanceValue.Value);
                    }
                }
                else
                {
                    // If identity isn't specified, we preserve the instance's existing identity
                    return null;
                }
            }
            else
            {
                return null;
            }
 
            if (identityCollection == null)
            {
                // Assume NULL Identity
                identityCollection = new Collection<WorkflowIdentity>();
                identityCollection.Add(null);
            }
 
            using (XmlWriter xmlWriter = XmlWriter.Create(stringBuilder))
            {
                xmlWriter.WriteStartDocument();
                xmlWriter.WriteStartElement("IdentityMetadata");
 
                // Write the Identity Collection
                xmlWriter.WriteStartElement("IdentityCollection");
                foreach (WorkflowIdentity id in identityCollection)
                {
                    xmlWriter.WriteStartElement("Identity");
        
                    if (id == null)
                    {
                        xmlWriter.WriteElementString("DefinitionIdentityHash", Guid.Empty.ToString());
                        xmlWriter.WriteElementString("DefinitionIdentityAnyRevisionHash", Guid.Empty.ToString());
                    }
                    else
                    {
                        idHash = GetIdentityHash(id);
                        idAnyRevisionHash = GetIdentityAnyRevisionFilterHash(id);
 
                        xmlWriter.WriteElementString("DefinitionIdentityHash", idHash.ToString());
                        xmlWriter.WriteElementString("DefinitionIdentityAnyRevisionHash", idAnyRevisionHash.ToString());
                        xmlWriter.WriteElementString("Name", id.Name);
                        
                        if (id.Package != null)
                        {
                            xmlWriter.WriteElementString("Package", id.Package);
                        }
 
                        if (id.Version != null)
                        {
                            xmlWriter.WriteElementString("Major", id.Version.Major.ToString(CultureInfo.InvariantCulture));
                            xmlWriter.WriteElementString("Minor", id.Version.Minor.ToString(CultureInfo.InvariantCulture));
                            if (id.Version.Build >= 0)
                            {
                                xmlWriter.WriteElementString("Build", id.Version.Build.ToString(CultureInfo.InvariantCulture));
                                if (id.Version.Revision >= 0)
                                {
                                    xmlWriter.WriteElementString("Revision", id.Version.Revision.ToString(CultureInfo.InvariantCulture));
                                }
                            }
                        }
                    }
        
                    xmlWriter.WriteEndElement();
                }
                xmlWriter.WriteEndElement();
 
                // Write the WorkflowIdentityFilter
                xmlWriter.WriteElementString("WorkflowIdentityFilter", workflowIdentityFilter.ToString(CultureInfo.InvariantCulture));
 
                xmlWriter.WriteEndElement();
            }
            return stringBuilder.ToString();
        }
 
    }
}