Refactoring

--HG--
branch : 1.x
extra : rebase_source : 0a3659f94fbdb3fab2185bdf26c7f9838bd4995b
This commit is contained in:
Sebastien Ros
2013-01-29 12:01:44 -08:00
parent 56b656747d
commit e5c9da31c8
28 changed files with 346 additions and 282 deletions

View File

@@ -160,9 +160,9 @@ namespace Orchard.Core.Navigation.Drivers {
}
}
menuShape.MenuName(menuName);
menuShape.ContentItem(menu);
NavigationHelper.PopulateMenu(shapeHelper, menuShape, menuShape, menuItems);
return shapeHelper.Parts_MenuWidget(Menu: menuShape);

View File

@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using Orchard.Localization;
using Orchard.Workflows.Models.Descriptors;
using Orchard.Workflows.Models;
using Orchard.Workflows.Services;
namespace Orchard.Workflows.Activities {
@@ -14,15 +14,15 @@ namespace Orchard.Workflows.Activities {
public Localizer T { get; set; }
public override bool CanExecute(ActivityContext context) {
public override bool CanExecute(WorkflowContext context) {
return true;
}
public override IEnumerable<LocalizedString> GetPossibleOutcomes(ActivityContext context) {
public override IEnumerable<LocalizedString> GetPossibleOutcomes(WorkflowContext context) {
return GetBranches(context).Select(x => T(x));
}
public override IEnumerable<LocalizedString> Execute(ActivityContext context) {
public override IEnumerable<LocalizedString> Execute(WorkflowContext context) {
return GetBranches(context).Select(x => T(x));
}
@@ -44,7 +44,7 @@ namespace Orchard.Workflows.Activities {
}
}
private IEnumerable<string> GetBranches(ActivityContext context) {
private IEnumerable<string> GetBranches(WorkflowContext context) {
if (context.State == null) {
return Enumerable.Empty<string>();
}

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Orchard.ContentManagement;
using Orchard.Localization;
using Orchard.Workflows.Models.Descriptors;
using Orchard.Workflows.Models;
using Orchard.Workflows.Services;
namespace Orchard.Workflows.Activities {
@@ -15,7 +15,7 @@ namespace Orchard.Workflows.Activities {
get { return true; }
}
public override bool CanExecute(ActivityContext context) {
public override bool CanExecute(WorkflowContext context) {
try {
string contentTypesState = context.State.ContentTypes;
@@ -40,11 +40,11 @@ namespace Orchard.Workflows.Activities {
}
}
public override IEnumerable<LocalizedString> GetPossibleOutcomes(ActivityContext context) {
public override IEnumerable<LocalizedString> GetPossibleOutcomes(WorkflowContext context) {
return new[] { T("Done") };
}
public override IEnumerable<LocalizedString> Execute(ActivityContext context) {
public override IEnumerable<LocalizedString> Execute(WorkflowContext context) {
yield return T("Done");
}

View File

@@ -1,6 +1,6 @@
using System.Collections.Generic;
using Orchard.Localization;
using Orchard.Workflows.Models.Descriptors;
using Orchard.Workflows.Models;
using Orchard.Workflows.Services;
namespace Orchard.Workflows.Activities {
@@ -24,11 +24,11 @@ namespace Orchard.Workflows.Activities {
get { return T("Evaluates an expression."); }
}
public override IEnumerable<LocalizedString> GetPossibleOutcomes(ActivityContext context) {
public override IEnumerable<LocalizedString> GetPossibleOutcomes(WorkflowContext context) {
return new[] { T("True"), T("False") };
}
public override IEnumerable<LocalizedString> Execute(ActivityContext context) {
public override IEnumerable<LocalizedString> Execute(WorkflowContext context) {
yield return T("True");
}
}

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Orchard.Localization;
using Orchard.Security;
using Orchard.Workflows.Models.Descriptors;
using Orchard.Workflows.Models;
using Orchard.Workflows.Services;
namespace Orchard.Workflows.Activities {
@@ -33,15 +33,15 @@ namespace Orchard.Workflows.Activities {
get { return "SelectRoles"; }
}
public override IEnumerable<LocalizedString> GetPossibleOutcomes(ActivityContext context) {
public override IEnumerable<LocalizedString> GetPossibleOutcomes(WorkflowContext context) {
return new[] {T("Yes"), T("No")};
}
public override bool CanExecute(ActivityContext context) {
public override bool CanExecute(WorkflowContext context) {
return true;
}
public override IEnumerable<LocalizedString> Execute(ActivityContext context) {
public override IEnumerable<LocalizedString> Execute(WorkflowContext context) {
if (UserIsInRole(context)) {
yield return T("Yes");
@@ -50,7 +50,7 @@ namespace Orchard.Workflows.Activities {
yield return T("No");
}
private bool UserIsInRole(ActivityContext context) {
private bool UserIsInRole(WorkflowContext context) {
// checking if user is in an accepted role
var workContext = _workContextAccessor.GetContext();
@@ -78,7 +78,7 @@ namespace Orchard.Workflows.Activities {
return isInRole;
}
private IEnumerable<string> GetRoles(ActivityContext context) {
private IEnumerable<string> GetRoles(WorkflowContext context) {
if (context.State == null) {
return Enumerable.Empty<string>();
}

View File

@@ -8,7 +8,7 @@ using Orchard.Messaging.Services;
using Orchard.ContentManagement;
using Orchard.Localization;
using Orchard.Security;
using Orchard.Workflows.Models.Descriptors;
using Orchard.Workflows.Models;
using Orchard.Workflows.Services;
namespace Orchard.Workflows.Activities {
@@ -31,7 +31,7 @@ namespace Orchard.Workflows.Activities {
public Localizer T { get; set; }
public override IEnumerable<LocalizedString> GetPossibleOutcomes(ActivityContext context) {
public override IEnumerable<LocalizedString> GetPossibleOutcomes(WorkflowContext context) {
return new[] { T("Sent") };
}
@@ -54,7 +54,7 @@ namespace Orchard.Workflows.Activities {
get { return T("Sends an e-mail to a specific user."); }
}
public override IEnumerable<LocalizedString> Execute(ActivityContext context) {
public override IEnumerable<LocalizedString> Execute(WorkflowContext context) {
string recipient = context.State.Recipient;
var properties = new Dictionary<string, string> {

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using Orchard.Localization;
using Orchard.Tokens;
using Orchard.UI.Notify;
using Orchard.Workflows.Models.Descriptors;
using Orchard.Workflows.Models;
using Orchard.Workflows.Services;
namespace Orchard.Workflows.Activities {
@@ -35,11 +35,11 @@ namespace Orchard.Workflows.Activities {
get { return "ActivityNotify"; }
}
public override IEnumerable<LocalizedString> GetPossibleOutcomes(ActivityContext context) {
public override IEnumerable<LocalizedString> GetPossibleOutcomes(WorkflowContext context) {
yield return T("Done");
}
public override IEnumerable<LocalizedString> Execute(ActivityContext context) {
public override IEnumerable<LocalizedString> Execute(WorkflowContext context) {
string notification = context.State.Notification;
string message = context.State.Message;

View File

@@ -1,7 +1,7 @@
using System.Collections.Generic;
using Orchard.ContentManagement;
using Orchard.Localization;
using Orchard.Workflows.Models.Descriptors;
using Orchard.Workflows.Models;
using Orchard.Workflows.Services;
namespace Orchard.Workflows.Activities {
@@ -14,15 +14,15 @@ namespace Orchard.Workflows.Activities {
public Localizer T { get; set; }
public override bool CanExecute(ActivityContext context) {
public override bool CanExecute(WorkflowContext context) {
return true;
}
public override IEnumerable<LocalizedString> GetPossibleOutcomes(ActivityContext context) {
public override IEnumerable<LocalizedString> GetPossibleOutcomes(WorkflowContext context) {
return new[] { T("Published") };
}
public override IEnumerable<LocalizedString> Execute(ActivityContext context) {
public override IEnumerable<LocalizedString> Execute(WorkflowContext context) {
_contentManager.Publish(context.Content.ContentItem);
yield return T("Published");
}

View File

@@ -8,19 +8,14 @@ using Orchard.Localization;
using Orchard.Services;
using Orchard.Tasks;
using Orchard.Workflows.Models;
using Orchard.Workflows.Models.Descriptors;
using Orchard.Workflows.Services;
namespace Orchard.Workflows.Activities {
public class TimerActivity : Event {
private readonly IClock _clock;
private readonly IWorkContextAccessor _workContextAccessor;
public TimerActivity(
IClock clock,
IWorkContextAccessor workContextAccessor) {
public TimerActivity(IClock clock) {
_clock = clock;
_workContextAccessor = workContextAccessor;
T = NullLocalizer.Instance;
}
@@ -42,26 +37,26 @@ namespace Orchard.Workflows.Activities {
get { return "ActivityTimer"; }
}
public override IEnumerable<LocalizedString> GetPossibleOutcomes(ActivityContext context) {
public override IEnumerable<LocalizedString> GetPossibleOutcomes(WorkflowContext context) {
yield return T("Done");
}
public override bool CanExecute(ActivityContext context) {
public override bool CanExecute(WorkflowContext context) {
return _clock.UtcNow > When(context);
}
public override void Touch(dynamic workflowState) {
workflowState.TimerActivity_StartedUtc = _clock.UtcNow;
public override void OnWorkflowStarted(WorkflowContext context) {
context.WorkflowState.TimerActivity_StartedUtc = _clock.UtcNow;
}
public override IEnumerable<LocalizedString> Execute(ActivityContext context) {
public override IEnumerable<LocalizedString> Execute(WorkflowContext context) {
if(_clock.UtcNow > When(context)) {
yield return T("Done");
}
}
public static DateTime When(ActivityContext context) {
public static DateTime When(WorkflowContext context) {
try {
int amount = context.State.Amount;
string type = context.State.Unity;
@@ -122,10 +117,10 @@ namespace Orchard.Workflows.Activities {
var contentItem = _contentManager.Get(x.ContentItemRecord.Id, VersionOptions.Latest);
var state = FormParametersHelper.FromJsonString(x.ActivityRecord.State);
var workflowState = FormParametersHelper.FromJsonString(x.WorkflowRecord.State);
return _clock.UtcNow > TimerActivity.When(new ActivityContext {
return _clock.UtcNow > TimerActivity.When(new WorkflowContext {
State = state,
WorkflowState = workflowState,
Content = contentItem
Content = contentItem,
});
});

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Orchard.Localization;
using Orchard.Security;
using Orchard.Workflows.Models.Descriptors;
using Orchard.Workflows.Models;
using Orchard.Workflows.Services;
namespace Orchard.Workflows.Activities {
@@ -33,24 +33,24 @@ namespace Orchard.Workflows.Activities {
get { return "ActivityUserTask"; }
}
public override IEnumerable<LocalizedString> GetPossibleOutcomes(ActivityContext context) {
public override IEnumerable<LocalizedString> GetPossibleOutcomes(WorkflowContext context) {
foreach (var action in GetActions(context)) {
yield return T(action);
}
}
public override bool CanExecute(ActivityContext context) {
public override bool CanExecute(WorkflowContext context) {
return ActionIsValid(context) && UserIsInRole(context);
}
public override IEnumerable<LocalizedString> Execute(ActivityContext context) {
public override IEnumerable<LocalizedString> Execute(WorkflowContext context) {
if (ActionIsValid(context) && UserIsInRole(context)) {
yield return T(context.Tokens["UserTask.Action"].ToString());
}
}
private bool UserIsInRole(ActivityContext context) {
private bool UserIsInRole(WorkflowContext context) {
// checking if user is in an accepted role
var workContext = _workContextAccessor.GetContext();
@@ -78,7 +78,7 @@ namespace Orchard.Workflows.Activities {
return isInRole;
}
private bool ActionIsValid(ActivityContext context) {
private bool ActionIsValid(WorkflowContext context) {
// checking if user has triggered an accepted action
@@ -91,7 +91,7 @@ namespace Orchard.Workflows.Activities {
return isValidAction;
}
private IEnumerable<string> GetRoles(ActivityContext context) {
private IEnumerable<string> GetRoles(WorkflowContext context) {
if (context.State == null) {
return Enumerable.Empty<string>();
}
@@ -105,7 +105,7 @@ namespace Orchard.Workflows.Activities {
return roles.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToList();
}
private IEnumerable<string> GetActions(ActivityContext context) {
private IEnumerable<string> GetActions(WorkflowContext context) {
if (context.State == null) {
return Enumerable.Empty<string>();

View File

@@ -0,0 +1,6 @@
namespace Orchard.Workflows.Models {
public enum ActivityState {
Excuted,
Executing
}
}

View File

@@ -0,0 +1,9 @@
namespace Orchard.Workflows.Models {
public class CancellationToken {
public void Cancel() {
IsCancelled = true;
}
public bool IsCancelled { get; private set; }
}
}

View File

@@ -1,19 +0,0 @@
using System.Collections.Generic;
using Orchard.ContentManagement;
namespace Orchard.Workflows.Models.Descriptors {
public class ActivityContext {
public ActivityContext() {
Tokens = new Dictionary<string, object>();
}
/// <summary>
/// If set, represents the subject of the current workflow
/// </summary>
public IContent Content { get; set; }
public IDictionary<string, object> Tokens { get; set; }
public dynamic State { get; set; }
public dynamic WorkflowState { get; set; }
}
}

View File

@@ -1,14 +0,0 @@
using System;
using Orchard.Localization;
namespace Orchard.Workflows.Models.Descriptors {
public class ActivityDescriptor {
public string Category { get; set; }
public string Type { get; set; }
public LocalizedString Name { get; set; }
public LocalizedString Description { get; set; }
public Func<ActivityContext, bool> Action { get; set; }
public string Form { get; set; }
public Func<ActivityContext, LocalizedString> Display { get; set; }
}
}

View File

@@ -1,33 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using Orchard.Localization;
namespace Orchard.Workflows.Models.Descriptors {
public class DescribeActivityContext {
private readonly Dictionary<string, DescribeActivityFor> _describes = new Dictionary<string, DescribeActivityFor>();
public IEnumerable<TypeDescriptor<ActivityDescriptor>> Describe() {
return _describes.Select(kp => new TypeDescriptor<ActivityDescriptor> {
Category = kp.Key,
Name = kp.Value.Name,
Description = kp.Value.Description,
Descriptors = kp.Value.Types
});
}
public DescribeActivityFor For(string category) {
return For(category, null, null);
}
public DescribeActivityFor For(string category, LocalizedString name, LocalizedString description) {
DescribeActivityFor describeFor;
if (!_describes.TryGetValue(category, out describeFor)) {
describeFor = new DescribeActivityFor(category, name, description);
_describes[category] = describeFor;
}
return describeFor;
}
}
}

View File

@@ -1,25 +0,0 @@
using System;
using System.Collections.Generic;
using Orchard.Localization;
namespace Orchard.Workflows.Models.Descriptors {
public class DescribeActivityFor {
private readonly string _category;
public DescribeActivityFor(string category, LocalizedString name, LocalizedString description) {
Types = new List<ActivityDescriptor>();
_category = category;
Name = name;
Description = description;
}
public LocalizedString Name { get; private set; }
public LocalizedString Description { get; private set; }
public List<ActivityDescriptor> Types { get; private set; }
public DescribeActivityFor Element(string type, LocalizedString name, LocalizedString description, Func<ActivityContext, bool> action, Func<ActivityContext, LocalizedString> display, string form = null) {
Types.Add(new ActivityDescriptor { Type = type, Name = name, Description = description, Category = _category, Action = action, Display = display, Form = form });
return this;
}
}
}

View File

@@ -1,11 +0,0 @@
using System.Collections.Generic;
using Orchard.Localization;
namespace Orchard.Workflows.Models.Descriptors {
public class TypeDescriptor<T> {
public string Category { get; set; }
public LocalizedString Name { get; set; }
public LocalizedString Description { get; set; }
public IEnumerable<T> Descriptors { get; set; }
}
}

View File

@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Orchard.ContentManagement;
using Orchard.Localization;
using Orchard.Workflows.Services;
namespace Orchard.Workflows.Models {
public class WorkflowContext {
public WorkflowContext() {
Tokens = new Dictionary<string, object>();
}
/// <summary>
/// If set, represents the subject of the current workflow
/// </summary>
public IContent Content { get; set; }
public IDictionary<string, object> Tokens { get; set; }
public dynamic State { get; set; }
public dynamic WorkflowState { get; set; }
public IActivity Activity { get; set; }
public ActivityRecord Record { get; set; }
/// <summary>
/// Schedules a specific
/// </summary>
public Action<ActivityRecord> Schedule { get; set; }
public IEnumerable<TransitionRecord> GetInboundTransitions(ActivityRecord activityRecord) {
return Record.WorkflowDefinitionRecord
.TransitionRecords
.Where(transition =>
transition.DestinationActivityRecord == activityRecord
).ToArray();
}
public IEnumerable<TransitionRecord> GetOutboundTransitions(ActivityRecord activityRecord) {
return Record.WorkflowDefinitionRecord
.TransitionRecords
.Where(transition =>
transition.SourceActivityRecord == activityRecord
).ToArray();
}
public IEnumerable<TransitionRecord> GetOutboundTransitions(ActivityRecord activityRecord, LocalizedString outcome) {
return Record.WorkflowDefinitionRecord
.TransitionRecords
.Where(transition =>
transition.SourceActivityRecord == activityRecord
&& transition.SourceEndpoint == outcome.TextHint
).ToArray();
}
}
}

View File

@@ -133,14 +133,12 @@
<Compile Include="Drivers\WorkflowDriver.cs" />
<Compile Include="Handlers\ContentHandler.cs" />
<Compile Include="Handlers\WorkflowHandler.cs" />
<Compile Include="Models\ActivityState.cs" />
<Compile Include="Models\AwaitingActivityRecord.cs" />
<Compile Include="Models\Descriptors\ActivityContext.cs" />
<Compile Include="Models\Descriptors\ActivityDescriptor.cs" />
<Compile Include="Models\Descriptors\DescribeActivityContext.cs" />
<Compile Include="Models\Descriptors\DescribeActivityFor.cs" />
<Compile Include="Models\CancellationToken.cs" />
<Compile Include="Models\WorkflowContext.cs" />
<Compile Include="Models\ActivityRecord.cs" />
<Compile Include="Models\TransitionRecord.cs" />
<Compile Include="Models\Descriptors\TypeDescriptor.cs" />
<Compile Include="Models\WorkflowRecord.cs" />
<Compile Include="Migrations.cs" />
<Compile Include="Models\WorkflowDefinitionRecord.cs" />
@@ -149,10 +147,9 @@
<Compile Include="Forms\UserTaskForms.cs" />
<Compile Include="Forms\NotificationActivityForms.cs" />
<Compile Include="ResourceManifest.cs" />
<Compile Include="Services\BaseActivity.cs" />
<Compile Include="Services\BlockingActivity.cs" />
<Compile Include="Services\Task.cs" />
<Compile Include="Services\Event.cs" />
<Compile Include="Services\IActivity.cs" />
<Compile Include="Services\IActivityProvider.cs" />
<Compile Include="Services\ActivitiesManager.cs" />
<Compile Include="Services\IActivitiesManager.cs" />
<Compile Include="Services\IWorkflowManager.cs" />

View File

@@ -1,35 +0,0 @@
using System.Collections.Generic;
using Orchard.Localization;
using Orchard.Workflows.Models.Descriptors;
namespace Orchard.Workflows.Services {
public abstract class Task : IActivity {
public abstract string Name { get; }
public abstract LocalizedString Category { get; }
public abstract LocalizedString Description { get; }
public virtual bool IsEvent {
get { return false; }
}
public bool CanStartWorkflow { get { return false; } }
public virtual string Form {
get { return null; }
}
public abstract IEnumerable<LocalizedString> GetPossibleOutcomes(ActivityContext context);
public virtual bool CanExecute(ActivityContext context) {
return true;
}
public abstract IEnumerable<LocalizedString> Execute(ActivityContext context);
public virtual void Touch(dynamic state) {
}
}
}

View File

@@ -1,36 +0,0 @@
using System.Collections.Generic;
using Orchard.Localization;
using Orchard.Workflows.Models.Descriptors;
namespace Orchard.Workflows.Services {
public abstract class Event : IActivity {
public abstract string Name { get; }
public abstract LocalizedString Category { get; }
public abstract LocalizedString Description { get; }
public virtual bool IsEvent {
get { return true; }
}
public virtual string Form {
get { return null; }
}
public virtual bool CanStartWorkflow {
get { return false; }
}
public abstract IEnumerable<LocalizedString> GetPossibleOutcomes(ActivityContext context);
public virtual bool CanExecute(ActivityContext context) {
return true;
}
public abstract IEnumerable<LocalizedString> Execute(ActivityContext context);
public virtual void Touch(dynamic workflowState) {
}
}
}

View File

@@ -0,0 +1,50 @@
using System.Collections.Generic;
using Orchard.Localization;
using Orchard.Workflows.Models;
namespace Orchard.Workflows.Services {
public abstract class Event : IActivity {
public abstract string Name { get; }
public abstract LocalizedString Category { get; }
public abstract LocalizedString Description { get; }
public virtual bool IsEvent {
get { return true; }
}
public virtual string Form {
get { return null; }
}
public virtual bool CanStartWorkflow {
get { return false; }
}
public abstract IEnumerable<LocalizedString> GetPossibleOutcomes(WorkflowContext context);
public virtual bool CanExecute(WorkflowContext context) {
return true;
}
public abstract IEnumerable<LocalizedString> Execute(WorkflowContext context);
public virtual void OnWorkflowStarting(WorkflowContext context, CancellationToken cancellationToken) {
}
public virtual void OnWorkflowStarted(WorkflowContext context) {
}
public virtual void OnWorkflowResuming(WorkflowContext context, CancellationToken cancellationToken) {
}
public virtual void OnWorkflowResumed(WorkflowContext context) {
}
public virtual void OnActivityExecuting(WorkflowContext context, CancellationToken cancellationToken) {
}
public virtual void OnActivityExecuted(WorkflowContext context) {
}
}
}

View File

@@ -1,6 +1,6 @@
using System.Collections.Generic;
using Orchard.Localization;
using Orchard.Workflows.Models.Descriptors;
using Orchard.Workflows.Models;
namespace Orchard.Workflows.Services {
public interface IActivity : IDependency {
@@ -15,23 +15,49 @@ namespace Orchard.Workflows.Services {
/// <summary>
/// List of possible outcomes when the activity is executed.
/// </summary>
IEnumerable<LocalizedString> GetPossibleOutcomes(ActivityContext context);
IEnumerable<LocalizedString> GetPossibleOutcomes(WorkflowContext context);
/// <summary>
/// Whether the activity can transition to the next outcome. Can prevent the activity from being transitioned
/// because a condition is not valid.
/// </summary>
bool CanExecute(ActivityContext context);
bool CanExecute(WorkflowContext context);
/// <summary>
/// Executes the current activity
/// </summary>
/// <returns>The names of the resulting outcomes.</returns>
IEnumerable<LocalizedString> Execute(ActivityContext context);
IEnumerable<LocalizedString> Execute(WorkflowContext context);
/// <summary>
/// Called on blocking activities when they are reached
/// Called on each activity when a workflow is about to start
/// </summary>
void Touch(dynamic workflowState);
void OnWorkflowStarting(WorkflowContext context, CancellationToken cancellationToken);
/// <summary>
/// Called on each activity when a workflow has started
/// </summary>
void OnWorkflowStarted(WorkflowContext context);
/// <summary>
/// Called on each activity when a workflow is about to be resumed
/// </summary>
void OnWorkflowResuming(WorkflowContext context, CancellationToken cancellationToken);
/// <summary>
/// Called on each activity when a workflow is resumed
/// </summary>
void OnWorkflowResumed(WorkflowContext context);
/// <summary>
/// Called on each activity when an activity is about to be executed
/// </summary>
void OnActivityExecuting(WorkflowContext context, CancellationToken cancellationToken);
/// <summary>
/// Called on each activity when an activity has been executed
/// </summary>
void OnActivityExecuted(WorkflowContext context);
}
}

View File

@@ -1,8 +0,0 @@
using Orchard.Events;
using Orchard.Workflows.Models.Descriptors;
namespace Orchard.Workflows.Services {
public interface IActivityProvider : IEventHandler {
void Describe(DescribeActivityContext describe);
}
}

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic;
using Orchard.ContentManagement;
using Orchard.Events;
using Orchard.Workflows.Models;
namespace Orchard.Workflows.Services {
public interface IWorkflowManager : IEventHandler {
@@ -14,8 +13,6 @@ namespace Orchard.Workflows.Services {
/// <param name="target">The <see cref="IContent"/> content item the event is related to</param>
/// <param name="tokensContext">An object containing the tokens context</param>
void TriggerEvent(string name, IContent target, Func<Dictionary<string, object>> tokensContext);
IEnumerable<ActivityRecord> ExecuteWorkflow(WorkflowDefinitionRecord workflowDefinitionRecord, ActivityRecord activityRecord, IContent target, Dictionary<string, object> tokens, dynamic workflowState);
}
}

View File

@@ -0,0 +1,48 @@
using System.Collections.Generic;
using Orchard.Localization;
using Orchard.Workflows.Models;
namespace Orchard.Workflows.Services {
public abstract class Task : IActivity {
public abstract string Name { get; }
public abstract LocalizedString Category { get; }
public abstract LocalizedString Description { get; }
public virtual bool IsEvent {
get { return false; }
}
public bool CanStartWorkflow { get { return false; } }
public virtual string Form {
get { return null; }
}
public abstract IEnumerable<LocalizedString> GetPossibleOutcomes(WorkflowContext context);
public virtual bool CanExecute(WorkflowContext context) {
return true;
}
public abstract IEnumerable<LocalizedString> Execute(WorkflowContext context);
public virtual void OnWorkflowStarting(WorkflowContext context, CancellationToken cancellationToken) {
}
public virtual void OnWorkflowStarted(WorkflowContext context) {
}
public virtual void OnWorkflowResuming(WorkflowContext context, CancellationToken cancellationToken) {
}
public virtual void OnWorkflowResumed(WorkflowContext context) {
}
public virtual void OnActivityExecuting(WorkflowContext context, CancellationToken cancellationToken) {
}
public virtual void OnActivityExecuted(WorkflowContext context) {
}
}
}

View File

@@ -10,7 +10,6 @@ using Orchard.Workflows.Models;
using Orchard.Data;
using Orchard.Localization;
using Orchard.Tokens;
using Orchard.Workflows.Models.Descriptors;
namespace Orchard.Workflows.Services {
public class WorkflowManager : IWorkflowManager {
@@ -83,7 +82,12 @@ namespace Orchard.Workflows.Services {
var serialized = String.IsNullOrEmpty(tokenized) ? "{}" : JsonConvert.SerializeXNode(XElement.Parse(tokenized));
var state = FormParametersHelper.FromJsonString(serialized);
var workflowState = FormParametersHelper.FromJsonString(a.WorkflowRecord.State);
var context = new ActivityContext { Tokens = tokens, State = state.Root, WorkflowState = workflowState, Content = target };
var context = new WorkflowContext {
Tokens = tokens,
State = state.Root,
WorkflowState = workflowState,
Content = target,
};
// check the condition
try {
@@ -102,7 +106,12 @@ namespace Orchard.Workflows.Services {
var serialized = String.IsNullOrEmpty(tokenized) ? "{}" : JsonConvert.SerializeXNode(XElement.Parse(tokenized));
var state = FormParametersHelper.FromJsonString(serialized);
var workflowState = FormParametersHelper.FromJsonString("{}");
var context = new ActivityContext { Tokens = tokens, State = state.Root, WorkflowState = workflowState, Content = target };
var context = new WorkflowContext {
Tokens = tokens,
State = state.Root,
WorkflowState = workflowState,
Content = target,
};
// check the condition
try {
@@ -125,29 +134,59 @@ namespace Orchard.Workflows.Services {
.ToList();
// resume halted workflows
foreach (var a in awaitingActivities) {
ResumeWorkflow(a, target, tokens);
foreach (var awaitingActivityRecord in awaitingActivities) {
var context = new WorkflowContext {
Activity = _activitiesManager.GetActivityByName(awaitingActivityRecord.ActivityRecord.Name),
Content = target,
Record = awaitingActivityRecord.ActivityRecord,
Tokens = tokens,
State = FormParametersHelper.FromJsonString(awaitingActivityRecord.ActivityRecord.State),
WorkflowState = FormParametersHelper.FromJsonString(awaitingActivityRecord.WorkflowRecord.State)
};
ResumeWorkflow(awaitingActivityRecord, context);
}
// start new workflows
foreach (var a in startedWorkflows) {
StartWorkflow(a, target, tokens);
foreach (var activityRecord in startedWorkflows) {
var context = new WorkflowContext {
Activity = _activitiesManager.GetActivityByName(activityRecord.Name),
Content = target,
Record = activityRecord,
Tokens = tokens,
State = FormParametersHelper.FromJsonString(activityRecord.State),
WorkflowState = FormParametersHelper.FromJsonString("{}")
};
StartWorkflow(context);
}
}
private void StartWorkflow(ActivityRecord activityRecord, IContent target, Dictionary<string, object> tokens) {
var workflowState = FormParametersHelper.FromJsonString("{}");
IEnumerable<ActivityRecord> blockedOn = ExecuteWorkflow(activityRecord.WorkflowDefinitionRecord, activityRecord, target, tokens, workflowState);
private void StartWorkflow(WorkflowContext context) {
// signal every activity that the workflow is about to start
var cancellationToken = new CancellationToken();
InvokeActivities(context.Record.WorkflowDefinitionRecord, context, ctx => ctx.Activity.OnWorkflowStarting(ctx, cancellationToken));
if (cancellationToken.IsCancelled) {
// workflow is aborted
return;
}
// signal every activity that the workflow is has started
InvokeActivities(context.Record.WorkflowDefinitionRecord, context, ctx => ctx.Activity.OnWorkflowStarted(ctx));
var blockedOn = ExecuteWorkflow(context).ToList();
// is the workflow halted on a blocking activity ?
if (blockedOn == null || !blockedOn.Any()) {
if (!blockedOn.Any()) {
// no, nothing to do
}
else {
// workflow halted, create a workflow state
var workflow = new WorkflowRecord {
WorkflowDefinitionRecord = activityRecord.WorkflowDefinitionRecord,
State = FormParametersHelper.ToJsonString(workflowState)
WorkflowDefinitionRecord = context.Record.WorkflowDefinitionRecord,
State = FormParametersHelper.ToJsonString(context.WorkflowState)
};
_workflowRepository.Create(workflow);
@@ -155,54 +194,66 @@ namespace Orchard.Workflows.Services {
foreach (var blocking in blockedOn) {
workflow.AwaitingActivities.Add(new AwaitingActivityRecord {
ActivityRecord = blocking,
ContentItemRecord = target.ContentItem.Record
ContentItemRecord = context.Content.ContentItem.Record
});
}
}
}
private void ResumeWorkflow(AwaitingActivityRecord awaitingActivityRecord, IContent target, Dictionary<string, object> tokens) {
var workflowState = FormParametersHelper.FromJsonString(awaitingActivityRecord.WorkflowRecord.State);
IEnumerable<ActivityRecord> blockedOn = ExecuteWorkflow(awaitingActivityRecord.WorkflowRecord.WorkflowDefinitionRecord, awaitingActivityRecord.ActivityRecord, target, tokens, workflowState);
private void ResumeWorkflow(AwaitingActivityRecord awaitingActivityRecord, WorkflowContext context) {
// signal every activity that the workflow is about to be resumed
var cancellationToken = new CancellationToken();
InvokeActivities(context.Record.WorkflowDefinitionRecord, context, ctx => ctx.Activity.OnWorkflowResuming(ctx, cancellationToken));
if (cancellationToken.IsCancelled) {
// workflow is aborted
return;
}
// signal every activity that the workflow is resumed
InvokeActivities(context.Record.WorkflowDefinitionRecord, context, ctx => ctx.Activity.OnWorkflowResumed(ctx));
var blockedOn = ExecuteWorkflow(context).ToList();
// is the workflow halted on a blocking activity ?
if (blockedOn == null || !blockedOn.Any()) {
if (!blockedOn.Any()) {
// no, delete the workflow
_workflowRepository.Delete(awaitingActivityRecord.WorkflowRecord);
}
else {
// remove all previous awaiting activities
var workflow = awaitingActivityRecord.WorkflowRecord;
workflow.State = FormParametersHelper.ToJsonString(workflowState);
workflow.State = FormParametersHelper.ToJsonString(context.WorkflowState);
workflow.AwaitingActivities.Clear();
// add the new ones
foreach (var blocking in blockedOn) {
workflow.AwaitingActivities.Add(new AwaitingActivityRecord {
ActivityRecord = blocking,
ContentItemRecord = target.ContentItem.Record
ContentItemRecord = context.Content.ContentItem.Record
});
}
}
}
public IEnumerable<ActivityRecord> ExecuteWorkflow(WorkflowDefinitionRecord workflowDefinitionRecord, ActivityRecord activityRecord, IContent target, Dictionary<string, object> tokens, dynamic workflowState) {
public IEnumerable<ActivityRecord> ExecuteWorkflow(WorkflowContext context) {
var firstPass = true;
var pending = new Stack<ActivityRecord>();
pending.Push(activityRecord);
var scheduled = new Stack<ActivityRecord>();
var activityRecord = context.Record;
scheduled.Push(activityRecord);
var blocking = new List<ActivityRecord>();
while (pending.Any()) {
while (scheduled.Any()) {
activityRecord = pending.Pop();
activityRecord = scheduled.Pop();
// while there is an activity to process
var activity = _activitiesManager.GetActivityByName(activityRecord.Name);
if (!firstPass){
if(activity.IsEvent) {
activity.Touch(workflowState);
blocking.Add(activityRecord);
continue;
}
@@ -211,17 +262,15 @@ namespace Orchard.Workflows.Services {
firstPass = false;
}
var state = FormParametersHelper.FromJsonString(activityRecord.State);
var activityContext = new ActivityContext { Tokens = tokens, State = state, WorkflowState = workflowState, Content = target };
var outcomes = activity.Execute(activityContext);
var outcomes = activity.Execute(context);
if (outcomes != null) {
foreach (var outcome in outcomes) {
// look for next activity in the graph
var transition = workflowDefinitionRecord.TransitionRecords.FirstOrDefault(x => x.SourceActivityRecord == activityRecord && x.SourceEndpoint == outcome.TextHint);
var transition = context.Record.WorkflowDefinitionRecord.TransitionRecords.FirstOrDefault(x => x.SourceActivityRecord == activityRecord && x.SourceEndpoint == outcome.TextHint);
if (transition != null) {
pending.Push(transition.DestinationActivityRecord);
scheduled.Push(transition.DestinationActivityRecord);
}
}
}
@@ -230,5 +279,16 @@ namespace Orchard.Workflows.Services {
// apply Distinct() as two paths could block on the same activity
return blocking.Distinct();
}
/// <summary>
/// Executes a specific action on all the activities of a workflow, using a specific context
/// </summary>
private void InvokeActivities(WorkflowDefinitionRecord workflowDefinitionRecord, WorkflowContext context, Action<WorkflowContext> action) {
foreach (var item in workflowDefinitionRecord.ActivityRecords) {
context.Activity = _activitiesManager.GetActivityByName(item.Name);
context.Record = item;
action(context);
}
}
}
}

View File

@@ -1,6 +1,7 @@
@using Orchard.DisplayManagement
@using Orchard.UI.Resources
@using Orchard.Utility.Extensions
@using Orchard.Workflows.Models
@using Orchard.Workflows.Models.Descriptors
@using Orchard.Workflows.Services
@@ -29,7 +30,7 @@
//<![CDATA[
var activities = { @foreach (var activity in allActivities) { <text>
'@activity.Name': {
outcomes: [@Html.Raw(String.Join(",", activity.GetPossibleOutcomes(new ActivityContext()).Where(x => !String.IsNullOrEmpty(x.Text)).Select(x => "'" + HttpUtility.JavaScriptStringEncode(x.Text) + "'").ToArray()))],
outcomes: [@Html.Raw(String.Join(",", activity.GetPossibleOutcomes(new WorkflowContext()).Where(x => !String.IsNullOrEmpty(x.Text)).Select(x => "'" + HttpUtility.JavaScriptStringEncode(x.Text) + "'").ToArray()))],
category: '@HttpUtility.JavaScriptStringEncode(activity.Category.Text)',
description: '@HttpUtility.JavaScriptStringEncode(activity.Description.Text)',
IsEvent: @(activity.IsEvent ? "true" : "false"),