/// <summary> /// Internal constructor. /// </summary> /// <param name="client">The associated client.</param> /// <param name="methodName"> /// Optionally identifies the target workflow method by the name specified in /// the <c>[WorkflowMethod]</c> attribute tagging the method. Pass a <c>null</c> /// or empty string to target the default method. /// </param> /// <param name="options">Optional workflow options.</param> internal WorkflowFutureStub(CadenceClient client, string methodName = null, WorkflowOptions options = null) { Covenant.Requires <ArgumentNullException>(client != null, nameof(client)); var workflowInterface = typeof(WorkflowInterface); CadenceHelper.ValidateWorkflowInterface(workflowInterface); this.client = client; this.workflowTypeName = CadenceHelper.GetWorkflowTarget(workflowInterface, methodName).WorkflowTypeName; this.options = WorkflowOptions.Normalize(client, options); }
/// <summary> /// Creates an untyped stub that can be used to start a single workflow execution. /// </summary> /// <param name="workflowTypeName">Specifies the workflow type name.</param> /// <param name="options">Specifies the workflow options (including the <see cref="WorkflowOptions.TaskList"/>).</param> /// <returns>The <see cref="WorkflowStub"/>.</returns> /// <remarks> /// <para> /// Unlike activity stubs, a workflow stub may only be used to launch a single /// workflow. You'll need to create a new stub for each workflow you wish to /// invoke and then the first method called on a workflow stub must be /// the one of the methods tagged by <see cref="WorkflowMethodAttribute"/>. /// </para> /// <note> /// <para> /// .NET and Java workflows can implement multiple workflow method using attributes /// and annotations to assign unique names to each. Each workflow method is actually /// registered with Cadence as a distinct workflow type. Workflow methods with a blank /// or <c>null</c> name will simply be registered using the workflow type name. /// </para> /// <para> /// Workflow methods with a name will be registered using a combination of the workflow /// type name and the method name, using <b>"::"</b> as the separator, like: /// </para> /// <code> /// WORKFLOW-TYPENAME::METHOD-NAME /// </code> /// </note> /// </remarks> public WorkflowStub NewUntypedWorkflowStub(string workflowTypeName, WorkflowOptions options) { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(workflowTypeName), nameof(workflowTypeName)); Covenant.Requires <ArgumentNullException>(options != null, nameof(options)); EnsureNotDisposed(); return(new WorkflowStub(this) { WorkflowTypeName = workflowTypeName, Options = options }); }
//--------------------------------------------------------------------- // Internal workflow related methods used by dynamically generated workflow stubs. /// <summary> /// Starts an external workflow using a specific workflow type name, returning a <see cref="WorkflowExecution"/> /// that can be used to track the workflow and also wait for its result via <see cref="GetWorkflowResultAsync(WorkflowExecution, string)"/>. /// </summary> /// <param name="workflowTypeName"> /// The type name used when registering the workers that will handle this workflow. /// This name will often be the fully qualified name of the workflow type but /// this may have been customized when the workflow worker was registered. /// </param> /// <param name="args">Specifies the workflow arguments encoded into a byte array.</param> /// <param name="options">Specifies the workflow options.</param> /// <returns>A <see cref="WorkflowExecution"/> identifying the new running workflow instance.</returns> /// <exception cref="EntityNotExistsException">Thrown if there is no workflow registered for <paramref name="workflowTypeName"/>.</exception> /// <exception cref="BadRequestException">Thrown if the request is not valid.</exception> /// <exception cref="WorkflowRunningException">Thrown if a workflow with this ID is already running.</exception> /// <remarks> /// This method kicks off a new workflow instance and returns after Cadence has /// queued the operation but the method <b>does not</b> wait for the workflow to /// complete. /// </remarks> internal async Task <WorkflowExecution> StartWorkflowAsync(string workflowTypeName, byte[] args, WorkflowOptions options) { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(workflowTypeName), nameof(workflowTypeName)); EnsureNotDisposed(); options = WorkflowOptions.Normalize(this, options); var reply = (WorkflowExecuteReply) await CallProxyAsync( new WorkflowExecuteRequest() { Workflow = workflowTypeName, Domain = options.Domain, Args = args, Options = options.ToInternal() }); reply.ThrowOnError(); var execution = reply.Execution; return(new WorkflowExecution(execution.ID, execution.RunID)); }
/// <summary> /// Transmits a signal to an external workflow, starting the workflow if it's not currently running. /// This low-level method accepts a byte array with the already encoded parameters. /// </summary> /// <param name="workflowTypeName">The target workflow type name.</param> /// <param name="signalName">Identifies the signal.</param> /// <param name="signalArgs">Optionally specifies the signal arguments as a byte array.</param> /// <param name="startArgs">Optionally specifies the workflow arguments.</param> /// <param name="options">Optionally specifies the options to be used for starting the workflow when required.</param> /// <returns>The <see cref="WorkflowExecution"/>.</returns> /// <exception cref="EntityNotExistsException">Thrown if the domain does not exist.</exception> /// <exception cref="BadRequestException">Thrown if the request is invalid.</exception> /// <exception cref="InternalServiceException">Thrown for internal Cadence problems.</exception> internal async Task <WorkflowExecution> SignalWorkflowWithStartAsync(string workflowTypeName, string signalName, byte[] signalArgs, byte[] startArgs, WorkflowOptions options) { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(workflowTypeName), nameof(workflowTypeName)); Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(signalName), nameof(signalName)); EnsureNotDisposed(); options = WorkflowOptions.Normalize(this, options); var reply = (WorkflowSignalWithStartReply) await CallProxyAsync( new WorkflowSignalWithStartRequest() { Workflow = workflowTypeName, WorkflowId = options.WorkflowId, Options = options.ToInternal(), SignalName = signalName, SignalArgs = signalArgs, WorkflowArgs = startArgs, Domain = options.Domain }); reply.ThrowOnError(); return(reply.Execution.ToPublic()); }
//--------------------------------------------------------------------- // Static members /// <summary> /// Normalizes the options passed by creating or cloning a new instance as /// required and filling unset properties using default client settings. /// </summary> /// <param name="client">The associated Cadence client.</param> /// <param name="options">The input options or <c>null</c>.</param> /// <param name="workflowInterface">Optionally specifies the workflow interface definition.</param> /// <returns>The normalized options.</returns> /// <exception cref="ArgumentNullException">Thrown if a valid task list is not specified.</exception> internal static WorkflowOptions Normalize(CadenceClient client, WorkflowOptions options, Type workflowInterface = null) { Covenant.Requires <ArgumentNullException>(client != null, nameof(client)); WorkflowInterfaceAttribute interfaceAttribute = null; if (workflowInterface != null) { CadenceHelper.ValidateWorkflowInterface(workflowInterface); interfaceAttribute = workflowInterface.GetCustomAttribute <WorkflowInterfaceAttribute>(); } if (options == null) { options = new WorkflowOptions(); } else { options = options.Clone(); } if (string.IsNullOrEmpty(options.Domain)) { options.Domain = client.Settings.DefaultDomain; } if (!options.ScheduleToCloseTimeout.HasValue || options.ScheduleToCloseTimeout.Value <= TimeSpan.Zero) { options.ScheduleToCloseTimeout = client.Settings.WorkflowScheduleToCloseTimeout; } if (!options.ScheduleToStartTimeout.HasValue || options.ScheduleToStartTimeout.Value <= TimeSpan.Zero) { options.ScheduleToStartTimeout = client.Settings.WorkflowScheduleToStartTimeout; } if (!options.TaskStartToCloseTimeout.HasValue || options.TaskStartToCloseTimeout.Value <= TimeSpan.Zero) { options.TaskStartToCloseTimeout = client.Settings.WorkflowTaskStartToCloseTimeout; } if (options.WorkflowIdReusePolicy == Cadence.WorkflowIdReusePolicy.UseDefault) { options.WorkflowIdReusePolicy = client.Settings.WorkflowIdReusePolicy; } if (string.IsNullOrEmpty(options.TaskList)) { if (interfaceAttribute != null && !string.IsNullOrEmpty(interfaceAttribute.TaskList)) { options.TaskList = interfaceAttribute.TaskList; } } if (string.IsNullOrEmpty(options.TaskList)) { throw new ArgumentNullException(nameof(options), "You must specify a valid task list explicitly or via an [WorkflowInterface(TaskList = \"my-tasklist\")] attribute on the target workflow interface."); } return(options); }
/// <summary> /// Default constructor. /// </summary> /// <param name="client">The associated client.</param> /// <param name="workflowTypeName">The workflow type name.</param> /// <param name="execution">The workflow execution or <c>null</c> if the workflow hasn't been started.</param> /// <param name="taskList">Specifies the task list.</param> /// <param name="options">Specifies the workflow options.</param> /// <param name="domain">Specifies specifies the domain.</param> internal WorkflowStub(CadenceClient client, string workflowTypeName, WorkflowExecution execution, string taskList, WorkflowOptions options, string domain) { Covenant.Requires <ArgumentNullException>(client != null); Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(workflowTypeName)); Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(taskList)); Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(domain)); this.client = client; this.WorkflowTypeName = workflowTypeName; this.Execution = execution; this.taskList = taskList; this.Options = options; this.domain = domain; }
/// <summary> /// Creates a stub suitable for starting an external workflow and then waiting /// for the result as separate operations. /// </summary> /// <typeparam name="TWorkflowInterface">The target workflow interface.</typeparam> /// <param name="methodName"> /// Optionally identifies the target workflow method. This is the name specified in /// <c>[WorkflowMethod]</c> attribute for the workflow method or <c>null</c>/empty for /// the default workflow method. /// </param> /// <param name="options">Optionally specifies custom <see cref="WorkflowOptions"/>.</param> /// <returns>A <see cref="ChildWorkflowStub{TWorkflowInterface}"/> instance.</returns> public WorkflowFutureStub <TWorkflowInterface> NewWorkflowFutureStub <TWorkflowInterface>(string methodName = null, WorkflowOptions options = null) where TWorkflowInterface : class { CadenceHelper.ValidateWorkflowInterface(typeof(TWorkflowInterface)); EnsureNotDisposed(); options = WorkflowOptions.Normalize(this, options, typeof(TWorkflowInterface)); return(new WorkflowFutureStub <TWorkflowInterface>(this, methodName, options)); }
/// <summary> /// Transmits a signal to a workflow, starting the workflow if it's not currently running. /// </summary> /// <param name="signalName">Identifies the signal.</param> /// <param name="signalArgs">Optionally specifies the signal arguments as a byte array.</param> /// <param name="startArgs">Optionally specifies the workflow arguments.</param> /// <param name="options">Optionally specifies the options to be used for starting the workflow when required.</param> /// <param name="taskList">Optionally specifies the task list. This defaults to <b>"default"</b>.</param> /// <param name="domain">Optionally specifies the domain. This defaults to the client domain.</param> /// <returns>The <see cref="WorkflowExecution"/>.</returns> /// <exception cref="CadenceEntityNotExistsException">Thrown if the domain does not exist.</exception> /// <exception cref="CadenceBadRequestException">Thrown if the request is invalid.</exception> /// <exception cref="CadenceInternalServiceException">Thrown for internal Cadence problems.</exception> internal async Task <WorkflowExecution> SignalWorkflowWithStartAsync(string signalName, byte[] signalArgs = null, byte[] startArgs = null, string taskList = null, WorkflowOptions options = null, string domain = null) { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(signalName)); options = options ?? new WorkflowOptions(); var reply = (WorkflowSignalWithStartReply) await CallProxyAsync( new WorkflowSignalWithStartRequest() { WorkflowId = options.WorkflowId, Options = options.ToInternal(this, taskList), SignalName = signalName, SignalArgs = signalArgs, WorkflowArgs = startArgs, Domain = ResolveDomain(domain) }); reply.ThrowOnError(); return(reply.Execution.ToPublic()); }
//--------------------------------------------------------------------- // Internal workflow related methods used by dynamically generated workflow stubs. /// <summary> /// Starts an external workflow using a specific workflow type name, returning a <see cref="WorkflowExecution"/> /// that can be used to track the workflow and also wait for its result via <see cref="GetWorkflowResultAsync(WorkflowExecution, string)"/>. /// </summary> /// <param name="workflowTypeName"> /// The type name used when registering the workers that will handle this workflow. /// This name will often be the fully qualified name of the workflow type but /// this may have been customized when the workflow worker was registered. /// </param> /// <param name="args">Optionally specifies the workflow arguments encoded into a byte array.</param> /// <param name="taskList">Optionally specifies the target task list. This defaults to the client task list.</param> /// <param name="options">Specifies the workflow options.</param> /// <param name="domain">Optionally specifies the Cadence domain where the workflow will run. This defaults to the client domain.</param> /// <returns>A <see cref="WorkflowExecution"/> identifying the new running workflow instance.</returns> /// <exception cref="CadenceEntityNotExistsException">Thrown if there is no workflow registered for <paramref name="workflowTypeName"/>.</exception> /// <exception cref="CadenceBadRequestException">Thrown if the request is not valid.</exception> /// <exception cref="CadenceWorkflowRunningException">Thrown if a workflow with this ID is already running.</exception> /// <remarks> /// This method kicks off a new workflow instance and returns after Cadence has /// queued the operation but the method <b>does not</b> wait for the workflow to /// complete. /// </remarks> internal async Task <WorkflowExecution> StartWorkflowAsync(string workflowTypeName, byte[] args = null, string taskList = null, WorkflowOptions options = null, string domain = null) { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(workflowTypeName)); options = options ?? new WorkflowOptions(); var reply = (WorkflowExecuteReply) await CallProxyAsync( new WorkflowExecuteRequest() { Workflow = workflowTypeName, Domain = domain ?? Settings.DefaultDomain, Args = args, Options = options.ToInternal(this, taskList) }); reply.ThrowOnError(); var execution = reply.Execution; return(new WorkflowExecution(execution.ID, execution.RunID)); }
/// <summary> /// Creates an untyped stub that can be used to execute, query, and signal a new workflow /// specified using an explicit type parameter. /// </summary> /// <param name="workflowTypeName">Specifies workflow type name (see the remarks).</param> /// <param name="options">Optionally specifies the workflow options.</param> /// <param name="domain">Optionally overrides the client's default domain.</param> /// <returns>The <see cref="IWorkflowStub"/>.</returns> /// <remarks> /// <para> /// Unlike activity stubs, a workflow stub may only be used to launch a single /// workflow. You'll need to create a new stub for each workflow you wish to /// invoke. /// </para> /// <para> /// <paramref name="workflowTypeName"/> specifies the target workflow implementation type name and optionally, /// the specific workflow method to be called for workflow interfaces that have multiple methods. For /// workflow methods tagged by <c>[WorkflowMethod]</c> with specifying a name, the workflow type name will default /// to the fully qualified interface type name or the custom type name specified by <see cref="WorkflowAttribute.TypeName"/>. /// </para> /// <para> /// For workflow methods with <see cref="WorkflowMethodAttribute.Name"/> specified, the workflow type will /// look like: /// </para> /// <code> /// WORKFLOW-TYPE-NAME::METHOD-NAME /// </code> /// <para> /// You'll need to use this format when calling workflows using external untyped stubs or /// from other languages. The Java Cadence client works the same way. /// </para> /// </remarks> public WorkflowStub NewUntypedWorkflowStub(string workflowTypeName, WorkflowOptions options = null, string domain = null) { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(workflowTypeName)); throw new NotImplementedException(); }
/// <summary> /// Used to construct an untyped workflow stub that can be used to start an external workflow. /// </summary> /// <param name="client">The associated client.</param> /// <param name="workflowTypeName">The workflow type name.</param> /// <param name="execution">The workflow execution.</param> /// <param name="options">The workflow options.</param> internal WorkflowStub(CadenceClient client, string workflowTypeName, WorkflowExecution execution, WorkflowOptions options) { Covenant.Requires <ArgumentNullException>(client != null, nameof(client)); Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(workflowTypeName)); Covenant.Requires <ArgumentNullException>(execution != null); Covenant.Requires <ArgumentNullException>(options != null); this.client = client; this.WorkflowTypeName = workflowTypeName; this.Execution = execution; this.Options = options; this.withinWorkflow = false; }
//--------------------------------------------------------------------- // Static members /// <summary> /// Normalizes the options passed by creating or cloning a new instance as /// required and filling unset properties using default client settings. /// </summary> /// <param name="client">The associated Cadence client.</param> /// <param name="options">The input options or <c>null</c>.</param> /// <param name="workflowInterface">Optionally specifies the workflow interface definition.</param> /// <param name="method">Optionally specifies the target workflow method.</param> /// <returns>The normalized options.</returns> /// <exception cref="ArgumentNullException">Thrown if a valid task list is not specified.</exception> internal static WorkflowOptions Normalize(CadenceClient client, WorkflowOptions options, Type workflowInterface = null, MethodInfo method = null) { Covenant.Requires <ArgumentNullException>(client != null, nameof(client)); WorkflowInterfaceAttribute interfaceAttribute = null; WorkflowMethodAttribute methodAttribute = null; if (options == null) { options = new WorkflowOptions(); } else { options = options.Clone(); } if (workflowInterface != null) { CadenceHelper.ValidateWorkflowInterface(workflowInterface); interfaceAttribute = workflowInterface.GetCustomAttribute <WorkflowInterfaceAttribute>(); } if (method != null) { methodAttribute = method.GetCustomAttribute <WorkflowMethodAttribute>(); } if (string.IsNullOrEmpty(options.Domain)) { if (!string.IsNullOrEmpty(methodAttribute?.Domain)) { options.Domain = methodAttribute.Domain; } if (string.IsNullOrEmpty(options.Domain) && !string.IsNullOrEmpty(interfaceAttribute?.Domain)) { options.Domain = interfaceAttribute.Domain; } if (string.IsNullOrEmpty(options.Domain)) { options.Domain = client.Settings.DefaultDomain; } if (string.IsNullOrEmpty(options.Domain)) { throw new ArgumentNullException(nameof(options), "You must specify a valid domain explicitly in [CadenceSettings], [ActivityOptions] or via an [ActivityInterface] or [ActivityMethod] attribute on the target activity interface or method."); } } if (string.IsNullOrEmpty(options.TaskList)) { if (!string.IsNullOrEmpty(methodAttribute?.TaskList)) { options.TaskList = methodAttribute.TaskList; } if (string.IsNullOrEmpty(options.TaskList) && !string.IsNullOrEmpty(interfaceAttribute?.TaskList)) { options.TaskList = interfaceAttribute.TaskList; } if (string.IsNullOrEmpty(options.TaskList)) { options.TaskList = client.Settings.DefaultTaskList; } if (string.IsNullOrEmpty(options.TaskList)) { throw new ArgumentNullException(nameof(options), "You must specify a valid task list explicitly via [WorkflowOptions] or using an [WorkflowInterface] or [WorkflowMethod] attribute on the target workflow interface or method."); } } if (options.StartToCloseTimeout <= TimeSpan.Zero) { if (methodAttribute != null && methodAttribute.StartToCloseTimeoutSeconds > 0) { options.StartToCloseTimeout = TimeSpan.FromSeconds(methodAttribute.StartToCloseTimeoutSeconds); } if (options.StartToCloseTimeout <= TimeSpan.Zero) { options.StartToCloseTimeout = client.Settings.WorkflowStartToCloseTimeout; } } if (options.ScheduleToStartTimeout <= TimeSpan.Zero) { if (methodAttribute != null && methodAttribute.ScheduleToStartTimeoutSeconds > 0) { options.ScheduleToStartTimeout = TimeSpan.FromSeconds(methodAttribute.ScheduleToStartTimeoutSeconds); } if (options.ScheduleToStartTimeout <= TimeSpan.Zero) { options.ScheduleToStartTimeout = client.Settings.WorkflowScheduleToStartTimeout; } } if (options.DecisionTaskTimeout <= TimeSpan.Zero) { if (methodAttribute != null && methodAttribute.DecisionTaskTimeoutSeconds > 0) { options.DecisionTaskTimeout = TimeSpan.FromSeconds(methodAttribute.DecisionTaskTimeoutSeconds); } if (options.DecisionTaskTimeout <= TimeSpan.Zero) { options.DecisionTaskTimeout = client.Settings.WorkflowDecisionTaskTimeout; } } if (options.WorkflowIdReusePolicy == Cadence.WorkflowIdReusePolicy.UseDefault) { if (methodAttribute != null && methodAttribute.WorkflowIdReusePolicy != WorkflowIdReusePolicy.UseDefault) { options.WorkflowIdReusePolicy = methodAttribute.WorkflowIdReusePolicy; } if (options.WorkflowIdReusePolicy == Cadence.WorkflowIdReusePolicy.UseDefault) { options.WorkflowIdReusePolicy = client.Settings.WorkflowIdReusePolicy; } } if (string.IsNullOrEmpty(options.CronSchedule) && !string.IsNullOrEmpty(methodAttribute?.CronSchedule)) { options.CronSchedule = methodAttribute.CronSchedule; } return(options); }