/// <summary> /// Internal constructor. /// </summary> /// <param name="parent">The parent activity implementation.</param> internal Activity(ActivityBase parent) { Covenant.Requires <ArgumentNullException>(parent != null, nameof(parent)); this.parent = parent; this.Logger = LogManager.Default.GetLogger(sourceModule: Client.Settings.ClientIdentity, contextId: parent.ActivityTask?.WorkflowExecution?.RunId); }
/// <summary> /// Registers an activity implementation with Cadence. /// </summary> /// <typeparam name="TActivity">The <see cref="ActivityBase"/> derived class implementing the activity.</typeparam> /// <param name="activityTypeName"> /// Optionally specifies a custom activity type name that will be used /// for identifying the activity implementation in Cadence. This defaults /// to the fully qualified <typeparamref name="TActivity"/> type name. /// </param> /// <param name="domain">Optionally overrides the default client domain.</param> /// <returns>The tracking <see cref="Task"/>.</returns> /// <exception cref="InvalidOperationException">Thrown if a different activity class has already been registered for <paramref name="activityTypeName"/>.</exception> /// <exception cref="ActivityWorkerStartedException"> /// Thrown if an activity worker has already been started for the client. You must /// register activity implementations before starting workers. /// </exception> /// <remarks> /// <note> /// Be sure to register all services you will be injecting into activities via /// <see cref="NeonHelper.ServiceContainer"/> before you call this as well as /// registering of your activity implementations before starting workers. /// </note> /// </remarks> public async Task RegisterActivityAsync <TActivity>(string activityTypeName = null, string domain = null) where TActivity : ActivityBase { await SyncContext.Clear; CadenceHelper.ValidateActivityImplementation(typeof(TActivity)); CadenceHelper.ValidateActivityTypeName(activityTypeName); EnsureNotDisposed(); if (activityWorkerStarted) { throw new ActivityWorkerStartedException(); } var activityType = typeof(TActivity); if (string.IsNullOrEmpty(activityTypeName)) { activityTypeName = CadenceHelper.GetActivityTypeName(activityType, activityType.GetCustomAttribute <ActivityAttribute>()); } await ActivityBase.RegisterAsync(this, activityType, activityTypeName, ResolveDomain(domain)); lock (registeredActivityTypes) { registeredActivityTypes.Add(CadenceHelper.GetActivityInterface(typeof(TActivity))); } }
/// <summary> /// Scans the assembly passed looking for activity implementations derived from /// <see cref="ActivityBase"/> and tagged by <see cref="ActivityAttribute"/> and /// registers them with Cadence. /// </summary> /// <param name="assembly">The target assembly.</param> /// <param name="domain">Optionally overrides the default client domain.</param> /// <returns>The tracking <see cref="Task"/>.</returns> /// <exception cref="TypeLoadException"> /// Thrown for types tagged by <see cref="ActivityAttribute"/> that are not /// derived from <see cref="ActivityBase"/>. /// </exception> /// <exception cref="InvalidOperationException">Thrown if one of the tagged classes conflict with an existing registration.</exception> /// <exception cref="ActivityWorkerStartedException"> /// Thrown if an activity worker has already been started for the client. You must /// register activity implementations before starting workers. /// </exception> /// <remarks> /// <note> /// Be sure to register all services you will be injecting into activities via /// <see cref="NeonHelper.ServiceContainer"/> before you call this as well as /// registering of your activity implementations before starting workers. /// </note> /// </remarks> public async Task RegisterAssemblyActivitiesAsync(Assembly assembly, string domain = null) { await SyncContext.Clear; Covenant.Requires <ArgumentNullException>(assembly != null, nameof(assembly)); EnsureNotDisposed(); if (activityWorkerStarted) { throw new ActivityWorkerStartedException(); } foreach (var type in assembly.GetTypes().Where(type => type.IsClass)) { var activityAttribute = type.GetCustomAttribute <ActivityAttribute>(); if (activityAttribute != null && activityAttribute.AutoRegister) { var activityTypeName = CadenceHelper.GetActivityTypeName(type, activityAttribute); await ActivityBase.RegisterAsync(this, type, activityTypeName, ResolveDomain(domain)); lock (registeredActivityTypes) { registeredActivityTypes.Add(CadenceHelper.GetActivityInterface(type)); } } } }
//--------------------------------------------------------------------- // Cadence activity related operations. /// <summary> /// Registers an activity implementation with Cadence. /// </summary> /// <typeparam name="TActivity">The <see cref="IActivityBase"/> derived class implementing the activity.</typeparam> /// <param name="activityTypeName"> /// Optionally specifies a custom activity type name that will be used /// for identifying the activity implementation in Cadence. This defaults /// to the fully qualified <typeparamref name="TActivity"/> type name. /// </param> /// <returns>The tracking <see cref="Task"/>.</returns> /// <exception cref="InvalidOperationException">Thrown if a different activity class has already been registered for <paramref name="activityTypeName"/>.</exception> /// <exception cref="CadenceActivityWorkerStartedException"> /// Thrown if an activity worker has already been started for the client. You must /// register activity implementations before starting workers. /// </exception> /// <remarks> /// <note> /// Be sure to register all of your activity implementations before starting a workflow worker. /// </note> /// </remarks> public async Task RegisterActivityAsync <TActivity>(string activityTypeName = null) where TActivity : ActivityBase { CadenceHelper.ValidateActivityImplementation(typeof(TActivity)); if (string.IsNullOrEmpty(activityTypeName)) { activityTypeName = activityTypeName ?? typeof(TActivity).FullName; } if (activityWorkerStarted) { throw new CadenceActivityWorkerStartedException(); } if (!ActivityBase.Register(this, typeof(TActivity), activityTypeName)) { var reply = (ActivityRegisterReply) await CallProxyAsync( new ActivityRegisterRequest() { Name = activityTypeName }); reply.ThrowOnError(); } }
/// <summary> /// Handles workflow local activity invocations. /// </summary> /// <param name="client">The client the request was received from.</param> /// <param name="request">The request message.</param> /// <returns>The reply message.</returns> internal static async Task<ActivityInvokeLocalReply> OnInvokeLocalActivity(CadenceClient client, ActivityInvokeLocalRequest request) { Covenant.Requires<ArgumentNullException>(request != null, nameof(request)); try { WorkflowBase.CallContext.Value = WorkflowCallContext.Activity; var workflow = GetWorkflow(client, request.ContextId); if (workflow != null) { LocalActivityAction activityAction; lock (syncLock) { if (!workflow.Workflow.IdToLocalActivityAction.TryGetValue(request.ActivityTypeId, out activityAction)) { return new ActivityInvokeLocalReply() { Error = new EntityNotExistsException($"Activity type does not exist for [activityTypeId={request.ActivityTypeId}].").ToCadenceError() }; } } var workerArgs = new WorkerArgs() { Client = client, ContextId = request.ActivityContextId }; var activity = ActivityBase.Create(client, activityAction, null); var result = await activity.OnInvokeAsync(client, request.Args); return new ActivityInvokeLocalReply() { Result = result }; } else { return new ActivityInvokeLocalReply() { Error = new EntityNotExistsException($"Workflow with [contextID={request.ContextId}] does not exist.").ToCadenceError() }; } } catch (Exception e) { log.LogError(e); return new ActivityInvokeLocalReply() { Error = new CadenceError(e) }; } finally { WorkflowBase.CallContext.Value = WorkflowCallContext.None; } }
/// <summary> /// Scans the assembly passed looking for activity implementations derived from /// <see cref="IActivityBase"/> and tagged by <see cref="ActivityAttribute"/> and /// registers them with Cadence. /// </summary> /// <param name="assembly">The target assembly.</param> /// <returns>The tracking <see cref="Task"/>.</returns> /// <exception cref="TypeLoadException"> /// Thrown for types tagged by <see cref="ActivityAttribute"/> that are not /// derived from <see cref="IActivityBase"/>. /// </exception> /// <exception cref="InvalidOperationException">Thrown if one of the tagged classes conflict with an existing registration.</exception> /// <exception cref="CadenceActivityWorkerStartedException"> /// Thrown if an activity worker has already been started for the client. You must /// register activity implementations before starting workers. /// </exception> /// <remarks> /// <note> /// Be sure to register all of your activity implementations before starting a workflow worker. /// </note> /// </remarks> public async Task RegisterAssemblyActivitiesAsync(Assembly assembly) { Covenant.Requires <ArgumentNullException>(assembly != null); if (activityWorkerStarted) { throw new CadenceActivityWorkerStartedException(); } foreach (var type in assembly.GetTypes()) { var activityAttribute = type.GetCustomAttribute <ActivityAttribute>(); if (activityAttribute != null) { if (type.Implements <IActivityBase>()) { if (activityAttribute.AutoRegister) { var activityTypeName = activityAttribute.TypeName ?? type.FullName; if (!ActivityBase.Register(this, type, activityTypeName)) { var reply = (ActivityRegisterReply) await CallProxyAsync( new ActivityRegisterRequest() { Name = activityTypeName }); reply.ThrowOnError(); } } } else { throw new TypeLoadException($"Type [{type.FullName}] is tagged by [{nameof(ActivityAttribute)}] but is not derived from [{nameof(IActivityBase)}]."); } } } }
/// <summary> /// Internal constructor. /// </summary> /// <param name="activity">The parent activity implementation.</param> internal Activity(ActivityBase activity) { Covenant.Requires <ArgumentNullException>(activity != null); this.activity = activity; }