386 lines
15 KiB
C#
386 lines
15 KiB
C#
|
//----------------------------------------------------------------
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
//----------------------------------------------------------------
|
||
|
|
||
|
namespace System.Activities.Statements
|
||
|
{
|
||
|
using System.Activities;
|
||
|
using System.Activities.Tracking;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Collections.ObjectModel;
|
||
|
using System.Diagnostics;
|
||
|
using System.Reflection;
|
||
|
using System.Runtime.Serialization;
|
||
|
using System.Transactions;
|
||
|
using System.Workflow.Runtime;
|
||
|
using System.Workflow.Runtime.Tracking;
|
||
|
using System.Runtime;
|
||
|
using System.Globalization;
|
||
|
|
||
|
class InteropEnvironment : IDisposable, IServiceProvider
|
||
|
{
|
||
|
static readonly ReadOnlyCollection<IComparable> emptyList = new ReadOnlyCollection<IComparable>(new IComparable[] { });
|
||
|
|
||
|
static MethodInfo getServiceMethod = typeof(NativeActivityContext).GetMethod("GetExtension");
|
||
|
|
||
|
NativeActivityContext nativeActivityContext;
|
||
|
|
||
|
BookmarkCallback bookmarkCallback;
|
||
|
|
||
|
bool disposed;
|
||
|
bool completed;
|
||
|
bool canceled;
|
||
|
|
||
|
InteropExecutor executor;
|
||
|
IEnumerable<IComparable> initialBookmarks;
|
||
|
Exception uncaughtException;
|
||
|
Transaction transaction;
|
||
|
|
||
|
public InteropEnvironment(InteropExecutor interopExecutor, NativeActivityContext nativeActivityContext,
|
||
|
BookmarkCallback bookmarkCallback, Interop activity, Transaction transaction)
|
||
|
{
|
||
|
//setup environment;
|
||
|
this.executor = interopExecutor;
|
||
|
this.nativeActivityContext = nativeActivityContext;
|
||
|
this.Activity = activity;
|
||
|
this.executor.ServiceProvider = this;
|
||
|
this.bookmarkCallback = bookmarkCallback;
|
||
|
this.transaction = transaction;
|
||
|
OnEnter();
|
||
|
}
|
||
|
|
||
|
public Interop Activity { get; set; }
|
||
|
|
||
|
void IDisposable.Dispose()
|
||
|
{
|
||
|
if (!this.disposed)
|
||
|
{
|
||
|
OnExit();
|
||
|
this.disposed = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void Execute(System.Workflow.ComponentModel.Activity definition, NativeActivityContext context)
|
||
|
{
|
||
|
Debug.Assert(!disposed, "Cannot access disposed object");
|
||
|
try
|
||
|
{
|
||
|
this.executor.Initialize(definition, this.Activity.GetInputArgumentValues(context), this.Activity.HasNameCollision);
|
||
|
ProcessExecutionStatus(this.executor.Execute());
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
this.uncaughtException = e;
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void Cancel()
|
||
|
{
|
||
|
Debug.Assert(!disposed, "Cannot access disposed object");
|
||
|
try
|
||
|
{
|
||
|
ProcessExecutionStatus(this.executor.Cancel());
|
||
|
this.canceled = true;
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
this.uncaughtException = e;
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void EnqueueEvent(IComparable queueName, object item)
|
||
|
{
|
||
|
Debug.Assert(!disposed, "Cannot access disposed object");
|
||
|
try
|
||
|
{
|
||
|
ProcessExecutionStatus(this.executor.EnqueueEvent(queueName, item));
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
this.uncaughtException = e;
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void TrackActivityStatusChange(System.Workflow.ComponentModel.Activity activity, int eventCounter)
|
||
|
{
|
||
|
this.nativeActivityContext.Track(
|
||
|
new InteropTrackingRecord(this.Activity.DisplayName,
|
||
|
new ActivityTrackingRecord(
|
||
|
activity.GetType(),
|
||
|
activity.QualifiedName,
|
||
|
activity.ContextGuid,
|
||
|
activity.Parent == null ? Guid.Empty : activity.Parent.ContextGuid,
|
||
|
activity.ExecutionStatus,
|
||
|
DateTime.UtcNow,
|
||
|
eventCounter,
|
||
|
null
|
||
|
)
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
public void TrackData(System.Workflow.ComponentModel.Activity activity, int eventCounter, string key, object data)
|
||
|
{
|
||
|
this.nativeActivityContext.Track(
|
||
|
new InteropTrackingRecord(this.Activity.DisplayName,
|
||
|
new UserTrackingRecord(
|
||
|
activity.GetType(),
|
||
|
activity.QualifiedName,
|
||
|
activity.ContextGuid,
|
||
|
activity.Parent == null ? Guid.Empty : activity.Parent.ContextGuid,
|
||
|
DateTime.UtcNow,
|
||
|
eventCounter,
|
||
|
key,
|
||
|
data
|
||
|
)
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
public void Resume()
|
||
|
{
|
||
|
Debug.Assert(!disposed, "Cannot access disposed object");
|
||
|
try
|
||
|
{
|
||
|
ProcessExecutionStatus(this.executor.Resume());
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
this.uncaughtException = e;
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
object IServiceProvider.GetService(Type serviceType)
|
||
|
{
|
||
|
Debug.Assert(!disposed, "Cannot access disposed object");
|
||
|
MethodInfo genericMethodInfo = getServiceMethod.MakeGenericMethod(serviceType);
|
||
|
return genericMethodInfo.Invoke(this.nativeActivityContext, null);
|
||
|
}
|
||
|
|
||
|
public void Persist()
|
||
|
{
|
||
|
this.Activity.Persist(this.nativeActivityContext);
|
||
|
}
|
||
|
|
||
|
public void CreateTransaction(TransactionOptions transactionOptions)
|
||
|
{
|
||
|
this.Activity.CreateTransaction(this.nativeActivityContext, transactionOptions);
|
||
|
}
|
||
|
|
||
|
public void CommitTransaction()
|
||
|
{
|
||
|
this.Activity.CommitTransaction(this.nativeActivityContext);
|
||
|
}
|
||
|
|
||
|
public void AddResourceManager(VolatileResourceManager resourceManager)
|
||
|
{
|
||
|
this.Activity.AddResourceManager(this.nativeActivityContext, resourceManager);
|
||
|
}
|
||
|
|
||
|
//Called everytime on enter of interopenvironment.
|
||
|
void OnEnter()
|
||
|
{
|
||
|
//Capture Current state of Queues in InteropEnvironment.
|
||
|
this.initialBookmarks = this.executor.Queues;
|
||
|
|
||
|
// This method sets up the ambient transaction for the current thread.
|
||
|
// Since the runtime can execute us on different threads,
|
||
|
// we have to set up the transaction scope everytime we enter the interop environment and clear it when we leave the environment.
|
||
|
this.executor.SetAmbientTransactionAndServiceEnvironment(this.transaction);
|
||
|
}
|
||
|
|
||
|
//Called everytime we leave InteropEnvironment.
|
||
|
void OnExit()
|
||
|
{
|
||
|
if (this.uncaughtException != null)
|
||
|
{
|
||
|
if (WorkflowExecutor.IsIrrecoverableException(this.uncaughtException))
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// This method clears the ambient transaction for the current thread.
|
||
|
// Since the runtime can execute us on different threads,
|
||
|
// we have to set up the transaction scope everytime we enter the interop environment and clear it when we leave the environment.
|
||
|
this.executor.ClearAmbientTransactionAndServiceEnvironment();
|
||
|
|
||
|
//Capture Current state of Queues in InteropEnvironment.
|
||
|
IEnumerable<IComparable> currentBookmarks = this.executor.Queues;
|
||
|
|
||
|
//Set outparameters when completed or faulted.
|
||
|
if (this.completed || this.uncaughtException != null)
|
||
|
{
|
||
|
this.Activity.OnClose(this.nativeActivityContext, this.uncaughtException);
|
||
|
|
||
|
this.Activity.SetOutputArgumentValues(
|
||
|
this.executor.Outputs, this.nativeActivityContext);
|
||
|
|
||
|
this.nativeActivityContext.RemoveAllBookmarks();
|
||
|
this.executor.BookmarkQueueMap.Clear();
|
||
|
|
||
|
if (this.canceled)
|
||
|
{
|
||
|
this.nativeActivityContext.MarkCanceled();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//Find Differentials
|
||
|
IList<IComparable> deletedBookmarks = new List<IComparable>();
|
||
|
foreach (IComparable value in this.initialBookmarks)
|
||
|
{
|
||
|
deletedBookmarks.Add(value);
|
||
|
}
|
||
|
|
||
|
IList<IComparable> newBookmarks = null;
|
||
|
foreach (IComparable value in currentBookmarks)
|
||
|
{
|
||
|
if (!deletedBookmarks.Remove(value))
|
||
|
{
|
||
|
if (newBookmarks == null)
|
||
|
{
|
||
|
newBookmarks = new List<IComparable>();
|
||
|
}
|
||
|
newBookmarks.Add(value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (newBookmarks != null)
|
||
|
{
|
||
|
// Create new Queues as Bookmark.
|
||
|
foreach (IComparable bookmark in newBookmarks)
|
||
|
{
|
||
|
//
|
||
|
Bookmark v2Bookmark = this.nativeActivityContext.CreateBookmark(bookmark.ToString(),
|
||
|
this.bookmarkCallback, BookmarkOptions.MultipleResume);
|
||
|
this.executor.BookmarkQueueMap.Add(v2Bookmark, bookmark);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Delete removed queues.
|
||
|
foreach (IComparable bookmark in deletedBookmarks)
|
||
|
{
|
||
|
this.nativeActivityContext.RemoveBookmark(bookmark.ToString());
|
||
|
List<Bookmark> bookmarksToRemove = new List<Bookmark>();
|
||
|
foreach (KeyValuePair<Bookmark, IComparable> entry in this.executor.BookmarkQueueMap)
|
||
|
{
|
||
|
if (entry.Value == bookmark)
|
||
|
{
|
||
|
bookmarksToRemove.Add(entry.Key);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
foreach (Bookmark bookmarkToRemove in bookmarksToRemove)
|
||
|
{
|
||
|
this.executor.BookmarkQueueMap.Remove(bookmarkToRemove);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ProcessExecutionStatus(System.Workflow.ComponentModel.ActivityExecutionStatus executionStatus)
|
||
|
{
|
||
|
this.completed = (executionStatus == System.Workflow.ComponentModel.ActivityExecutionStatus.Closed);
|
||
|
}
|
||
|
|
||
|
public static class ParameterHelper
|
||
|
{
|
||
|
static readonly Type activityType = typeof(System.Workflow.ComponentModel.Activity);
|
||
|
static readonly Type compositeActivityType = typeof(System.Workflow.ComponentModel.CompositeActivity);
|
||
|
static readonly Type dependencyObjectType = typeof(System.Workflow.ComponentModel.DependencyObject);
|
||
|
static readonly Type activityConditionType = typeof(System.Workflow.ComponentModel.ActivityCondition);
|
||
|
// Interop Property Names
|
||
|
internal const string interopPropertyActivityType = "ActivityType";
|
||
|
internal const string interopPropertyActivityProperties = "ActivityProperties";
|
||
|
internal const string interopPropertyActivityMetaProperties = "ActivityMetaProperties";
|
||
|
// Allowed Meta-Properties
|
||
|
internal const string activityNameMetaProperty = "Name";
|
||
|
|
||
|
|
||
|
//Check property names for any Property/PropertyOut pairs that would conflict with our naming scheme
|
||
|
public static bool HasPropertyNameCollision(IList<PropertyInfo> properties)
|
||
|
{
|
||
|
bool hasNameCollision = false;
|
||
|
HashSet<string> propertyNames = new HashSet<string>();
|
||
|
foreach (PropertyInfo propertyInfo in properties)
|
||
|
{
|
||
|
propertyNames.Add(propertyInfo.Name);
|
||
|
}
|
||
|
|
||
|
if (propertyNames.Contains(interopPropertyActivityType) ||
|
||
|
propertyNames.Contains(interopPropertyActivityProperties) ||
|
||
|
propertyNames.Contains(interopPropertyActivityMetaProperties))
|
||
|
{
|
||
|
hasNameCollision = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
foreach (PropertyInfo propertyInfo in properties)
|
||
|
{
|
||
|
if (propertyNames.Contains(propertyInfo.Name + Interop.OutArgumentSuffix))
|
||
|
{
|
||
|
hasNameCollision = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return hasNameCollision;
|
||
|
}
|
||
|
|
||
|
public static bool IsBindable(PropertyInfo propertyInfo)
|
||
|
{
|
||
|
bool isMetaProperty;
|
||
|
if (!IsBindableOrMetaProperty(propertyInfo, out isMetaProperty))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
return !isMetaProperty;
|
||
|
}
|
||
|
|
||
|
public static bool IsBindableOrMetaProperty(PropertyInfo propertyInfo, out bool isMetaProperty)
|
||
|
{
|
||
|
isMetaProperty = false;
|
||
|
|
||
|
// Validate the declaring type: CompositeActivity and DependencyObject
|
||
|
if (propertyInfo.DeclaringType.Equals(compositeActivityType) ||
|
||
|
propertyInfo.DeclaringType.Equals(dependencyObjectType))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Validate the declaring type: Activity
|
||
|
// We allow certain meta-properties on System.Workflow.ComponentModel.Activity to be visible
|
||
|
if (propertyInfo.DeclaringType.Equals(activityType) &&
|
||
|
!String.Equals(propertyInfo.Name, activityNameMetaProperty, StringComparison.Ordinal))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//Validate the data type
|
||
|
if (activityConditionType.IsAssignableFrom(propertyInfo.PropertyType))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//Validate whether there is DP(Meta) backup
|
||
|
string dependencyPropertyName = propertyInfo.Name;
|
||
|
|
||
|
System.Workflow.ComponentModel.DependencyProperty dependencyProperty = System.Workflow.ComponentModel.DependencyProperty.FromName(dependencyPropertyName,
|
||
|
propertyInfo.DeclaringType);
|
||
|
|
||
|
if (dependencyProperty != null && dependencyProperty.DefaultMetadata.IsMetaProperty)
|
||
|
{
|
||
|
isMetaProperty = true;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|