//----------------------------------------------------------------------------- // 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 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 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 DeserializeMetadataPropertyBag(byte[] serializedMetadataProperties, InstanceEncodingOption instanceEncodingOption) { Dictionary metadataProperties = new Dictionary(); if (serializedMetadataProperties != null) { IObjectSerializer serializer = ObjectSerializerFactory.GetObjectSerializer(instanceEncodingOption); Dictionary propertyBag = serializer.DeserializePropertyBag(serializedMetadataProperties); foreach (KeyValuePair property in propertyBag) { metadataProperties.Add(property.Key, new InstanceValue(property.Value)); } } return metadataProperties; } public static ArraySegment SerializeMetadataPropertyBag(SaveWorkflowCommand saveWorkflowCommand, InstancePersistenceContext context, InstanceEncodingOption instanceEncodingOption) { IObjectSerializer serializer = ObjectSerializerFactory.GetObjectSerializer(instanceEncodingOption); Dictionary propertyBagToSerialize = new Dictionary(); if (context.InstanceView.InstanceMetadataConsistency == InstanceValueConsistency.None) { foreach (KeyValuePair metadataProperty in context.InstanceView.InstanceMetadata) { if ((metadataProperty.Value.Options & InstanceValueOptions.WriteOnly) == 0) { propertyBagToSerialize.Add(metadataProperty.Key, metadataProperty.Value.Value); } } } foreach (KeyValuePair 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(); } public static ArraySegment[] SerializePropertyBag(IDictionary properties, InstanceEncodingOption encodingOption) { ArraySegment[] dataArrays = new ArraySegment[4]; if (properties.Count > 0) { IObjectSerializer serializer = ObjectSerializerFactory.GetObjectSerializer(encodingOption); XmlPropertyBag primitiveProperties = new XmlPropertyBag(); XmlPropertyBag primitiveWriteOnlyProperties = new XmlPropertyBag(); Dictionary complexProperties = new Dictionary(); Dictionary complexWriteOnlyProperties = new Dictionary(); Dictionary[] propertyBags = new Dictionary[] { primitiveProperties, complexProperties, primitiveWriteOnlyProperties, complexWriteOnlyProperties }; foreach (KeyValuePair 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 SerializeKeyMetadata(IDictionary metadataProperties, InstanceEncodingOption encodingOption) { if (metadataProperties != null && metadataProperties.Count > 0) { Dictionary propertyBag = new Dictionary(); foreach (KeyValuePair 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(); } public static Dictionary DeserializeKeyMetadata(byte[] serializedKeyMetadata, InstanceEncodingOption encodingOption) { return DeserializeMetadataPropertyBag(serializedKeyMetadata, encodingOption); } public static Dictionary DeserializePropertyBag(byte[] primitiveDataProperties, byte[] complexDataProperties, InstanceEncodingOption encodingOption) { IObjectSerializer serializer = ObjectSerializerFactory.GetObjectSerializer(encodingOption); Dictionary properties = new Dictionary(); Dictionary[] propertyBags = new Dictionary[2]; if (primitiveDataProperties != null) { propertyBags[0] = (Dictionary)serializer.DeserializeValue(primitiveDataProperties); } if (complexDataProperties != null) { propertyBags[1] = serializer.DeserializePropertyBag(complexDataProperties); } foreach (Dictionary propertyBag in propertyBags) { if (propertyBag != null) { foreach (KeyValuePair 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 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; 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(); 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(); 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(); } } }