1 //-----------------------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //-----------------------------------------------------------------------------
4 
5 namespace System.Activities.DurableInstancing
6 {
7     using System.Collections.Generic;
8     using System.Collections.ObjectModel;
9     using System.ComponentModel;
10     using System.Globalization;
11     using System.IO;
12     using System.IO.Compression;
13     using System.Runtime;
14     using System.Runtime.DurableInstancing;
15     using System.Runtime.Serialization;
16     using System.Linq;
17     using System.Xml.Linq;
18     using System.Text;
19     using System.Xml;
20     using System.Xml.Serialization;
21 
22     static class SerializationUtilities
23     {
CreateKeyBinaryBlob(List<CorrelationKey> correlationKeys)24         public static byte[] CreateKeyBinaryBlob(List<CorrelationKey> correlationKeys)
25         {
26             long memoryRequired = correlationKeys.Sum(i => i.BinaryData.Count);
27             byte[] concatenatedBlob = null;
28 
29             if (memoryRequired > 0)
30             {
31                 concatenatedBlob = new byte[memoryRequired];
32                 long insertLocation = 0;
33 
34                 foreach (CorrelationKey correlationKey in correlationKeys)
35                 {
36                     Buffer.BlockCopy(correlationKey.BinaryData.Array, 0, concatenatedBlob, Convert.ToInt32(insertLocation), Convert.ToInt32(correlationKey.BinaryData.Count));
37                     correlationKey.StartPosition = insertLocation;
38                     insertLocation += correlationKey.BinaryData.Count;
39                 }
40             }
41 
42             return concatenatedBlob;
43         }
44 
CreateCorrelationKeyXmlBlob(List<CorrelationKey> correlationKeys)45         public static object CreateCorrelationKeyXmlBlob(List<CorrelationKey> correlationKeys)
46         {
47             if (correlationKeys == null || correlationKeys.Count == 0)
48             {
49                 return DBNull.Value;
50             }
51 
52             StringBuilder stringBuilder = new StringBuilder(SqlWorkflowInstanceStoreConstants.DefaultStringBuilderCapacity);
53 
54             using (XmlWriter xmlWriter = XmlWriter.Create(stringBuilder))
55             {
56                 xmlWriter.WriteStartElement("CorrelationKeys");
57 
58                 foreach (CorrelationKey correlationKey in correlationKeys)
59                 {
60                     correlationKey.SerializeToXmlElement(xmlWriter);
61                 }
62 
63                 xmlWriter.WriteEndElement();
64             }
65 
66             return stringBuilder.ToString();
67         }
68 
IsPropertyTypeSqlVariantCompatible(InstanceValue value)69         public static bool IsPropertyTypeSqlVariantCompatible(InstanceValue value)
70         {
71             if ((value.IsDeletedValue) ||
72                 (value.Value == null) ||
73                 (value.Value is string && ((string)value.Value).Length <= 4000) ||
74                 (value.Value is Guid) ||
75                 (value.Value is DateTime) ||
76                 (value.Value is int) ||
77                 (value.Value is double) ||
78                 (value.Value is float) ||
79                 (value.Value is long) ||
80                 (value.Value is short) ||
81                 (value.Value is byte) ||
82                 (value.Value is decimal && CanDecimalBeStoredAsSqlVariant((decimal)value.Value)))
83             {
84                 return true;
85             }
86             else
87             {
88                 return false;
89             }
90         }
91 
DeserializeMetadataPropertyBag(byte[] serializedMetadataProperties, InstanceEncodingOption instanceEncodingOption)92         public static Dictionary<XName, InstanceValue> DeserializeMetadataPropertyBag(byte[] serializedMetadataProperties, InstanceEncodingOption instanceEncodingOption)
93         {
94             Dictionary<XName, InstanceValue> metadataProperties = new Dictionary<XName, InstanceValue>();
95 
96             if (serializedMetadataProperties != null)
97             {
98                 IObjectSerializer serializer = ObjectSerializerFactory.GetObjectSerializer(instanceEncodingOption);
99                 Dictionary<XName, object> propertyBag = serializer.DeserializePropertyBag(serializedMetadataProperties);
100 
101                 foreach (KeyValuePair<XName, object> property in propertyBag)
102                 {
103                     metadataProperties.Add(property.Key, new InstanceValue(property.Value));
104                 }
105             }
106 
107             return metadataProperties;
108         }
109 
SerializeMetadataPropertyBag(SaveWorkflowCommand saveWorkflowCommand, InstancePersistenceContext context, InstanceEncodingOption instanceEncodingOption)110         public static ArraySegment<byte> SerializeMetadataPropertyBag(SaveWorkflowCommand saveWorkflowCommand,
111             InstancePersistenceContext context, InstanceEncodingOption instanceEncodingOption)
112         {
113             IObjectSerializer serializer = ObjectSerializerFactory.GetObjectSerializer(instanceEncodingOption);
114             Dictionary<XName, object> propertyBagToSerialize = new Dictionary<XName, object>();
115 
116             if (context.InstanceView.InstanceMetadataConsistency == InstanceValueConsistency.None)
117             {
118                 foreach (KeyValuePair<XName, InstanceValue> metadataProperty in context.InstanceView.InstanceMetadata)
119                 {
120                     if ((metadataProperty.Value.Options & InstanceValueOptions.WriteOnly) == 0)
121                     {
122                         propertyBagToSerialize.Add(metadataProperty.Key, metadataProperty.Value.Value);
123                     }
124                 }
125             }
126 
127             foreach (KeyValuePair<XName, InstanceValue> metadataChange in saveWorkflowCommand.InstanceMetadataChanges)
128             {
129                 if (metadataChange.Value.IsDeletedValue)
130                 {
131                     if (context.InstanceView.InstanceMetadataConsistency == InstanceValueConsistency.None)
132                     {
133                         propertyBagToSerialize.Remove(metadataChange.Key);
134                     }
135                     else
136                     {
137                         propertyBagToSerialize[metadataChange.Key] = new DeletedMetadataValue();
138                     }
139                 }
140                 else if ((metadataChange.Value.Options & InstanceValueOptions.WriteOnly) == 0)
141                 {
142                     propertyBagToSerialize[metadataChange.Key] = metadataChange.Value.Value;
143                 }
144             }
145 
146             if (propertyBagToSerialize.Count > 0)
147             {
148                 return serializer.SerializePropertyBag(propertyBagToSerialize);
149             }
150 
151             return new ArraySegment<byte>();
152         }
153 
SerializePropertyBag(IDictionary<XName, InstanceValue> properties, InstanceEncodingOption encodingOption)154         public static ArraySegment<byte>[] SerializePropertyBag(IDictionary<XName, InstanceValue> properties, InstanceEncodingOption encodingOption)
155         {
156             ArraySegment<byte>[] dataArrays = new ArraySegment<byte>[4];
157 
158             if (properties.Count > 0)
159             {
160                 IObjectSerializer serializer = ObjectSerializerFactory.GetObjectSerializer(encodingOption);
161                 XmlPropertyBag primitiveProperties = new XmlPropertyBag();
162                 XmlPropertyBag primitiveWriteOnlyProperties = new XmlPropertyBag();
163                 Dictionary<XName, object> complexProperties = new Dictionary<XName, object>();
164                 Dictionary<XName, object> complexWriteOnlyProperties = new Dictionary<XName, object>();
165                 Dictionary<XName, object>[] propertyBags = new Dictionary<XName, object>[] { primitiveProperties, complexProperties,
166                     primitiveWriteOnlyProperties, complexWriteOnlyProperties };
167 
168                 foreach (KeyValuePair<XName, InstanceValue> property in properties)
169                 {
170                     bool isComplex = (XmlPropertyBag.GetPrimitiveType(property.Value.Value) == PrimitiveType.Unavailable);
171                     bool isWriteOnly = (property.Value.Options & InstanceValueOptions.WriteOnly) == InstanceValueOptions.WriteOnly;
172                     int index = (isWriteOnly ? 2 : 0) + (isComplex ? 1 : 0);
173                     propertyBags[index].Add(property.Key, property.Value.Value);
174                 }
175 
176                 // Remove the properties that are already stored as individual columns from the serialized blob
177                 primitiveWriteOnlyProperties.Remove(SqlWorkflowInstanceStoreConstants.StatusPropertyName);
178                 primitiveWriteOnlyProperties.Remove(SqlWorkflowInstanceStoreConstants.LastUpdatePropertyName);
179                 primitiveWriteOnlyProperties.Remove(SqlWorkflowInstanceStoreConstants.PendingTimerExpirationPropertyName);
180 
181                 complexWriteOnlyProperties.Remove(SqlWorkflowInstanceStoreConstants.BinaryBlockingBookmarksPropertyName);
182 
183                 for (int i = 0; i < propertyBags.Length; i++)
184                 {
185                     if (propertyBags[i].Count > 0)
186                     {
187                         if (propertyBags[i] is XmlPropertyBag)
188                         {
189                             dataArrays[i] = serializer.SerializeValue(propertyBags[i]);
190                         }
191                         else
192                         {
193                             dataArrays[i] = serializer.SerializePropertyBag(propertyBags[i]);
194                         }
195                     }
196                 }
197             }
198 
199             return dataArrays;
200         }
201 
SerializeKeyMetadata(IDictionary<XName, InstanceValue> metadataProperties, InstanceEncodingOption encodingOption)202         public static ArraySegment<byte> SerializeKeyMetadata(IDictionary<XName, InstanceValue> metadataProperties, InstanceEncodingOption encodingOption)
203         {
204             if (metadataProperties != null && metadataProperties.Count > 0)
205             {
206                 Dictionary<XName, object> propertyBag = new Dictionary<XName, object>();
207 
208                 foreach (KeyValuePair<XName, InstanceValue> property in metadataProperties)
209                 {
210                     if ((property.Value.Options & InstanceValueOptions.WriteOnly) != InstanceValueOptions.WriteOnly)
211                     {
212                         propertyBag.Add(property.Key, property.Value.Value);
213                     }
214                 }
215 
216                 IObjectSerializer serializer = ObjectSerializerFactory.GetObjectSerializer(encodingOption);
217                 return serializer.SerializePropertyBag(propertyBag);
218             }
219 
220             return new ArraySegment<byte>();
221         }
222 
DeserializeKeyMetadata(byte[] serializedKeyMetadata, InstanceEncodingOption encodingOption)223         public static Dictionary<XName, InstanceValue> DeserializeKeyMetadata(byte[] serializedKeyMetadata, InstanceEncodingOption encodingOption)
224         {
225             return DeserializeMetadataPropertyBag(serializedKeyMetadata, encodingOption);
226         }
227 
DeserializePropertyBag(byte[] primitiveDataProperties, byte[] complexDataProperties, InstanceEncodingOption encodingOption)228         public static Dictionary<XName, InstanceValue> DeserializePropertyBag(byte[] primitiveDataProperties, byte[] complexDataProperties, InstanceEncodingOption encodingOption)
229         {
230             IObjectSerializer serializer = ObjectSerializerFactory.GetObjectSerializer(encodingOption);
231             Dictionary<XName, InstanceValue> properties = new Dictionary<XName, InstanceValue>();
232             Dictionary<XName, object>[] propertyBags = new Dictionary<XName, object>[2];
233 
234             if (primitiveDataProperties != null)
235             {
236                 propertyBags[0] = (Dictionary<XName, object>)serializer.DeserializeValue(primitiveDataProperties);
237             }
238 
239             if (complexDataProperties != null)
240             {
241                 propertyBags[1] = serializer.DeserializePropertyBag(complexDataProperties);
242             }
243 
244             foreach (Dictionary<XName, object> propertyBag in propertyBags)
245             {
246                 if (propertyBag != null)
247                 {
248                     foreach (KeyValuePair<XName, object> property in propertyBag)
249                     {
250                         properties.Add(property.Key, new InstanceValue(property.Value));
251                     }
252                 }
253             }
254 
255             return properties;
256         }
257 
CanDecimalBeStoredAsSqlVariant(decimal value)258         static bool CanDecimalBeStoredAsSqlVariant(decimal value)
259         {
260             string decimalAsString = value.ToString("G", CultureInfo.InvariantCulture);
261             return ((decimalAsString.Length - decimalAsString.IndexOf(".", StringComparison.Ordinal)) - 1 <= 18);
262         }
263 
GetIdentityHash(WorkflowIdentity id)264         static Guid GetIdentityHash(WorkflowIdentity id)
265         {
266             byte[] identityHashBuffer = Encoding.Unicode.GetBytes(id.ToString());
267             return new Guid(HashHelper.ComputeHash(identityHashBuffer));
268         }
269 
GetIdentityAnyRevisionFilterHash(WorkflowIdentity id)270         static Guid GetIdentityAnyRevisionFilterHash(WorkflowIdentity id)
271         {
272             if (id.Version != null)
273             {
274                 Version version;
275                 if (id.Version.Build >= 0)
276                 {
277                     version = new Version(id.Version.Major, id.Version.Minor, id.Version.Build);
278                 }
279                 else
280                 {
281                     version = new Version(id.Version.Minor, id.Version.Minor);
282                 }
283 
284                 return GetIdentityHash(new WorkflowIdentity(id.Name, version, id.Package));
285             }
286             else
287             {
288                 return GetIdentityHash(id);
289             }
290         }
291 
GetIdentityMetadataXml(InstancePersistenceCommand command)292         public static string GetIdentityMetadataXml(InstancePersistenceCommand command)
293         {
294             StringBuilder stringBuilder = new StringBuilder(512);
295             Guid idHash = Guid.Empty;
296             Guid idAnyRevisionHash = Guid.Empty;
297             int workflowIdentityFilter = (int)WorkflowIdentityFilter.Exact;
298 
299             IList<WorkflowIdentity> identityCollection = null;
300 
301             if (command is CreateWorkflowOwnerWithIdentityCommand)
302             {
303                 InstanceValue instanceValueIdentityCollection = null;
304                 CreateWorkflowOwnerWithIdentityCommand ownerCommand = command as CreateWorkflowOwnerWithIdentityCommand;
305 
306                 if (ownerCommand.InstanceOwnerMetadata.TryGetValue(Workflow45Namespace.DefinitionIdentities, out instanceValueIdentityCollection))
307                 {
308                     if (instanceValueIdentityCollection.Value != null)
309                     {
310                         identityCollection = instanceValueIdentityCollection.Value as IList<WorkflowIdentity>;
311                         if (identityCollection == null)
312                         {
313                             string typeName = typeof(IList<>).Name.Replace("`1", "<" + typeof(WorkflowIdentity).Name + ">");
314                             throw FxTrace.Exception.AsError(new InstancePersistenceCommandException(SR.InvalidMetadataValue(Workflow45Namespace.DefinitionIdentities, typeName)));
315                         }
316                     }
317                 }
318 
319                 InstanceValue instanceValue = null;
320                 if (ownerCommand.InstanceOwnerMetadata.TryGetValue(Workflow45Namespace.DefinitionIdentityFilter, out instanceValue))
321                 {
322                     if (instanceValue.Value != null)
323                     {
324                         if (instanceValue.Value is WorkflowIdentityFilter)
325                         {
326                             workflowIdentityFilter = (int)instanceValue.Value;
327                         }
328                         else
329                         {
330                             workflowIdentityFilter = -1;
331                         }
332 
333                         if (workflowIdentityFilter != (int)WorkflowIdentityFilter.Exact &&
334                             workflowIdentityFilter != (int)WorkflowIdentityFilter.Any &&
335                             workflowIdentityFilter != (int)WorkflowIdentityFilter.AnyRevision)
336                         {
337                             throw FxTrace.Exception.AsError(new InstancePersistenceCommandException(SR.InvalidMetadataValue(Workflow45Namespace.DefinitionIdentityFilter, typeof(WorkflowIdentityFilter).Name)));
338                         }
339                     }
340                 }
341             }
342             else if (command is SaveWorkflowCommand)
343             {
344                 InstanceValue instanceValue = null;
345                 SaveWorkflowCommand saveCommand = command as SaveWorkflowCommand;
346                 if (saveCommand.InstanceMetadataChanges.TryGetValue(Workflow45Namespace.DefinitionIdentity, out instanceValue))
347                 {
348                     if (!instanceValue.IsDeletedValue && instanceValue.Value != null)
349                     {
350                         identityCollection = new Collection<WorkflowIdentity>();
351                         if (!(instanceValue.Value is WorkflowIdentity))
352                         {
353                             throw FxTrace.Exception.AsError(new InstancePersistenceCommandException(SR.InvalidMetadataValue(Workflow45Namespace.DefinitionIdentity, typeof(WorkflowIdentity).Name)));
354                         }
355 
356                         identityCollection.Add((WorkflowIdentity)instanceValue.Value);
357                     }
358                 }
359                 else
360                 {
361                     // If identity isn't specified, we preserve the instance's existing identity
362                     return null;
363                 }
364             }
365             else
366             {
367                 return null;
368             }
369 
370             if (identityCollection == null)
371             {
372                 // Assume NULL Identity
373                 identityCollection = new Collection<WorkflowIdentity>();
374                 identityCollection.Add(null);
375             }
376 
377             using (XmlWriter xmlWriter = XmlWriter.Create(stringBuilder))
378             {
379                 xmlWriter.WriteStartDocument();
380                 xmlWriter.WriteStartElement("IdentityMetadata");
381 
382                 // Write the Identity Collection
383                 xmlWriter.WriteStartElement("IdentityCollection");
384                 foreach (WorkflowIdentity id in identityCollection)
385                 {
386                     xmlWriter.WriteStartElement("Identity");
387 
388                     if (id == null)
389                     {
390                         xmlWriter.WriteElementString("DefinitionIdentityHash", Guid.Empty.ToString());
391                         xmlWriter.WriteElementString("DefinitionIdentityAnyRevisionHash", Guid.Empty.ToString());
392                     }
393                     else
394                     {
395                         idHash = GetIdentityHash(id);
396                         idAnyRevisionHash = GetIdentityAnyRevisionFilterHash(id);
397 
398                         xmlWriter.WriteElementString("DefinitionIdentityHash", idHash.ToString());
399                         xmlWriter.WriteElementString("DefinitionIdentityAnyRevisionHash", idAnyRevisionHash.ToString());
400                         xmlWriter.WriteElementString("Name", id.Name);
401 
402                         if (id.Package != null)
403                         {
404                             xmlWriter.WriteElementString("Package", id.Package);
405                         }
406 
407                         if (id.Version != null)
408                         {
409                             xmlWriter.WriteElementString("Major", id.Version.Major.ToString(CultureInfo.InvariantCulture));
410                             xmlWriter.WriteElementString("Minor", id.Version.Minor.ToString(CultureInfo.InvariantCulture));
411                             if (id.Version.Build >= 0)
412                             {
413                                 xmlWriter.WriteElementString("Build", id.Version.Build.ToString(CultureInfo.InvariantCulture));
414                                 if (id.Version.Revision >= 0)
415                                 {
416                                     xmlWriter.WriteElementString("Revision", id.Version.Revision.ToString(CultureInfo.InvariantCulture));
417                                 }
418                             }
419                         }
420                     }
421 
422                     xmlWriter.WriteEndElement();
423                 }
424                 xmlWriter.WriteEndElement();
425 
426                 // Write the WorkflowIdentityFilter
427                 xmlWriter.WriteElementString("WorkflowIdentityFilter", workflowIdentityFilter.ToString(CultureInfo.InvariantCulture));
428 
429                 xmlWriter.WriteEndElement();
430             }
431             return stringBuilder.ToString();
432         }
433 
434     }
435 }
436