Bump corefx
[mono-project.git] / mcs / class / referencesource / System.Workflow.Runtime / WorkflowDefinitionDispenser.cs
blobad491b256995dccce6c2cf401e96a7d59d8413a0
1 #region Imports
3 using System;
4 using System.ComponentModel;
5 using System.ComponentModel.Design;
6 using System.ComponentModel.Design.Serialization;
7 using System.Diagnostics;
8 using System.Collections;
9 using System.Collections.Generic;
10 using System.Collections.Specialized;
11 using System.Collections.ObjectModel;
12 using System.Configuration;
13 using System.Reflection;
14 using System.Threading;
15 using System.Globalization;
16 using System.IO;
17 using System.Xml;
18 using System.Text;
19 using System.Workflow.Runtime.Hosting;
20 using System.Workflow.Runtime.Configuration;
21 using System.Workflow.ComponentModel;
22 using System.Workflow.Runtime.Tracking;
23 using System.Workflow.ComponentModel.Compiler;
24 using System.Workflow.ComponentModel.Serialization;
25 using System.Security.Cryptography;
26 using System.Workflow.ComponentModel.Design;
28 #endregion
31 namespace System.Workflow.Runtime
33 internal sealed class WorkflowDefinitionDispenser : IDisposable
35 private MruCache workflowTypes;
36 private MruCache xomlFragments;
37 private Dictionary<Type, List<PropertyInfo>> workflowOutParameters;
38 private WorkflowRuntime workflowRuntime;
39 private bool validateOnCreate = true;
40 internal static DependencyProperty WorkflowDefinitionHashCodeProperty = DependencyProperty.RegisterAttached("WorkflowDefinitionHashCode", typeof(byte[]), typeof(WorkflowDefinitionDispenser));
41 internal event EventHandler<WorkflowDefinitionEventArgs> WorkflowDefinitionLoaded;
43 private ReaderWriterLock parametersLock;
45 internal WorkflowDefinitionDispenser(WorkflowRuntime runtime, bool validateOnCreate, int capacity)
47 if (capacity <= 0)
49 capacity = 2000;
51 this.workflowRuntime = runtime;
52 this.workflowTypes = new MruCache(capacity, this, CacheType.Type);
53 this.xomlFragments = new MruCache(capacity, this, CacheType.Xoml);
54 this.workflowOutParameters = new Dictionary<Type, List<PropertyInfo>>();
55 this.parametersLock = new ReaderWriterLock();
56 this.validateOnCreate = validateOnCreate;
59 internal ReadOnlyCollection<PropertyInfo> GetOutputParameters(Activity rootActivity)
61 Type workflowType = rootActivity.GetType();
62 this.parametersLock.AcquireReaderLock(-1);
63 try
65 if (this.workflowOutParameters.ContainsKey(workflowType))
66 return new ReadOnlyCollection<PropertyInfo>(this.workflowOutParameters[workflowType]);
68 finally
70 this.parametersLock.ReleaseLock();
73 // We will recurse at most once because CacheOutputParameters() will perform negative caching.
74 CacheOutputParameters(rootActivity);
75 return GetOutputParameters(rootActivity);
78 internal void GetWorkflowTypes(out ReadOnlyCollection<Type> keys, out ReadOnlyCollection<Activity> values)
80 this.workflowTypes.GetWorkflowDefinitions<Type>(out keys, out values);
83 internal void GetWorkflowDefinitions(out ReadOnlyCollection<byte[]> keys, out ReadOnlyCollection<Activity> values)
85 this.xomlFragments.GetWorkflowDefinitions<byte[]>(out keys, out values);
88 internal Activity GetWorkflowDefinition(byte[] xomlHashCode)
90 Activity workflowDefinition = null;
91 if (xomlHashCode == null)
92 throw new ArgumentNullException("xomlHashCode");
94 workflowDefinition = xomlFragments.GetDefinition(xomlHashCode);
96 if (workflowDefinition == null)
97 throw new ArgumentException("xomlHashCode");
99 return workflowDefinition;
102 internal Activity GetWorkflowDefinition(Type workflowType)
104 if (workflowType == null)
105 throw new ArgumentNullException("workflowType");
107 return this.GetRootActivity(workflowType, false, true);
110 internal Activity GetRootActivity(Type workflowType, bool createNew, bool initForRuntime)
112 Activity root = null;
114 if (createNew)
115 return LoadRootActivity(workflowType, false, initForRuntime);
116 bool exist;
117 root = workflowTypes.GetOrGenerateDefinition(workflowType, null, null, null, initForRuntime, out exist);
118 if (exist)
120 return root;
122 // Set the locking object used for cloning the definition
123 // for non-internal use (WorkflowInstance.GetWorkflowDefinition
124 // and WorkflowCompletedEventArgs.WorkflowDefinition)
125 WorkflowDefinitionLock.SetWorkflowDefinitionLockObject(root, new object());
127 EventHandler<WorkflowDefinitionEventArgs> localWorkflowDefinitionLoaded = WorkflowDefinitionLoaded;
128 if (localWorkflowDefinitionLoaded != null)
129 localWorkflowDefinitionLoaded(this.workflowRuntime, new WorkflowDefinitionEventArgs(workflowType));
131 return root;
134 // This function will create a new root activity definition tree by deserializing the xoml and the rules file.
135 // The last parameter createNew should be true when the caller is asking for a new definition for performing
136 // dynamic updates instead of a cached definition.
137 internal Activity GetRootActivity(string xomlText, string rulesText, bool createNew, bool initForRuntime)
139 if (string.IsNullOrEmpty(xomlText))
140 throw new ArgumentNullException("xomlText");
142 //calculate the "hash". Think 60s!
143 byte[] xomlHashCode = null;
144 MemoryStream xomlBytesStream = new MemoryStream();
145 using (StreamWriter streamWriter = new StreamWriter(xomlBytesStream))
147 streamWriter.Write(xomlText);
149 //consider rules, if they exist
150 if (!string.IsNullOrEmpty(rulesText))
151 streamWriter.Write(rulesText);
153 streamWriter.Flush();
154 xomlBytesStream.Position = 0;
156 xomlHashCode = MD5HashHelper.ComputeHash(xomlBytesStream.GetBuffer());
159 if (createNew)
160 return LoadRootActivity(xomlText, rulesText, xomlHashCode, false, initForRuntime);
162 bool exist;
163 Activity root = xomlFragments.GetOrGenerateDefinition(null, xomlText, rulesText, xomlHashCode, initForRuntime, out exist);
164 if (exist)
166 return root;
168 // Set the locking object used for cloning the definition
169 // for non-internal use (WorkflowInstance.GetWorkflowDefinition
170 // and WorkflowCompletedEventArgs.WorkflowDefinition)
171 WorkflowDefinitionLock.SetWorkflowDefinitionLockObject(root, new object());
173 EventHandler<WorkflowDefinitionEventArgs> localWorkflowDefinitionLoaded = WorkflowDefinitionLoaded;
174 if (localWorkflowDefinitionLoaded != null)
175 localWorkflowDefinitionLoaded(this.workflowRuntime, new WorkflowDefinitionEventArgs(xomlHashCode));
177 return root;
180 internal void ValidateDefinition(Activity root, bool isNewType, ITypeProvider typeProvider)
182 if (!this.validateOnCreate)
183 return;
185 ValidationErrorCollection errors = new ValidationErrorCollection();
187 // For validation purposes, create a type provider in the type case if the
188 // host did not push one.
189 if (typeProvider == null)
190 typeProvider = WorkflowRuntime.CreateTypeProvider(root);
192 // Validate that we are purely XAML.
193 if (!isNewType)
195 if (!string.IsNullOrEmpty(root.GetValue(WorkflowMarkupSerializer.XClassProperty) as string))
196 errors.Add(new ValidationError(ExecutionStringManager.XomlWorkflowHasClassName, ErrorNumbers.Error_XomlWorkflowHasClassName));
198 Queue compositeActivities = new Queue();
199 compositeActivities.Enqueue(root);
200 while (compositeActivities.Count > 0)
202 Activity activity = compositeActivities.Dequeue() as Activity;
204 if (activity.GetValue(WorkflowMarkupSerializer.XCodeProperty) != null)
205 errors.Add(new ValidationError(ExecutionStringManager.XomlWorkflowHasCode, ErrorNumbers.Error_XomlWorkflowHasCode));
207 CompositeActivity compositeActivity = activity as CompositeActivity;
208 if (compositeActivity != null)
210 foreach (Activity childActivity in compositeActivity.EnabledActivities)
211 compositeActivities.Enqueue(childActivity);
216 ServiceContainer serviceContainer = new ServiceContainer();
217 serviceContainer.AddService(typeof(ITypeProvider), typeProvider);
219 ValidationManager validationManager = new ValidationManager(serviceContainer);
220 using (WorkflowCompilationContext.CreateScope(validationManager))
222 foreach (Validator validator in validationManager.GetValidators(root.GetType()))
224 foreach (ValidationError error in validator.Validate(validationManager, root))
226 if (!error.UserData.Contains(typeof(Activity)))
227 error.UserData[typeof(Activity)] = root;
229 errors.Add(error);
233 if (errors.HasErrors)
234 throw new WorkflowValidationFailedException(ExecutionStringManager.WorkflowValidationFailure, errors);
237 public void Dispose()
239 xomlFragments.Dispose();
240 workflowTypes.Dispose();
243 private Activity LoadRootActivity(Type workflowType, bool createDefinition, bool initForRuntime)
245 WorkflowLoaderService loader = workflowRuntime.GetService<WorkflowLoaderService>();
246 Activity root = loader.CreateInstance(workflowType);
247 if (root == null)
248 throw new InvalidOperationException(ExecutionStringManager.CannotCreateRootActivity);
249 if (root.GetType() != workflowType)
250 throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, ExecutionStringManager.WorkflowTypeMismatch, workflowType.FullName));
252 if (createDefinition)
253 ValidateDefinition(root, true, workflowRuntime.GetService<ITypeProvider>());
255 if (initForRuntime)
256 ((IDependencyObjectAccessor)root).InitializeDefinitionForRuntime(null);
258 root.SetValue(Activity.WorkflowRuntimeProperty, workflowRuntime);
260 return root;
263 private Activity LoadRootActivity(string xomlText, string rulesText, byte[] xomlHashCode, bool createDefinition, bool initForRuntime)
265 Activity root = null;
266 WorkflowLoaderService loader = workflowRuntime.GetService<WorkflowLoaderService>();
268 using (StringReader xomlTextReader = new StringReader(xomlText))
270 using (XmlReader xomlReader = XmlReader.Create(xomlTextReader))
272 XmlReader rulesReader = null;
273 StringReader rulesTextReader = null;
276 if (!string.IsNullOrEmpty(rulesText))
278 rulesTextReader = new StringReader(rulesText);
279 rulesReader = XmlReader.Create(rulesTextReader);
281 root = loader.CreateInstance(xomlReader, rulesReader);
283 finally
285 if (rulesReader != null)
286 rulesReader.Close();
287 if (rulesTextReader != null)
288 rulesTextReader.Close();
293 if (root == null)
294 throw new InvalidOperationException(ExecutionStringManager.CannotCreateRootActivity);
296 if (createDefinition)
298 ITypeProvider typeProvider = workflowRuntime.GetService<ITypeProvider>();
299 ValidateDefinition(root, false, typeProvider);
302 if (initForRuntime)
303 ((IDependencyObjectAccessor)root).InitializeDefinitionForRuntime(null);
305 // Save the original markup.
306 root.SetValue(Activity.WorkflowXamlMarkupProperty, xomlText);
307 root.SetValue(Activity.WorkflowRulesMarkupProperty, rulesText);
308 root.SetValue(WorkflowDefinitionHashCodeProperty, xomlHashCode);
309 root.SetValue(Activity.WorkflowRuntimeProperty, workflowRuntime);
311 return root;
314 private void CacheOutputParameters(Activity rootActivity)
316 Type workflowType = rootActivity.GetType();
317 List<PropertyInfo> outputParameters = null;
319 this.parametersLock.AcquireWriterLock(-1);
323 if (this.workflowOutParameters.ContainsKey(workflowType))
324 return;
326 // Cache negative and positive cases!
327 outputParameters = new List<PropertyInfo>();
328 this.workflowOutParameters.Add(workflowType, outputParameters);
330 PropertyInfo[] properties = workflowType.GetProperties();
331 foreach (PropertyInfo property in properties)
333 if (!property.CanRead || property.DeclaringType == typeof(DependencyObject) || property.DeclaringType == typeof(Activity) || property.DeclaringType == typeof(CompositeActivity))
334 continue;
336 bool ignoreProperty = false;
337 foreach (DependencyProperty dependencyProperty in rootActivity.MetaDependencyProperties)
339 if (dependencyProperty.Name == property.Name && dependencyProperty.DefaultMetadata.IsMetaProperty)
341 ignoreProperty = true;
342 break;
346 if (!ignoreProperty)
347 outputParameters.Add(property);
350 finally
352 Thread.MemoryBarrier();
353 this.parametersLock.ReleaseLock();
357 private enum CacheType
359 Type = 0,
360 Xoml = 1,
363 private class MruCache : IDisposable
365 Hashtable hashtable;
366 LinkedList<Activity> mruList;
367 int size;
368 int capacity;
369 WorkflowDefinitionDispenser dispenser;
370 CacheType type;
372 internal MruCache(int capacity, WorkflowDefinitionDispenser dispenser, CacheType type)
374 if (type == CacheType.Xoml)
376 this.hashtable = new Hashtable((IEqualityComparer)new DigestComparerWrapper());
378 else
380 this.hashtable = new Hashtable();
382 this.mruList = new LinkedList<Activity>();
383 this.capacity = capacity;
384 this.dispenser = dispenser;
385 this.type = type;
388 private void RemoveFromDictionary(Activity activity)
390 byte[] key = activity.GetValue(WorkflowDefinitionHashCodeProperty) as byte[];
391 if (key != null)
393 this.hashtable.Remove(key);
395 else
397 Type type = activity.GetType();
398 this.hashtable.Remove(type);
402 private void AddToDictionary(LinkedListNode<Activity> node)
404 byte[] key = node.Value.GetValue(WorkflowDefinitionHashCodeProperty) as byte[];
405 if (key != null)
407 this.hashtable.Add(key, node);
409 else
411 Type type = node.Value.GetType();
412 this.hashtable.Add(type, node);
416 internal Activity GetDefinition(byte[] md5Codes)
418 LinkedListNode<Activity> node;
419 node = this.hashtable[md5Codes] as LinkedListNode<Activity>;
420 if (node != null)
422 return node.Value;
424 else
426 return null;
430 internal Activity GetOrGenerateDefinition(Type type, string xomlText, string rulesText, byte[] md5Codes, bool initForRuntime, out bool exist)
432 LinkedListNode<Activity> node;
433 object key;
435 if (type != null)
437 key = type;
439 else
441 key = md5Codes;
445 exist = false;
446 node = this.hashtable[key] as LinkedListNode<Activity>;
448 if (node != null)
450 lock (this.mruList)
452 node = this.hashtable[key] as LinkedListNode<Activity>;
453 if (node != null)
455 exist = true;
456 this.mruList.Remove(node);
457 this.mruList.AddFirst(node);
459 else
461 exist = false;
466 if (!exist)
468 lock (this.hashtable)
470 node = this.hashtable[key] as LinkedListNode<Activity>;
471 if (node != null)
473 exist = true;
474 lock (this.mruList)
476 this.mruList.Remove(node);
477 this.mruList.AddFirst(node);
480 else
482 exist = false;
483 Activity activity;
484 if (type != null)
486 activity = this.dispenser.LoadRootActivity(type, true, initForRuntime);
488 else
490 activity = this.dispenser.LoadRootActivity(xomlText, rulesText, key as byte[], true, initForRuntime);
492 lock (this.mruList)
494 if (this.size < this.capacity)
496 this.size++;
498 else
500 RemoveFromDictionary(this.mruList.Last.Value);
501 this.mruList.RemoveLast();
503 node = new LinkedListNode<Activity>(activity);
504 AddToDictionary(node);
505 this.mruList.AddFirst(node);
511 finally
513 Thread.MemoryBarrier();
515 return node.Value;
518 internal void GetWorkflowDefinitions<K>(out ReadOnlyCollection<K> keys, out ReadOnlyCollection<Activity> values)
520 lock (this.hashtable)
522 if (((typeof(K) == typeof(Type)) && (this.type == CacheType.Type)) || ((typeof(K) == typeof(byte[])) && (this.type == CacheType.Xoml)))
524 List<K> keyList = new List<K>();
525 foreach (K key in this.hashtable.Keys)
527 keyList.Add(key);
529 keys = new ReadOnlyCollection<K>(keyList);
530 List<Activity> list = new List<Activity>();
531 foreach (LinkedListNode<Activity> node in this.hashtable.Values)
533 list.Add(node.Value);
535 values = new ReadOnlyCollection<Activity>(list);
537 else
539 keys = null;
540 values = null;
545 public void Dispose()
547 foreach (LinkedListNode<Activity> node in hashtable.Values)
551 node.Value.Dispose();
553 catch (Exception)//ignore any dispose exception.
560 private class DigestComparerWrapper : IEqualityComparer
562 IEqualityComparer<byte[]> comparer = (IEqualityComparer<byte[]>)new DigestComparer();
563 bool IEqualityComparer.Equals(object object1, object object2)
565 return comparer.Equals((byte[])object1, (byte[])object2);
568 int IEqualityComparer.GetHashCode(object obj)
570 return comparer.GetHashCode((byte[])obj);
576 internal class WorkflowDefinitionLock : IDisposable
578 internal static readonly DependencyProperty WorkflowDefinitionLockObjectProperty = DependencyProperty.RegisterAttached("WorkflowDefinitionLockObject", typeof(object), typeof(WorkflowDefinitionLock), new PropertyMetadata(DependencyPropertyOptions.NonSerialized));
580 internal static object GetWorkflowDefinitionLockObject(DependencyObject dependencyObject)
582 // The Dependency Properties are kept in a Dictionary<>, which is not thread safe between
583 // "getters" and "setters", so lock around the "getter", too.
584 lock (dependencyObject)
586 return dependencyObject.GetValue(WorkflowDefinitionLockObjectProperty);
590 internal static void SetWorkflowDefinitionLockObject(DependencyObject dependencyObject, object value)
592 lock (dependencyObject)
594 if (dependencyObject.GetValue(WorkflowDefinitionLockObjectProperty) == null)
596 dependencyObject.SetValue(WorkflowDefinitionLockObjectProperty, value);
601 private object _syncObj;
603 public WorkflowDefinitionLock(Activity definition)
605 this._syncObj = GetWorkflowDefinitionLockObject(definition);
607 Debug.Assert(this._syncObj != null, "Definition's synchronization object was null. This should always be set.");
609 #pragma warning disable 0618
611 Monitor.Enter(this._syncObj);
612 #pragma warning restore 0618
615 #region IDisposable Members
617 public void Dispose()
619 Monitor.Exit(this._syncObj);
622 #endregion