/// <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))); } }
//--------------------------------------------------------------------- // 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> /// Called internally to initialize the activity. /// </summary> /// <param name="client">The associated client.</param> /// <param name="activityType">Specifies the target activity type.</param> /// <param name="activityMethod">Specifies the target activity method.</param> /// <param name="dataConverter">Specifies the data converter to be used for parameter and result serilization.</param> /// <param name="contextId">The activity's context ID.</param> internal void Initialize(CadenceClient client, Type activityType, MethodInfo activityMethod, IDataConverter dataConverter, long contextId) { Covenant.Requires <ArgumentNullException>(client != null, nameof(client)); Covenant.Requires <ArgumentNullException>(activityType != null, nameof(activityType)); Covenant.Requires <ArgumentNullException>(activityMethod != null, nameof(activityMethod)); Covenant.Requires <ArgumentNullException>(dataConverter != null, nameof(dataConverter)); CadenceHelper.ValidateActivityImplementation(activityType); this.Client = client; this.Activity = new Activity(this); this.activityType = activityType; this.activityMethod = activityMethod; this.dataConverter = dataConverter; this.ContextId = contextId; this.CancellationTokenSource = new CancellationTokenSource(); this.CancellationToken = CancellationTokenSource.Token; this.logger = LogManager.Default.GetLogger(module: activityType.FullName); }
/// <summary> /// Called internally to initialize the activity. /// </summary> /// <param name="client">The associated client.</param> /// <param name="activityType">Specifies the target activity type.</param> /// <param name="activityMethod">Specifies the target activity method.</param> /// <param name="dataConverter">Specifies the data converter to be used for parameter and result serilization.</param> /// <param name="contextId">The activity's context ID or <c>null</c> for local activities.</param> internal void Initialize(CadenceClient client, Type activityType, MethodInfo activityMethod, IDataConverter dataConverter, long?contextId) { Covenant.Requires <ArgumentNullException>(client != null, nameof(client)); Covenant.Requires <ArgumentNullException>(activityType != null, nameof(activityType)); Covenant.Requires <ArgumentNullException>(activityMethod != null, nameof(activityMethod)); Covenant.Requires <ArgumentNullException>(dataConverter != null, nameof(dataConverter)); CadenceHelper.ValidateActivityImplementation(activityType); var activityTask = new ActivityTask() { // $todo(jefflill): Need to initialize these properties. }; this.Client = client; this.ActivityTask = activityTask; this.Activity = new Activity(this); this.activityType = activityType; this.activityMethod = activityMethod; this.dataConverter = dataConverter; this.ContextId = contextId; this.CancellationTokenSource = new CancellationTokenSource(); this.CancellationToken = CancellationTokenSource.Token; }
/// <summary> /// Registers an activity type. /// </summary> /// <param name="client">The associated client.</param> /// <param name="activityType">The activity type.</param> /// <param name="activityTypeName">The name used to identify the implementation.</param> /// <returns><c>true</c> if the activity was already registered.</returns> /// <exception cref="InvalidOperationException">Thrown if a different activity class has already been registered for <paramref name="activityTypeName"/>.</exception> internal static bool Register(CadenceClient client, Type activityType, string activityTypeName) { Covenant.Requires <ArgumentNullException>(client != null); CadenceHelper.ValidateActivityImplementation(activityType); activityTypeName = GetActivityTypeKey(client, activityTypeName); var constructInfo = new ActivityInvokeInfo(); constructInfo.ActivityType = activityType; constructInfo.Constructor = constructInfo.ActivityType.GetConstructor(noTypeArgs); if (constructInfo.Constructor == null) { throw new ArgumentException($"Activity type [{constructInfo.ActivityType.FullName}] does not have a default constructor."); } lock (syncLock) { if (nameToInvokeInfo.TryGetValue(activityTypeName, out var existingEntry)) { if (!object.ReferenceEquals(existingEntry.ActivityType, constructInfo.ActivityType)) { throw new InvalidOperationException($"Conflicting activity type registration: Activity type [{activityType.FullName}] is already registered for workflow type name [{activityTypeName}]."); } return(true); } else { nameToInvokeInfo[activityTypeName] = constructInfo; return(false); } } }
/// <summary> /// Registers an activity type. /// </summary> /// <param name="client">The associated client.</param> /// <param name="activityType">The activity type.</param> /// <param name="activityTypeName">The name used to identify the implementation.</param> /// <param name="domain">Specifies the target domain.</param> /// <returns><c>true</c> if the activity was already registered.</returns> /// <exception cref="InvalidOperationException">Thrown if a different activity class has already been registered for <paramref name="activityTypeName"/>.</exception> internal async static Task RegisterAsync(CadenceClient client, Type activityType, string activityTypeName, string domain) { Covenant.Requires <ArgumentNullException>(client != null, nameof(client)); Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(domain), nameof(domain)); CadenceHelper.ValidateActivityImplementation(activityType); var constructor = activityType.GetConstructor(Type.EmptyTypes); if (constructor == null) { throw new ArgumentException($"Activity type [{activityType.FullName}] does not have a default constructor.", nameof(activityType)); } // We need to register each activity method that implements an activity interface method // with the same signature that that was tagged by [ActivityMethod]. // // First, we'll create a dictionary that maps method signatures from any inherited // interfaces that are tagged by [ActivityMethod] to the attribute. var methodSignatureToAttribute = new Dictionary <string, ActivityMethodAttribute>(); foreach (var interfaceType in activityType.GetInterfaces()) { foreach (var method in interfaceType.GetMethods(BindingFlags.Public | BindingFlags.Instance)) { var activityMethodAttribute = method.GetCustomAttribute <ActivityMethodAttribute>(); if (activityMethodAttribute == null) { continue; } var signature = method.ToString(); if (methodSignatureToAttribute.ContainsKey(signature)) { throw new NotSupportedException($"Activity type [{activityType.FullName}] cannot implement the [{signature}] method from two different interfaces."); } methodSignatureToAttribute.Add(signature, activityMethodAttribute); } } // Next, we need to register the activity methods that implement the // activity interface. foreach (var method in activityType.GetMethods()) { if (!methodSignatureToAttribute.TryGetValue(method.ToString(), out var activityMethodAttribute)) { continue; } var activityTypeKey = GetActivityTypeKey(client, activityTypeName, activityMethodAttribute); lock (syncLock) { if (nameToRegistration.TryGetValue(activityTypeKey, out var registration)) { if (!object.ReferenceEquals(registration.ActivityType, registration.ActivityType)) { throw new InvalidOperationException($"Conflicting activity type registration: Activity type [{activityType.FullName}] is already registered for activity type name [{activityTypeKey}]."); } } else { nameToRegistration[activityTypeKey] = new ActivityRegistration() { ActivityType = activityType, ActivityConstructor = constructor, ActivityMethod = method, ActivityMethodParamaterTypes = method.GetParameterTypes() }; } } var reply = (ActivityRegisterReply)await client.CallProxyAsync( new ActivityRegisterRequest() { Name = GetActivityTypeNameFromKey(activityTypeKey), Domain = client.ResolveDomain(domain) }); // $hack(jefflill): // // We're going to ignore any errors here to handle: // // https://github.com/nforgeio/neonKUBE/issues/668 // reply.ThrowOnError(); } }