/// <summary> /// Run a workflow step /// </summary> /// <param name="workflowMessage">Message to pass to the step class</param> /// <param name="retryStepTimes">Times to retry on error</param> /// <param name="workflowStep">The step to run</param> internal void RunFrameworkStep(IWorkflowMessage workflowMessage, int retryStepTimes, ProcessorStep workflowStep, ProcessorJob currentJob, bool isCheckDepends) { bool impersonated = false; if (!string.IsNullOrEmpty(workflowStep.RunAsDomain) && !string.IsNullOrEmpty(workflowStep.RunAsUser) && !string.IsNullOrEmpty(workflowStep.RunAsPassword) && workflowStep.RunMode == FrameworkStepRunMode.STA) impersonated = impersonate.ImpersonateValidUser(workflowStep.RunAsUser, workflowStep.RunAsDomain, workflowStep.RunAsPassword); using (IStep step = StepFactory.GetStep(workflowStep.InvokeClass)) { try { workflowStep.RunStatus = FrameworkStepRunStatus.Loaded; if (isCheckDepends) WaitForDependents(workflowStep, currentJob); workflowStep.StartDate = DateTime.Now; step.RunStep(workflowMessage); workflowStep.EndDate = DateTime.Now; workflowStep.RunStatus = FrameworkStepRunStatus.Complete; workflowStep.ExitMessage = "Complete"; } catch (Exception e) { workflowStep.RunStatus = FrameworkStepRunStatus.CompleteWithErrors; workflowStep.ExitMessage = e.Message; while (e.InnerException != null) { e = e.InnerException; workflowStep.ExitMessage += '|' + e.Message; } switch (workflowStep.OnError) { case OnFrameworkStepError.RetryStep: if (workflowStep.WaitBetweenRetriesMilliseconds > 0) Thread.Sleep(workflowStep.WaitBetweenRetriesMilliseconds); // Try the step again retryStepTimes++; if (retryStepTimes <= workflowStep.RetryTimes) RunFrameworkStep(workflowMessage, retryStepTimes, workflowStep, currentJob, isCheckDepends); break; default: // If this is running from a thread, do not throw the error up if (workflowStep.RunMode == FrameworkStepRunMode.STA) throw e; else Processor.ReportJobError(e, workflowStep, workflowMessage, currentJob); break; } } } if (impersonated) impersonate.UndoImpersonation(); }
/// <summary> /// Load the framework config from workflow.xml /// </summary> internal static void LoadFrameworkConfig(ProcessorJob processorJob) { XmlDocument doc = new XmlDocument(); XmlTextReader reader = new XmlTextReader(EngineConfiguration.FrameworkConfigLocation); doc.Load(reader); reader.Close(); // Load the job XmlNode jobNode = doc.SelectSingleNode("//Job[@Name='" + processorJob.JobName + "']"); processorJob.MessageClass = jobNode.Attributes["MessageClass"].Value; if (jobNode.Attributes["MessageQueue"] != null && !string.IsNullOrEmpty(jobNode.Attributes["MessageQueue"].Value)) processorJob.MessageQueue = jobNode.Attributes["MessageQueue"].Value; if (jobNode.Attributes["ErrorQueue"] != null && !string.IsNullOrEmpty(jobNode.Attributes["ErrorQueue"].Value)) processorJob.ErrorQueue = jobNode.Attributes["ErrorQueue"].Value; if (jobNode.Attributes["PoisonQueue"] != null && !string.IsNullOrEmpty(jobNode.Attributes["PoisonQueue"].Value)) processorJob.PoisonQueue = jobNode.Attributes["PoisonQueue"].Value; if (jobNode.Attributes["CompletedQueue"] != null && !string.IsNullOrEmpty(jobNode.Attributes["CompletedQueue"].Value)) processorJob.CompletedQueue = jobNode.Attributes["CompletedQueue"].Value; if (jobNode.Attributes["NotifyComplete"] != null && !string.IsNullOrEmpty(jobNode.Attributes["NotifyComplete"].Value)) { bool notifyComplete = false; if (bool.TryParse(jobNode.Attributes["NotifyComplete"].Value, out notifyComplete)) processorJob.NotifyComplete = notifyComplete; } if (jobNode.Attributes["MaxRunTimeMilliseconds"] != null && !string.IsNullOrEmpty(jobNode.Attributes["MaxRunTimeMilliseconds"].Value)) { int maxRunTimeMilliseconds = 0; if (int.TryParse(jobNode.Attributes["MaxRunTimeMilliseconds"].Value, out maxRunTimeMilliseconds)) processorJob.MaxRunTimeMilliseconds = maxRunTimeMilliseconds; } if (jobNode.Attributes["MessageQueueType"] != null && !string.IsNullOrEmpty(jobNode.Attributes["MessageQueueType"].Value)) processorJob.MessageQueueType = (MessageQueueType)Enum.Parse(typeof(MessageQueueType), jobNode.Attributes["MessageQueueType"].Value, true); //Load the Queues XmlNode queuesNode = jobNode.SelectSingleNode("//Job[@Name='" + processorJob.JobName + "']/Queues"); if (queuesNode != null) LoadJobQueues(processorJob, queuesNode); //Load the steps XmlNode stepsNode = jobNode.SelectSingleNode("//Job[@Name='" + processorJob.JobName + "']/Steps"); if (stepsNode != null) LoadJobSteps(processorJob, stepsNode); else // For backwards compatibility LoadJobSteps(processorJob, jobNode); }
/// <summary> /// Load the Job's Queues /// </summary> /// <param name="processorJob">The ProcessorJob to load to</param> /// <param name="queuesNode">The XmlNode to load from</param> private static void LoadJobQueues(ProcessorJob processorJob, XmlNode queuesNode) { foreach (XmlNode queueNode in queuesNode.ChildNodes) { ProcessorQueue processorQueue = new ProcessorQueue(); if (!string.IsNullOrEmpty(queueNode.Attributes["MessageQueue"].Value)) //Required processorQueue.MessageQueue = queueNode.Attributes["MessageQueue"].Value; if (!string.IsNullOrEmpty(queueNode.Attributes["ErrorQueue"].Value)) //Required processorQueue.ErrorQueue = queueNode.Attributes["ErrorQueue"].Value; if (!string.IsNullOrEmpty(queueNode.Attributes["PoisonQueue"].Value)) //Required processorQueue.PoisonQueue = queueNode.Attributes["PoisonQueue"].Value; if (!string.IsNullOrEmpty(queueNode.Attributes["CompletedQueue"].Value)) //Required processorQueue.CompletedQueue = queueNode.Attributes["CompletedQueue"].Value; if (!string.IsNullOrEmpty(queueNode.Attributes["MessageQueueType"].Value)) //Required processorQueue.MessageQueueType = (MessageQueueType)Enum.Parse(typeof(MessageQueueType), queueNode.Attributes["MessageQueueType"].Value, true); processorJob.ProcessorQueues.Add(processorQueue); } }
/// <summary> /// Block untill all steps and groups this step depends on have finished /// </summary> /// <param name="workflowStep"></param> private void WaitForDependents(ProcessorStep workflowStep, ProcessorJob currentJob) { if (string.IsNullOrEmpty(workflowStep.DependsOn) && string.IsNullOrEmpty(workflowStep.DependsOnGroup)) return; var result = from ProcessorStep in currentJob.WorkFlowSteps where ProcessorStep.RunStatus != FrameworkStepRunStatus.Complete && (workflowStep.DependsOn.Split(',').Contains(ProcessorStep.StepName) || workflowStep.DependsOnGroup.Split(',').Contains(ProcessorStep.Group)) select ProcessorStep; if (result.Count() > 0) //There are still steps to wait for { Thread.Sleep(100); workflowStep.RunStatusTime += 100; if (workflowStep.RunStatusTime <= workflowStep.WaitForDependsOnMilliseconds) WaitForDependents(workflowStep, currentJob); } }
/// <summary> /// Load the Job's steps /// </summary> /// <param name="processorJob">The ProcessorJob to load to</param> /// <param name="queuesNode">The XmlNode to load from</param> private static void LoadJobSteps(ProcessorJob processorJob, XmlNode stepsNode) { foreach (XmlNode stepNode in stepsNode.ChildNodes) { ProcessorStep workFlowStep = new ProcessorStep(); if (!string.IsNullOrEmpty(stepNode.Attributes["Name"].Value)) //Required workFlowStep.StepName = stepNode.Attributes["Name"].Value; if (!string.IsNullOrEmpty(stepNode.Attributes["Group"].Value)) //Required workFlowStep.Group = stepNode.Attributes["Group"].Value; if (!string.IsNullOrEmpty(stepNode.Attributes["InvokeClass"].Value)) //Required workFlowStep.InvokeClass = stepNode.Attributes["InvokeClass"].Value; if (stepNode.Attributes["OnError"] != null && !string.IsNullOrEmpty(stepNode.Attributes["OnError"].Value)) workFlowStep.OnError = (OnFrameworkStepError)Enum.Parse(typeof(OnFrameworkStepError), stepNode.Attributes["OnError"].Value, true); if (stepNode.Attributes["RetryTimes"] != null && !string.IsNullOrEmpty(stepNode.Attributes["RetryTimes"].Value)) { int retryTimes = 0; int.TryParse(stepNode.Attributes["RetryTimes"].Value, out retryTimes); workFlowStep.RetryTimes = retryTimes; } if (stepNode.Attributes["WaitBetweenRetriesMilliseconds"] != null && !string.IsNullOrEmpty(stepNode.Attributes["WaitBetweenRetriesMilliseconds"].Value)) { int waitBetweenRetriesMilliseconds = 0; int.TryParse(stepNode.Attributes["WaitBetweenRetriesMilliseconds"].Value, out waitBetweenRetriesMilliseconds); workFlowStep.WaitBetweenRetriesMilliseconds = waitBetweenRetriesMilliseconds; } if (stepNode.Attributes["RunMode"] != null && !string.IsNullOrEmpty(stepNode.Attributes["RunMode"].Value)) workFlowStep.RunMode = (FrameworkStepRunMode)Enum.Parse(typeof(FrameworkStepRunMode), stepNode.Attributes["RunMode"].Value, true); if (stepNode.Attributes["DependsOn"] != null && !string.IsNullOrEmpty(stepNode.Attributes["DependsOn"].Value)) workFlowStep.DependsOn = stepNode.Attributes["DependsOn"].Value; if (stepNode.Attributes["DependsOnGroup"] != null && !string.IsNullOrEmpty(stepNode.Attributes["DependsOnGroup"].Value)) workFlowStep.DependsOnGroup = stepNode.Attributes["DependsOnGroup"].Value; if (stepNode.Attributes["WaitForDependsOnMilliseconds"] != null && !string.IsNullOrEmpty(stepNode.Attributes["WaitForDependsOnMilliseconds"].Value)) { int waitForDependsOnMilliseconds = int.MaxValue; if (!string.IsNullOrEmpty(stepNode.Attributes["WaitForDependsOnMilliseconds"].Value)) int.TryParse(stepNode.Attributes["WaitForDependsOnMilliseconds"].Value, out waitForDependsOnMilliseconds); workFlowStep.WaitForDependsOnMilliseconds = waitForDependsOnMilliseconds; } if (stepNode.Attributes["RunAsDomain"] != null && !string.IsNullOrEmpty(stepNode.Attributes["RunAsDomain"].Value)) workFlowStep.RunAsDomain = Utils.GetDecryptedString(stepNode.Attributes["RunAsDomain"].Value); if (stepNode.Attributes["RunAsUser"] != null && !string.IsNullOrEmpty(stepNode.Attributes["RunAsUser"].Value)) workFlowStep.RunAsUser = Utils.GetDecryptedString(stepNode.Attributes["RunAsUser"].Value); if (stepNode.Attributes["RunAsPassword"] != null && !string.IsNullOrEmpty(stepNode.Attributes["RunAsPassword"].Value)) workFlowStep.RunAsPassword = Utils.GetDecryptedString(stepNode.Attributes["RunAsPassword"].Value); processorJob.WorkFlowSteps.Add(workFlowStep); } }
/// <summary> /// Reports a job error /// </summary> /// <param name="e">The exception to report</param> /// <param name="step">The step the exception accrued</param> /// <param name="workflowMessage">The original message pulled from the queue</param> public static void ReportJobError(Exception e, ProcessorStep workflowStep, IWorkflowMessage workflowMessage, ProcessorJob currentJob) { // Push an error message to the error Queue WorkflowErrorMessage errorMessage = new WorkflowErrorMessage() { ExceptionMessage = e.Message, JobName = currentJob.JobName, StepName = workflowStep.StepName }; //if e contains inner exceptions, add those messages while (e.InnerException != null) { //assign e to the inner exception - recursive e = e.InnerException; errorMessage.ExceptionMessage += '|' + e.Message; } FrameworkManager.AddFrameworkError(currentJob, workflowMessage, errorMessage); }
/// <summary> /// This method starts the framework and blocks. /// Call this from an exe or a windows service on a new thread. /// </summary> /// <param name="jobName">The workflow job name</param> public void StartFramework(string jobName) { processorJob = new ProcessorJob(); processorJob.JobName = jobName; pool = new Semaphore(EngineConfiguration.FrameworkMaxThreads, EngineConfiguration.FrameworkMaxThreads); JobsRunning = 0; // Load the config file and start processing jobs WorkflowConfiguration.LoadFrameworkConfig(processorJob); FrameworkOn = true; LoadActiveQueue(); ProcessorQueue.ProcessorQueueChanged += new ProcessorQueue.ProcessorQueueEventHandler(ProcessorQueue_ProcessorQueueChanged); executor.ProcessorJob = processorJob; RunFramework(5, 0, 5); FrameworkOn = false; }
public static void ReportJobComplete(IWorkflowMessage workflowMessage, ProcessorJob currentJob) { if (currentJob.NotifyComplete && currentJob.WorkFlowSteps.Where(s => s.RunStatus == FrameworkStepRunStatus.Complete).Count() == currentJob.WorkFlowSteps.Count()) FrameworkManager.AddFrameworkJobComplete(currentJob, workflowMessage); }
/// <summary> /// Add a job for the framework to process /// </summary> /// <param name="jobName">The name of the job in the framework's workflow file</param> /// <param name="message">A class containing the message data</param> public static void AddFrameworkJob(string jobName, IWorkflowMessage message) { // Add a message to the Queue ProcessorJob processorJob = new ProcessorJob() { JobName = jobName, CreatedDate = DateTime.Now }; WorkflowConfiguration.LoadFrameworkConfig(processorJob); ProcessorQueue processorQueue = GetActiveQueue(processorJob, QueueOperationType.Delivery); MessageQueue workflowQueue = new MessageQueue(processorQueue.MessageQueue); MessageQueueTransaction transaction = new MessageQueueTransaction(); try { if (processorQueue.MessageQueueType == MessageQueueType.Transactional) { transaction.Begin(); workflowQueue.Send(message, jobName, transaction); transaction.Commit(); } else { workflowQueue.Send(message, jobName); } } catch (Exception e) { if (processorQueue.MessageQueueType == MessageQueueType.Transactional && transaction.Status == MessageQueueTransactionStatus.Pending) transaction.Abort(); throw new WorkflowException("Error adding message to Queue", e); } finally { transaction.Dispose(); workflowQueue.Dispose(); } }
internal static void AddFrameworkJobComplete(ProcessorJob processorJob, IWorkflowMessage message) { try { WorkflowConfiguration.LoadFrameworkConfig(processorJob); ProcessorQueue processorQueue = GetActiveQueue(processorJob, QueueOperationType.Delivery); QueueOperationsHandler.HandleComplete(processorQueue, message); } catch (Exception) { } }
/// <summary> /// Add a message containing the error to the error queue and the original message to the poison queue /// </summary> /// <param name="jobName"></param> /// <param name="message"></param> internal static void AddFrameworkError(ProcessorJob processorJob, IWorkflowMessage message, WorkflowErrorMessage errorMessage) { try { WorkflowConfiguration.LoadFrameworkConfig(processorJob); ProcessorQueue processorQueue = GetActiveQueue(processorJob, QueueOperationType.Delivery); MsmqPoisonMessageException error = new MsmqPoisonMessageException() { Source = processorJob.JobName }; error.Data["processorQueue"] = processorQueue; error.Data["message"] = message; error.Data["errorMessage"] = errorMessage; QueueOperationsHandler.HandleError(error); } catch (Exception) { } }
/// <summary> /// Get the currently active Queue to pickup or deliver messages /// </summary> /// <param name="processorJob">The current loaded configuration</param> /// <param name="queueOperationType">The type of operation to execute on the Queue</param> /// <returns>The current active ProcessorQueue for the Operation Type</returns> public static ProcessorQueue GetActiveQueue(ProcessorJob processorJob, QueueOperationType queueOperationType) { // For backward compatibility if the master Queue is present return it if (!string.IsNullOrEmpty(processorJob.MessageQueue) && !string.IsNullOrEmpty(processorJob.ErrorQueue)) return new ProcessorQueue() { MessageQueue = processorJob.MessageQueue, ErrorQueue = processorJob.ErrorQueue, PoisonQueue = processorJob.PoisonQueue, CompletedQueue = processorJob.CompletedQueue, MessageQueueType = processorJob.MessageQueueType }; //If this is a delivery, deliver to the first available queue //If this is a pickup, pick it from the queues in a reverse order // Get the first queue with messages ProcessorQueue queue = new ProcessorQueue(); lock (activeQueueSearchLock) { if (queueOperationType == QueueOperationType.Pickup) processorJob.ProcessorQueues.Reverse(); foreach (ProcessorQueue processorQueue in processorJob.ProcessorQueues) { MessageQueue workflowQueue = new MessageQueue(processorQueue.MessageQueue); queue = processorQueue; try { using (MessageQueue msmq = new MessageQueue(processorQueue.MessageQueue)) { msmq.Peek(new TimeSpan(0)); break; } } catch (MessageQueueException e) { if (e.MessageQueueErrorCode == MessageQueueErrorCode.IOTimeout) { // The queue is is available and empty it can be used for delivery if (queueOperationType == QueueOperationType.Delivery) break; } } catch (Exception) { } } // Reverse the queue list back to the original order if (queueOperationType == QueueOperationType.Pickup) processorJob.ProcessorQueues.Reverse(); } return queue; }