9cff87fe85
Remove project bookkeeping citations from shipped code comments across the solution: hyphenated task IDs (WP-14, StoreAndForward-025), milestone/task/ issue refs (M3, Task 4, Audit Log #23, #21), Bundle X task-bundle labels, and C/D/K/S/T phase labels. Comment text only — no code logic, string/log literals, or XML-doc structure changed. Genuine descriptions are preserved (only the citation is stripped), and technical lookalikes are retained (UTF-8, SHA-256, T00:00:00, M365, UTC-5, pre-C4/pre-C5 schema versions). Flagged by the new CommentChecker TaskReferenceInComment / TrackingReferenceInComment checks plus targeted grep passes; full solution builds clean, append-only guard tests pass.
163 lines
6.9 KiB
C#
163 lines
6.9 KiB
C#
using System.Text.Json;
|
|
using Akka.Actor;
|
|
using Akka.Event;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Messages.RemoteQuery;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.StoreAndForward;
|
|
|
|
/// <summary>
|
|
/// Akka actor bridge for <see cref="StoreAndForwardService"/> parked-message operations.
|
|
/// Receives Query/Retry/Discard requests from the SiteCommunicationActor and replies
|
|
/// with the matching response records.
|
|
/// </summary>
|
|
public class ParkedMessageHandlerActor : ReceiveActor
|
|
{
|
|
private readonly ILoggingAdapter _log = Context.GetLogger();
|
|
private readonly StoreAndForwardService _service;
|
|
private readonly string _siteId;
|
|
|
|
/// <summary>
|
|
/// Initializes the actor and registers message handlers for query, retry, and discard operations.
|
|
/// </summary>
|
|
/// <param name="service">The store-and-forward service used to execute parked-message operations.</param>
|
|
/// <param name="siteId">The site identifier this actor manages parked messages for.</param>
|
|
public ParkedMessageHandlerActor(StoreAndForwardService service, string siteId)
|
|
{
|
|
_service = service;
|
|
_siteId = siteId;
|
|
|
|
Receive<ParkedMessageQueryRequest>(HandleQuery);
|
|
Receive<ParkedMessageRetryRequest>(HandleRetry);
|
|
Receive<ParkedMessageDiscardRequest>(HandleDiscard);
|
|
|
|
// Central→site Retry/Discard relay for parked cached
|
|
// operations. The cached call's S&F buffer message id is the
|
|
// TrackedOperationId, so these reuse the same parked-message primitive
|
|
// as HandleRetry/HandleDiscard, keyed off the tracked id.
|
|
Receive<RetryParkedOperation>(HandleRetryParkedOperation);
|
|
Receive<DiscardParkedOperation>(HandleDiscardParkedOperation);
|
|
}
|
|
|
|
private void HandleQuery(ParkedMessageQueryRequest msg)
|
|
{
|
|
var sender = Sender;
|
|
var siteId = _siteId;
|
|
|
|
_service.GetParkedMessagesAsync(category: null, msg.PageNumber, msg.PageSize)
|
|
.PipeTo(
|
|
sender,
|
|
success: result =>
|
|
{
|
|
var entries = result.Messages
|
|
.Select(m => new ParkedMessageEntry(
|
|
MessageId: m.Id,
|
|
TargetSystem: m.Target,
|
|
MethodName: ExtractMethodName(m.PayloadJson, m.Category),
|
|
ErrorMessage: m.LastError ?? string.Empty,
|
|
AttemptCount: m.RetryCount,
|
|
OriginalTimestamp: m.CreatedAt,
|
|
LastAttemptTimestamp: m.LastAttemptAt ?? m.CreatedAt,
|
|
MaxAttempts: m.MaxRetries,
|
|
Category: m.Category,
|
|
OriginInstance: m.OriginInstanceName))
|
|
.ToList();
|
|
|
|
return new ParkedMessageQueryResponse(
|
|
msg.CorrelationId, siteId, entries, result.TotalCount,
|
|
msg.PageNumber, msg.PageSize, true, null, DateTimeOffset.UtcNow);
|
|
},
|
|
failure: ex => new ParkedMessageQueryResponse(
|
|
msg.CorrelationId, siteId, [], 0, msg.PageNumber, msg.PageSize,
|
|
false, ex.GetBaseException().Message, DateTimeOffset.UtcNow));
|
|
}
|
|
|
|
private void HandleRetry(ParkedMessageRetryRequest msg)
|
|
{
|
|
var sender = Sender;
|
|
|
|
_service.RetryParkedMessageAsync(msg.MessageId)
|
|
.PipeTo(
|
|
sender,
|
|
success: retried => new ParkedMessageRetryResponse(
|
|
msg.CorrelationId, retried,
|
|
retried ? null : "Message not found or no longer parked."),
|
|
failure: ex => new ParkedMessageRetryResponse(
|
|
msg.CorrelationId, false, ex.GetBaseException().Message));
|
|
}
|
|
|
|
private void HandleDiscard(ParkedMessageDiscardRequest msg)
|
|
{
|
|
var sender = Sender;
|
|
|
|
_service.DiscardParkedMessageAsync(msg.MessageId)
|
|
.PipeTo(
|
|
sender,
|
|
success: discarded => new ParkedMessageDiscardResponse(
|
|
msg.CorrelationId, discarded,
|
|
discarded ? null : "Message not found or no longer parked."),
|
|
failure: ex => new ParkedMessageDiscardResponse(
|
|
msg.CorrelationId, false, ex.GetBaseException().Message));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes a central-relayed Retry of a parked cached call.
|
|
/// The tracked id is the S&F buffer message id, so this reuses
|
|
/// <see cref="StoreAndForwardService.RetryParkedMessageAsync"/> — which only
|
|
/// touches rows that are actually <c>Parked</c> (a non-parked or unknown
|
|
/// operation yields <c>false</c>, a safe no-op). Central never mutates the
|
|
/// central <c>SiteCalls</c> mirror; the reset row's corrected state flows
|
|
/// back via the normal cached-call telemetry path.
|
|
/// </summary>
|
|
private void HandleRetryParkedOperation(RetryParkedOperation msg)
|
|
{
|
|
var sender = Sender;
|
|
|
|
_service.RetryParkedMessageAsync(msg.TrackedOperationId.ToString())
|
|
.PipeTo(
|
|
sender,
|
|
success: applied => new ParkedOperationActionAck(
|
|
msg.CorrelationId, applied, ErrorMessage: null),
|
|
failure: ex => new ParkedOperationActionAck(
|
|
msg.CorrelationId, Applied: false, ex.GetBaseException().Message));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes a central-relayed Discard of a parked cached call.
|
|
/// Mirrors <see cref="HandleRetryParkedOperation"/>; Discard removes the
|
|
/// parked S&F buffer row (only when it is actually <c>Parked</c>).
|
|
/// </summary>
|
|
private void HandleDiscardParkedOperation(DiscardParkedOperation msg)
|
|
{
|
|
var sender = Sender;
|
|
|
|
_service.DiscardParkedMessageAsync(msg.TrackedOperationId.ToString())
|
|
.PipeTo(
|
|
sender,
|
|
success: applied => new ParkedOperationActionAck(
|
|
msg.CorrelationId, applied, ErrorMessage: null),
|
|
failure: ex => new ParkedOperationActionAck(
|
|
msg.CorrelationId, Applied: false, ex.GetBaseException().Message));
|
|
}
|
|
|
|
private static string ExtractMethodName(string payloadJson, Commons.Types.Enums.StoreAndForwardCategory category)
|
|
{
|
|
if (string.IsNullOrEmpty(payloadJson))
|
|
return category.ToString();
|
|
|
|
try
|
|
{
|
|
using var doc = JsonDocument.Parse(payloadJson);
|
|
var root = doc.RootElement;
|
|
if (root.TryGetProperty("MethodName", out var method) && method.ValueKind == JsonValueKind.String)
|
|
return method.GetString() ?? category.ToString();
|
|
if (root.TryGetProperty("Subject", out var subject) && subject.ValueKind == JsonValueKind.String)
|
|
return subject.GetString() ?? category.ToString();
|
|
}
|
|
catch (JsonException)
|
|
{
|
|
}
|
|
|
|
return category.ToString();
|
|
}
|
|
}
|