diff --git a/src/Api/Hl7ApplicationConfigEntity.cs b/src/Api/Hl7ApplicationConfigEntity.cs index f7a111ca..80ced9d2 100755 --- a/src/Api/Hl7ApplicationConfigEntity.cs +++ b/src/Api/Hl7ApplicationConfigEntity.cs @@ -16,8 +16,8 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using FellowOakDicom; using Monai.Deploy.InformaticsGateway.Api.Storage; @@ -28,6 +28,13 @@ namespace Monai.Deploy.InformaticsGateway.Api { public class Hl7ApplicationConfigEntity : MongoDBEntityBase { + /// + /// Gets or sets the name of a Hl7 application entity. + /// This value must be unique. + /// + [Key, Column(Order = 0)] + public string Name { get; set; } = default!; + /// /// Gets or sets the sending identifier. /// @@ -48,6 +55,11 @@ public class Hl7ApplicationConfigEntity : MongoDBEntityBase [JsonProperty("data_mapping")] public List DataMapping { get; set; } = new(); + /// + /// Optional list of data input plug-in type names to be executed by the . + /// + public List PlugInAssemblies { get; set; } = default!; + public IEnumerable Validate() { var errors = new List(); diff --git a/src/Database/EntityFramework/Configuration/Hl7ApplicationConfigConfiguration.cs b/src/Database/EntityFramework/Configuration/Hl7ApplicationConfigConfiguration.cs old mode 100644 new mode 100755 index 41ad63ad..3e3528f2 --- a/src/Database/EntityFramework/Configuration/Hl7ApplicationConfigConfiguration.cs +++ b/src/Database/EntityFramework/Configuration/Hl7ApplicationConfigConfiguration.cs @@ -14,7 +14,10 @@ * limitations under the License. */ +using System.Text.Json.Serialization; +using System.Text.Json; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Monai.Deploy.InformaticsGateway.Api; @@ -24,7 +27,24 @@ internal class Hl7ApplicationConfigConfiguration : IEntityTypeConfiguration builder) { + var valueComparer = new ValueComparer>( + (c1, c2) => c1!.SequenceEqual(c2!), + c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())), + c => c.ToList()); + + var jsonSerializerSettings = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + builder.HasKey(j => j.Id); + builder.Property(j => j.PlugInAssemblies) + .HasConversion( + v => JsonSerializer.Serialize(v, jsonSerializerSettings), + v => JsonSerializer.Deserialize>(v, jsonSerializerSettings)!) + .Metadata.SetValueComparer(valueComparer); + + builder.HasIndex(p => p.Name, "idx_hl7_name").IsUnique(); } } } diff --git a/src/InformaticsGateway/Services/Export/ExportServiceBase.cs b/src/InformaticsGateway/Services/Export/ExportServiceBase.cs index e7ccc6f8..935f8781 100755 --- a/src/InformaticsGateway/Services/Export/ExportServiceBase.cs +++ b/src/InformaticsGateway/Services/Export/ExportServiceBase.cs @@ -134,7 +134,9 @@ public async Task StopAsync(CancellationToken cancellationToken) _cancellationTokenSource.Cancel(); _logger.ServiceStopping(ServiceName); Status = ServiceStatus.Stopped; +#pragma warning disable CA2016 // Forward the 'CancellationToken' parameter to methods await Task.Delay(250).ConfigureAwait(false); +#pragma warning restore CA2016 // Forward the 'CancellationToken' parameter to methods _cancellationTokenSource.Dispose(); return; } @@ -459,12 +461,9 @@ private async Task LookupDestinationAsync(string d var repository = scope.ServiceProvider.GetRequiredService(); var destination = await repository.FindByNameAsync(destinationName, cancellationToken).ConfigureAwait(false); - if (destination is null) - { - throw new ConfigurationException($"Specified destination '{destinationName}' does not exist."); - } - - return destination; + return destination is null + ? throw new ConfigurationException($"Specified destination '{destinationName}' does not exist.") + : destination; } protected virtual async Task GetDestination(ExportRequestDataMessage exportRequestData, string destinationName, CancellationToken cancellationToken) diff --git a/src/InformaticsGateway/Services/Export/Hl7ExportService.cs b/src/InformaticsGateway/Services/Export/Hl7ExportService.cs index 65558f17..16ed9a34 100755 --- a/src/InformaticsGateway/Services/Export/Hl7ExportService.cs +++ b/src/InformaticsGateway/Services/Export/Hl7ExportService.cs @@ -89,7 +89,6 @@ protected override async Task HandleDesination(ExportRequestDataMessage exportRe { Guard.Against.Null(exportRequestData, nameof(exportRequestData)); - var manualResetEvent = new ManualResetEvent(false); var destination = await GetHL7Destination(exportRequestData, destinationName, cancellationToken).ConfigureAwait(false); if (destination is null) { @@ -98,7 +97,7 @@ protected override async Task HandleDesination(ExportRequestDataMessage exportRe try { - await ExecuteHl7Export(exportRequestData, manualResetEvent, destination!, cancellationToken).ConfigureAwait(false); + await ExecuteHl7Export(exportRequestData, destination!, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { @@ -108,7 +107,6 @@ protected override async Task HandleDesination(ExportRequestDataMessage exportRe private async Task ExecuteHl7Export( ExportRequestDataMessage exportRequestData, - ManualResetEvent manualResetEvent, HL7DestinationEntity destination, CancellationToken cancellationToken) => await Policy .Handle() @@ -139,12 +137,9 @@ private async Task LookupDestinationAsync(string destinati var repository = scope.ServiceProvider.GetRequiredService(); var destination = await repository.FindByNameAsync(destinationName, cancellationToken).ConfigureAwait(false); - if (destination is null) - { - throw new ConfigurationException($"Specified destination '{destinationName}' does not exist."); - } - - return destination; + return destination is null + ? throw new ConfigurationException($"Specified destination '{destinationName}' does not exist.") + : destination; } private async Task GetHL7Destination(ExportRequestDataMessage exportRequestData, string destinationName, CancellationToken cancellationToken) @@ -160,9 +155,9 @@ private async Task LookupDestinationAsync(string destinati } } - protected override async Task ExecuteOutputDataEngineCallback(ExportRequestDataMessage exportDataRequest) + protected override Task ExecuteOutputDataEngineCallback(ExportRequestDataMessage exportDataRequest) { - return exportDataRequest; + return Task.FromResult(exportDataRequest); } } } diff --git a/src/InformaticsGateway/Services/HealthLevel7/IMllpClientFactory.cs b/src/InformaticsGateway/Services/HealthLevel7/IMllpClientFactory.cs index fd3c09bd..fe2c1b8a 100755 --- a/src/InformaticsGateway/Services/HealthLevel7/IMllpClientFactory.cs +++ b/src/InformaticsGateway/Services/HealthLevel7/IMllpClientFactory.cs @@ -22,12 +22,12 @@ namespace Monai.Deploy.InformaticsGateway.Services.HealthLevel7 { internal interface IMllpClientFactory { - IMllpClient CreateClient(ITcpClientAdapter client, Hl7Configuration configurations, IMllpExtract mIIpExtract, ILogger logger); + IMllpClient CreateClient(ITcpClientAdapter client, Hl7Configuration configurations, ILogger logger); } internal class MllpClientFactory : IMllpClientFactory { - public IMllpClient CreateClient(ITcpClientAdapter client, Hl7Configuration configurations, IMllpExtract mIIpExtract, ILogger logger) - => new MllpClient(client, configurations, mIIpExtract, logger); + public IMllpClient CreateClient(ITcpClientAdapter client, Hl7Configuration configurations, ILogger logger) + => new MllpClient(client, configurations, logger); } } diff --git a/src/InformaticsGateway/Services/HealthLevel7/MllpClient.cs b/src/InformaticsGateway/Services/HealthLevel7/MllpClient.cs index 34d325e7..b3c87372 100755 --- a/src/InformaticsGateway/Services/HealthLevel7/MllpClient.cs +++ b/src/InformaticsGateway/Services/HealthLevel7/MllpClient.cs @@ -38,7 +38,6 @@ internal sealed class MllpClient : IMllpClient private readonly List _exceptions; private readonly List _messages; private readonly IDisposable _loggerScope; - private readonly IMllpExtract _mIIpExtract; private bool _disposedValue; public Guid ClientId { get; } @@ -48,12 +47,11 @@ public string ClientIp get { return _client.RemoteEndPoint.ToString() ?? string.Empty; } } - public MllpClient(ITcpClientAdapter client, Hl7Configuration configurations, IMllpExtract mIIpExtract, ILogger logger) + public MllpClient(ITcpClientAdapter client, Hl7Configuration configurations, ILogger logger) { _client = client ?? throw new ArgumentNullException(nameof(client)); _configurations = configurations ?? throw new ArgumentNullException(nameof(configurations)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _mIIpExtract = mIIpExtract ?? throw new ArgumentNullException(nameof(_mIIpExtract)); ClientId = Guid.NewGuid(); _exceptions = new List(); @@ -112,8 +110,6 @@ private async Task> ReceiveData(INetworkStream clientStream, Canc break; } - linkedCancellationTokenSource.Dispose(); - data += Encoding.UTF8.GetString(messageBuffer.ToArray()); do @@ -147,6 +143,7 @@ private async Task> ReceiveData(INetworkStream clientStream, Canc } } while (true); } + linkedCancellationTokenSource.Dispose(); return messages; } diff --git a/src/InformaticsGateway/Services/HealthLevel7/MllpExtract.cs b/src/InformaticsGateway/Services/HealthLevel7/MllpExtract.cs index 94b2437f..d27e7a71 100755 --- a/src/InformaticsGateway/Services/HealthLevel7/MllpExtract.cs +++ b/src/InformaticsGateway/Services/HealthLevel7/MllpExtract.cs @@ -65,7 +65,7 @@ public async Task ExtractInfo(Hl7FileStorageMetadata meta, Message mess } // extract data for the given fields // Use Id to get record from Db - var details = await GetExtAppDetails(configItem, message); + var details = await GetExtAppDetails(configItem, message).ConfigureAwait(false); if (details is null) { @@ -101,9 +101,9 @@ public async Task ExtractInfo(Hl7FileStorageMetadata meta, Message mess switch (type) { case DataLinkType.PatientId: - return await _externalAppDetailsRepository.GetByPatientIdOutboundAsync(tagId, new CancellationToken()); + return await _externalAppDetailsRepository.GetByPatientIdOutboundAsync(tagId, new CancellationToken()).ConfigureAwait(false); ; case DataLinkType.StudyInstanceUid: - return await _externalAppDetailsRepository.GetByStudyIdOutboundAsync(tagId, new CancellationToken()); + return await _externalAppDetailsRepository.GetByStudyIdOutboundAsync(tagId, new CancellationToken()).ConfigureAwait(false); ; default: break; } @@ -115,7 +115,6 @@ public async Task ExtractInfo(Hl7FileStorageMetadata meta, Message mess { foreach (var item in config) { - var t = message.GetValue(item.SendingId.Key); if (item.SendingId.Value == message.GetValue(item.SendingId.Key)) { return item; diff --git a/src/InformaticsGateway/Services/HealthLevel7/MllpService.cs b/src/InformaticsGateway/Services/HealthLevel7/MllpService.cs index 4f4621d0..8a4a99cd 100755 --- a/src/InformaticsGateway/Services/HealthLevel7/MllpService.cs +++ b/src/InformaticsGateway/Services/HealthLevel7/MllpService.cs @@ -24,8 +24,6 @@ using System.Threading; using System.Threading.Tasks; using Ardalis.GuardClauses; -using CommunityToolkit.HighPerformance.Buffers; -using FellowOakDicom.Network; using HL7.Dotnetcore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -139,7 +137,7 @@ private async Task BackgroundProcessing(CancellationToken cancellationToken) continue; } - mllpClient = _mllpClientFactory.CreateClient(client, _configuration.Value.Hl7, _mIIpExtract, _logginFactory.CreateLogger()); + mllpClient = _mllpClientFactory.CreateClient(client, _configuration.Value.Hl7, _logginFactory.CreateLogger()); _ = mllpClient.Start(OnDisconnect, cancellationToken); _activeTasks.TryAdd(mllpClient.ClientId, mllpClient); } @@ -229,12 +227,9 @@ public async Task SendMllp(IPAddress address, int port, string hl7Message, Cance { try { - using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - linkedCancellationTokenSource.CancelAfter(_configuration.Value.Hl7.ClientTimeoutMilliseconds); - var body = $"{Resources.AsciiVT}{hl7Message}{Resources.AsciiFS}{Resources.AcsiiCR}"; var sendMessageByteBuffer = Encoding.UTF8.GetBytes(body); - await WriteMessage(sendMessageByteBuffer, address, port, linkedCancellationTokenSource.Token).ConfigureAwait(false); + await WriteMessage(sendMessageByteBuffer, address, port).ConfigureAwait(false); } catch (ArgumentOutOfRangeException) { @@ -248,7 +243,7 @@ public async Task SendMllp(IPAddress address, int port, string hl7Message, Cance } } - private async Task WriteMessage(byte[] sendMessageByteBuffer, IPAddress address, int port, CancellationToken linkedCancellationToken) + private async Task WriteMessage(byte[] sendMessageByteBuffer, IPAddress address, int port) { using var tcpClient = new TcpClient(); diff --git a/src/InformaticsGateway/Test/Repositories/MonaiServiceLocatorTest.cs b/src/InformaticsGateway/Test/Repositories/MonaiServiceLocatorTest.cs index 605023bf..d7a3dc91 100755 --- a/src/InformaticsGateway/Test/Repositories/MonaiServiceLocatorTest.cs +++ b/src/InformaticsGateway/Test/Repositories/MonaiServiceLocatorTest.cs @@ -15,7 +15,6 @@ */ using System; -using Microsoft.AspNetCore.Http.Features; using Monai.Deploy.InformaticsGateway.Api.Rest; using Monai.Deploy.InformaticsGateway.Repositories; using Monai.Deploy.InformaticsGateway.Services.Common; diff --git a/src/InformaticsGateway/Test/Services/HealthLevel7/MllpClientTest.cs b/src/InformaticsGateway/Test/Services/HealthLevel7/MllpClientTest.cs index 8b74f950..033efea8 100755 --- a/src/InformaticsGateway/Test/Services/HealthLevel7/MllpClientTest.cs +++ b/src/InformaticsGateway/Test/Services/HealthLevel7/MllpClientTest.cs @@ -20,7 +20,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using FellowOakDicom; using HL7.Dotnetcore; using Microsoft.Extensions.Logging; using Monai.Deploy.InformaticsGateway.Configuration; @@ -54,12 +53,12 @@ public MllpClientTest() [Fact(DisplayName = "Constructor")] public void Constructor() { - Assert.Throws(() => new MllpClient(null, null, null, null)); - Assert.Throws(() => new MllpClient(_tcpClient.Object, null, null, null)); - Assert.Throws(() => new MllpClient(_tcpClient.Object, _config, null, null)); - Assert.Throws(() => new MllpClient(_tcpClient.Object, _config, _mIIpExtract.Object, null)); + Assert.Throws(() => new MllpClient(null, null, null)); + Assert.Throws(() => new MllpClient(_tcpClient.Object, null, null)); + Assert.Throws(() => new MllpClient(_tcpClient.Object, _config, null)); + Assert.Throws(() => new MllpClient(_tcpClient.Object, _config, null)); - new MllpClient(_tcpClient.Object, _config, _mIIpExtract.Object, _logger.Object); + new MllpClient(_tcpClient.Object, _config, _logger.Object); } [Fact(DisplayName = "ReceiveData - records exception thrown by network stream")] @@ -70,7 +69,7 @@ public async Task ReceiveData_ExceptionReadingStream() .ThrowsAsync(new Exception("error")); _tcpClient.Setup(p => p.GetStream()).Returns(stream.Object); - var client = new MllpClient(_tcpClient.Object, _config, _mIIpExtract.Object, _logger.Object); + var client = new MllpClient(_tcpClient.Object, _config, _logger.Object); var action = new Func(async (client, results) => { @@ -93,7 +92,7 @@ public async Task ReceiveData_ZeroByte() .ReturnsAsync(0); _tcpClient.Setup(p => p.GetStream()).Returns(stream.Object); - var client = new MllpClient(_tcpClient.Object, _config, _mIIpExtract.Object, _logger.Object); + var client = new MllpClient(_tcpClient.Object, _config, _logger.Object); var action = new Func(async (client, results) => { @@ -128,7 +127,7 @@ public async Task ReceiveData_InvalidMessage() }); _tcpClient.Setup(p => p.GetStream()).Returns(stream.Object); - var client = new MllpClient(_tcpClient.Object, _config, _mIIpExtract.Object, _logger.Object); + var client = new MllpClient(_tcpClient.Object, _config, _logger.Object); var action = new Func(async (client, results) => { @@ -169,7 +168,7 @@ public async Task ReceiveData_DisabledAck() }); _tcpClient.Setup(p => p.GetStream()).Returns(stream.Object); - var client = new MllpClient(_tcpClient.Object, _config, _mIIpExtract.Object, _logger.Object); + var client = new MllpClient(_tcpClient.Object, _config, _logger.Object); var action = new Func(async (client, results) => { @@ -210,7 +209,7 @@ public async Task ReceiveData_NeverSendAck() }); _tcpClient.Setup(p => p.GetStream()).Returns(stream.Object); - var client = new MllpClient(_tcpClient.Object, _config, _mIIpExtract.Object, _logger.Object); + var client = new MllpClient(_tcpClient.Object, _config, _logger.Object); var action = new Func(async (client, results) => { @@ -251,7 +250,7 @@ public async Task ReceiveData_ExceptionSendingAck() }); _tcpClient.Setup(p => p.GetStream()).Returns(stream.Object); - var client = new MllpClient(_tcpClient.Object, _config, _mIIpExtract.Object, _logger.Object); + var client = new MllpClient(_tcpClient.Object, _config, _logger.Object); var action = new Func(async (client, results) => { @@ -291,7 +290,7 @@ public async Task ReceiveData_CompleteWorkflow() }); _tcpClient.Setup(p => p.GetStream()).Returns(stream.Object); - var client = new MllpClient(_tcpClient.Object, _config, _mIIpExtract.Object, _logger.Object); + var client = new MllpClient(_tcpClient.Object, _config, _logger.Object); var action = new Func(async (client, results) => { @@ -337,7 +336,7 @@ public async Task ReceiveData_CompleteWorkflow_WithMultipleMessages() }); _tcpClient.Setup(p => p.GetStream()).Returns(stream.Object); - var client = new MllpClient(_tcpClient.Object, _config, _mIIpExtract.Object, _logger.Object); + var client = new MllpClient(_tcpClient.Object, _config, _logger.Object); var action = new Func(async (client, results) => { diff --git a/src/InformaticsGateway/Test/Services/HealthLevel7/MllpServiceTest.cs b/src/InformaticsGateway/Test/Services/HealthLevel7/MllpServiceTest.cs index 403f51d7..52757d6d 100755 --- a/src/InformaticsGateway/Test/Services/HealthLevel7/MllpServiceTest.cs +++ b/src/InformaticsGateway/Test/Services/HealthLevel7/MllpServiceTest.cs @@ -18,7 +18,6 @@ using System.Collections.Generic; using System.IO.Abstractions; using System.Net; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using HL7.Dotnetcore; @@ -137,7 +136,7 @@ public async Task GivenTcpConnections_WhenConnectsAndDisconnectsFromMllpService_ var actions = new Dictionary>(); var mllpClients = new List>(); var checkEvent = new CountdownEvent(5); - _mllpClientFactory.Setup(p => p.CreateClient(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) + _mllpClientFactory.Setup(p => p.CreateClient(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns(() => { var client = new Mock(); @@ -185,7 +184,7 @@ public async Task GivenAMllpService_WhenMaximumConnectionLimitIsConfigure_Expect { var checkEvent = new CountdownEvent(_options.Value.Hl7.MaximumNumberOfConnections); var mllpClients = new List>(); - _mllpClientFactory.Setup(p => p.CreateClient(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) + _mllpClientFactory.Setup(p => p.CreateClient(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns(() => { var client = new Mock(); @@ -219,7 +218,7 @@ public async Task GivenConnectedTcpClients_WhenDisconnects_ExpectServiceToDispos var checkEvent = new ManualResetEventSlim(); var client = new Mock(); var callCount = 0; - _mllpClientFactory.Setup(p => p.CreateClient(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) + _mllpClientFactory.Setup(p => p.CreateClient(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns(() => { client.Setup(p => p.Start(It.IsAny>(), It.IsAny())) @@ -279,7 +278,7 @@ public async Task GivenATcpClientWithHl7Messages_WhenDisconnected_ExpectMessageT var client = new Mock(); _mIIpExtract.Setup(e => e.ExtractInfo(It.IsAny(), It.IsAny())) .ReturnsAsync((Hl7FileStorageMetadata meta, Message Msg) => Msg); - _mllpClientFactory.Setup(p => p.CreateClient(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) + _mllpClientFactory.Setup(p => p.CreateClient(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns(() => { client.Setup(p => p.Start(It.IsAny>(), It.IsAny())) @@ -323,7 +322,7 @@ public async Task GivenATcpClientWithHl7Messages_WhenDisconnected_ExpectMessageT _mIIpExtract.Setup(e => e.ExtractInfo(It.IsAny(), It.IsAny())) .ReturnsAsync((Hl7FileStorageMetadata meta, Message Msg) => Msg); - _mllpClientFactory.Setup(p => p.CreateClient(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) + _mllpClientFactory.Setup(p => p.CreateClient(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns(() => { client.Setup(p => p.Start(It.IsAny>(), It.IsAny())) diff --git a/tests/Integration.Test/StepDefinitions/Hl7StepDefinitions.cs b/tests/Integration.Test/StepDefinitions/Hl7StepDefinitions.cs index 4e946900..be93a8bc 100755 --- a/tests/Integration.Test/StepDefinitions/Hl7StepDefinitions.cs +++ b/tests/Integration.Test/StepDefinitions/Hl7StepDefinitions.cs @@ -227,7 +227,7 @@ private async Task SendAcknowledgment(NetworkStream networkStream, HL7.Dotnetcor await networkStream.WriteAsync(ackData, cancellationToken).ConfigureAwait(false); await networkStream.FlushAsync(cancellationToken).ConfigureAwait(false); } - catch (Exception ex) + catch (Exception) { throw; }