Esempio n. 1
0
        /// <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);
        }
Esempio n. 2
0
        /// <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
            });
        }
Esempio n. 3
0
        //---------------------------------------------------------------------
        // 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));
        }
Esempio n. 4
0
        /// <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());
        }
Esempio n. 5
0
        //---------------------------------------------------------------------
        // 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);
        }
Esempio n. 6
0
        /// <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;
        }
Esempio n. 7
0
        /// <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));
        }
Esempio n. 8
0
        /// <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());
        }
Esempio n. 9
0
        //---------------------------------------------------------------------
        // 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));
        }
Esempio n. 10
0
        /// <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();
        }
Esempio n. 11
0
        /// <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;
        }
Esempio n. 12
0
        //---------------------------------------------------------------------
        // 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);
        }