Monday, September 5, 2011

Scheduling workflows in eXpandFrameWork

In this post we are going to extend the functionality of the workflow module. to create a UI that will help us to schedule workflows. Some of you may recall that we looked at using a Delay inside a While activity in Working with CRUD activities – Short Transactions. Recently, DX-Squad member Martin Praxmarer raised an interesting question relating to this topic:

I have the requirement to do a workflow which starts each day on 6 clock - searches for orderdocuments where a specific date is less then X days. I know I will do an do while loop, but workflow definition has 2 options, start when new object, start when criteria, so when do i start this Workflow?

In order to achieve this we will use the WorkFlow demo that ships with our framework. The first thing is to design our custom ScheduledWorkflow persistent object by implementing IWorkflowDefinition. We didn’t derive it from the existing WorkFlowDefinition object because it has properties like TargetObjectType and start up conditions.

public enum StartMode {

OneTime,

Daily,

Weekly

}

[DefaultClassOptions]

[Appearance("WeekDays", "StartMode <> 'Weekly'",

TargetItems = "RecurEveryWeeks;Moday;Tuesday;Wednesday;Thursday;Friday;Saturday;Sunday",

Visibility = ViewItemVisibility.Hide)]

public class ScheduledWorkflow : BaseObject, IWorkflowDefinition {

public ScheduledWorkflow(Session session)

: base(session) {

}

public bool IsActive {

get { return GetPropertyValue<bool>("IsActive"); }

set { SetPropertyValue("IsActive", value); }

}

public bool RuntASAPIfScheduledStartIsMissed {

get { return GetPropertyValue<bool>("RuntASAPIfScheduledStartIsMissed"); }

set { SetPropertyValue("RuntASAPIfScheduledStartIsMissed", value); }

}

[Association]

public XPCollection<ScheduledWorkflowLaunchHistory> LaunchHistoryItems {

get { return GetCollection<ScheduledWorkflowLaunchHistory>("LaunchHistoryItems"); }

}

[ImmediatePostData]

public StartMode StartMode {

get { return GetPropertyValue<StartMode>("StartMode"); }

set { SetPropertyValue("StartMode", value); }

}

public TimeSpan StartTime {

get { return GetPropertyValue<TimeSpan>("StartTime"); }

set { SetPropertyValue("StartTime", value); }

}

[Appearance("RecurEveryDays", "StartMode <> 'Daily'", Visibility = ViewItemVisibility.Hide)]

public int RecurEveryDays {

get { return GetPropertyValue<int>("RecurEveryDays"); }

set { SetPropertyValue("RecurEveryDays", value); }

}

public int RecurEveryWeeks {

get { return GetPropertyValue<int>("RecurEveryWeeks"); }

set { SetPropertyValue("RecurEveryWeeks", value); }

}

public bool Moday {

get { return GetPropertyValue<bool>("Moday"); }

set { SetPropertyValue("Moday", value); }

}

public bool Tuesday {

get { return GetPropertyValue<bool>("Tuesday"); }

set { SetPropertyValue("Tuesday", value); }

}

public bool Wednesday {

get { return GetPropertyValue<bool>("Wednesday"); }

set { SetPropertyValue("Wednesday", value); }

}

public bool Thursday {

get { return GetPropertyValue<bool>("Thursday"); }

set { SetPropertyValue("Thursday", value); }

}

public bool Friday {

get { return GetPropertyValue<bool>("Friday"); }

set { SetPropertyValue("Friday", value); }

}

public bool Saturday {

get { return GetPropertyValue<bool>("Moday"); }

set { SetPropertyValue("Saturday", value); }

}

public bool Sunday {

get { return GetPropertyValue<bool>("Sunday"); }

set { SetPropertyValue("Sunday", value); }

}

#region IWorkflowDefinition Members

public string GetActivityTypeName() {

return GetUniqueId();

}

public IList<IStartWorkflowCondition> GetConditions() {

return new IStartWorkflowCondition[0];

}

public string GetUniqueId() {

if (Session.IsNewObject(this)) {

throw new InvalidOperationException();

}

return "ScheduledWorkflow" + Oid.ToString().ToUpper().Replace("-", "_");

}

[Browsable(false)]

public bool CanCompile {

get { return false; }

}

[Browsable(false)]

public bool CanOpenHost {

get { return IsActive && !string.IsNullOrEmpty(Name); }

}

public string Name {

get { return GetPropertyValue<string>("Name"); }

set { SetPropertyValue("Name", value); }

}

[Size(SizeAttribute.Unlimited)]

public string Xaml {

get { return GetPropertyValue<string>("Xaml"); }

set { SetPropertyValue("Xaml", value); }

}

#endregion

public override void AfterConstruction() {

base.AfterConstruction();

Xaml = DCWorkflowDefinitionLogic.InitialXaml;

}

}

In the above class we have added some scheduled specific properties such as StartMode, StartTime, RecurEveryDays etc. The class has been decorated with the Appearance attribute to control the visibility of the Day properties. This means when StartMode <> 'Weekly these properties will be hidden.

Moreover there is a collection LaunchHistoryItems of ScheduledWorkflowLaunchHistory objects, which will be used later to check if the workflow has been launched.

public class ScheduledWorkflowLaunchHistory : BaseObject {

public ScheduledWorkflowLaunchHistory(Session session) : base(session) {}

public DateTime LaunchedOn {

get { return GetPropertyValue<DateTime>("LaunchedOn"); }

set { SetPropertyValue<DateTime>("LaunchedOn", value); }

}

[Association]

public ScheduledWorkflow Workflow {

get { return GetPropertyValue<ScheduledWorkflow>("Workflow"); }

set { SetPropertyValue<ScheduledWorkflow>("Workflow", value); }

}

}

After designing these classes, we now have all the required input in order to schedule our workflows.

The next step is to load our custom workflows by extending the workflow provider service as shown,

public class ScheduledWorkflowDefinitionProvider : WorkflowDefinitionProvider {

public ScheduledWorkflowDefinitionProvider(Type workflowDefinitionType) : base(workflowDefinitionType) { }

public ScheduledWorkflowDefinitionProvider(Type workflowDefinitionType, IObjectSpaceProvider objectSpaceProvider) : base(workflowDefinitionType, objectSpaceProvider) { }

public override IList<IWorkflowDefinition> GetDefinitions() {

IList<IWorkflowDefinition> result = base.GetDefinitions();

IObjectSpace objectSpace = ObjectSpaceProvider.CreateObjectSpace(); //don't dispose immediately

foreach(ScheduledWorkflow workflow in objectSpace.GetObjects<ScheduledWorkflow>()) {

result.Add(workflow);

}

return result;

}

}

After this we are ready to implement our final service that will schedule our workflows,

public class ScheduledWorkflowStartService : BaseTimerService {

private bool NeedToStartWorkflow(IObjectSpace objectSpace, ScheduledWorkflow workflow) {

if (workflow.StartMode == StartMode.OneTime) {

if (workflow.LaunchHistoryItems.Count == 0) {

return true;

}

} else if (workflow.StartMode == StartMode.Daily) {

var historyItem = objectSpace.FindObject<ScheduledWorkflowLaunchHistory>(CriteriaOperator.Parse("GetDate(LaunchedOn) = ?", DateTime.Today));

if (historyItem == null && DateTime.Now.TimeOfDay > workflow.StartTime) {

return true;

}

} else if (workflow.StartMode == StartMode.Weekly) {

throw new NotImplementedException();

}

return false;

}

public ScheduledWorkflowStartService()

: base(TimeSpan.FromMinutes(1)) {

}

public ScheduledWorkflowStartService(TimeSpan requestsDetectionPeriod) : base(requestsDetectionPeriod) { }

public override void OnTimer() {

using (IObjectSpace objectSpace = ObjectSpaceProvider.CreateObjectSpace()) {

foreach (ScheduledWorkflow workflow in objectSpace.GetObjects<ScheduledWorkflow>(new BinaryOperator("IsActive", true))) {

WorkflowHost host;

if (HostManager.Hosts.TryGetValue(workflow.GetUniqueId(), out host)) {

if (NeedToStartWorkflow(objectSpace, workflow)) {

host.StartWorkflow(new Dictionary<string, object>());

var historyItem = objectSpace.CreateObject<ScheduledWorkflowLaunchHistory>();

historyItem.Workflow = workflow;

historyItem.LaunchedOn = DateTime.Now;

objectSpace.CommitChanges();

}

}

}

}

}

}

Note; the service is not fully implemented, however its very easy to continue from this point. This code will live in the new Xpand.ExpressApp.Workflow module. Now, for the rest of the implementation I would like to ask the help of our community. Anyone that wants to finish it contribute it is most welcome!

Finally we modify the WorkflowServerStarter class and add this service,

private void Start_(string connectionString, string applicationName) {

ServerApplication serverApplication = new ServerApplication();

serverApplication.ApplicationName = applicationName;

serverApplication.Modules.Add(new WorkflowDemoModule());

serverApplication.ConnectionString = connectionString;

serverApplication.Security = new SecurityComplex<User, Role>(

new WorkflowServerAuthentication(new BinaryOperator("UserName", "WorkflowService")));

serverApplication.Setup();

serverApplication.Logon();

IObjectSpaceProvider objectSpaceProvider = serverApplication.ObjectSpaceProvider;

server = new WorkflowServer("http://localhost:46232", objectSpaceProvider, objectSpaceProvider);

server.WorkflowDefinitionProvider = new ScheduledWorkflowDefinitionProvider(typeof(XpoWorkflowDefinition));

//Add the service

server.ServiceProvider.AddService(new ScheduledWorkflowStartService());

We are now ready to go!

4-9-2011 12-50-05 μμ

Related Links
Blog posts
Online documentation
Videos

Subscribe to XAF feed
Subscribe to community feed

DiggIt!

0 comments:

Post a Comment