8825df56be
conditionFilter was plumbed end-to-end but applied nowhere — a filtered source silently mirrored all conditions. Define the filter as a comma-separated, case-insensitive list of condition type names (blank = all); enforce it authoritatively client-side in DataConnectionActor routing (uniform across OPC UA + MxGateway) and, for OPC UA, additionally build a server-side EventFilter WhereClause as a bandwidth optimization.
79 lines
3.5 KiB
C#
79 lines
3.5 KiB
C#
using ZB.MOM.WW.ScadaBridge.Commons.Types.Alarms;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer;
|
|
|
|
/// <summary>
|
|
/// Parsed native-alarm condition filter (M2.4 / #8).
|
|
///
|
|
/// <para>
|
|
/// A source's <c>conditionFilter</c> is a comma-separated, case-insensitive list
|
|
/// of alarm/condition <em>type names</em>, matched against
|
|
/// <see cref="NativeAlarmTransition.AlarmTypeName"/>. A <c>null</c>, blank, or
|
|
/// all-empty list means "mirror every condition" (the historical behaviour),
|
|
/// represented here by <see cref="IsEmpty"/>.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// This is the authoritative <em>client-side</em> gate consulted in the
|
|
/// <c>DataConnectionActor</c> routing path, so it applies uniformly across OPC UA
|
|
/// (whose server-side <c>WhereClause</c> is only a bandwidth optimisation) and the
|
|
/// MxGateway (whose single gateway-wide feed has no server-side filter at all).
|
|
/// Parse once at subscribe time; <see cref="IsAllowed"/> is the hot-path check.
|
|
/// </para>
|
|
/// </summary>
|
|
public sealed class AlarmConditionFilter
|
|
{
|
|
/// <summary>The shared allow-all instance (empty filter set).</summary>
|
|
public static readonly AlarmConditionFilter AllowAll = new(new HashSet<string>(StringComparer.OrdinalIgnoreCase));
|
|
|
|
private readonly HashSet<string> _names;
|
|
|
|
private AlarmConditionFilter(HashSet<string> names) => _names = names;
|
|
|
|
/// <summary><c>true</c> when no type names are configured — every condition is allowed.</summary>
|
|
public bool IsEmpty => _names.Count == 0;
|
|
|
|
/// <summary>The normalized (trimmed) type names, for the OPC UA server-side WhereClause optimisation.</summary>
|
|
public IReadOnlyCollection<string> Names => _names;
|
|
|
|
/// <summary>
|
|
/// Parses a raw <c>conditionFilter</c> string into a normalized, case-insensitive
|
|
/// type-name set. <c>null</c>/blank/all-empty input yields an empty (allow-all) filter.
|
|
/// </summary>
|
|
/// <param name="conditionFilter">The raw comma-separated filter string, or <c>null</c>.</param>
|
|
/// <returns>A parsed <see cref="AlarmConditionFilter"/>; never <c>null</c>.</returns>
|
|
public static AlarmConditionFilter Parse(string? conditionFilter)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(conditionFilter))
|
|
return AllowAll;
|
|
|
|
var names = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (var raw in conditionFilter.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
|
|
names.Add(raw);
|
|
|
|
return names.Count == 0 ? AllowAll : new AlarmConditionFilter(names);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns <c>true</c> when <paramref name="transition"/> should be delivered:
|
|
/// the filter is empty (allow all), the transition is a framing sentinel
|
|
/// (<see cref="AlarmTransitionKind.SnapshotComplete"/>, which carries no condition
|
|
/// type and must never be swallowed or the snapshot swap never completes), or its
|
|
/// <see cref="NativeAlarmTransition.AlarmTypeName"/> is in the configured set.
|
|
/// </summary>
|
|
/// <param name="transition">The protocol-neutral transition to test.</param>
|
|
/// <returns><c>true</c> to deliver the transition; <c>false</c> to drop it.</returns>
|
|
public bool IsAllowed(NativeAlarmTransition transition)
|
|
{
|
|
if (_names.Count == 0)
|
|
return true;
|
|
|
|
// SnapshotComplete is pure framing (no condition payload) — never filter it.
|
|
if (transition.Kind == AlarmTransitionKind.SnapshotComplete)
|
|
return true;
|
|
|
|
return _names.Contains(transition.AlarmTypeName);
|
|
}
|
|
}
|