e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
436 lines
19 KiB
C#
436 lines
19 KiB
C#
//-----------------------------------------------------------------------------
|
|
// 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();
|
|
}
|
|
|
|
}
|
|
}
|