Skip to content

Commit 2384817

Browse files
committed
rejig services for better seperation
Signed-off-by: Neil South <[email protected]>
1 parent 3896708 commit 2384817

File tree

9 files changed

+316
-293
lines changed

9 files changed

+316
-293
lines changed

.github/.gitversion.yml

100644100755
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ branches:
1818
main:
1919
tag: ''
2020
release:
21-
tag: rc
21+
tag: 'rc'
2222
develop:
23-
tag: beta
23+
tag: 'beta'
2424
feature:
25-
tag: alpha.{BranchName}
25+
tag: 'alpha.{BranchName}'
2626
pull-request:
27-
tag: pr
27+
tag: 'pr'
2828

2929
ignore:
3030
sha: []

doc/dependency_decisions.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,7 @@
588588
- 8.0.2
589589
- 8.0.3
590590
- 8.0.6
591+
- 8.0.7
591592
:when: 2022-10-14T23:37:16.793Z
592593
:who: mocsharp
593594
:why: MIT (https://github.com/dotnet/runtime/raw/main/LICENSE.TXT)

src/InformaticsGateway/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
using Monai.Deploy.InformaticsGateway.Services.DicomWeb;
3737
using Monai.Deploy.InformaticsGateway.Services.Export;
3838
using Monai.Deploy.InformaticsGateway.Services.Fhir;
39+
using Monai.Deploy.InformaticsGateway.Services.HealthLevel7;
3940
using Monai.Deploy.InformaticsGateway.Services.Http;
4041
using Monai.Deploy.InformaticsGateway.Services.Scp;
4142
using Monai.Deploy.InformaticsGateway.Services.Scu;
@@ -138,7 +139,6 @@ internal static IHostBuilder CreateHostBuilder(string[] args) =>
138139
services.AddSingleton<IScuQueue, ScuQueue>();
139140
services.AddSingleton<IMllpService, MllpService>();
140141

141-
142142
var timeout = TimeSpan.FromSeconds(hostContext.Configuration.GetValue("InformaticsGateway:dicomWeb:clientTimeout", DicomWebConfiguration.DefaultClientTimeout));
143143
services
144144
.AddHttpClient("dicomweb", configure => configure.Timeout = timeout)
@@ -163,7 +163,7 @@ internal static IHostBuilder CreateHostBuilder(string[] args) =>
163163
services.AddHostedService<ScuExportService>();
164164
services.AddHostedService<DicomWebExportService>();
165165
services.AddHostedService<PayloadNotificationService>();
166-
services.AddHostedService<MllpService>();
166+
services.AddHostedService<MllpServiceHost>();
167167
services.AddHostedService<Hl7ExportService>();
168168

169169
})

src/InformaticsGateway/Services/Export/Hl7ExportService.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,37 +28,38 @@
2828
using Monai.Deploy.InformaticsGateway.Configuration;
2929
using Monai.Deploy.InformaticsGateway.Database.Api.Repositories;
3030
using Monai.Deploy.InformaticsGateway.Logging;
31-
using Monai.Deploy.InformaticsGateway.Api.Mllp;
3231
using Monai.Deploy.Messaging.Common;
3332
using Polly;
33+
using Monai.Deploy.InformaticsGateway.Api.Mllp;
3434

3535
namespace Monai.Deploy.InformaticsGateway.Services.Export
3636
{
3737
internal class Hl7ExportService : ExportServiceBase
3838
{
3939
private readonly ILogger<Hl7ExportService> _logger;
4040
private readonly InformaticsGatewayConfiguration _configuration;
41-
private readonly IMllpService _mllpService;
4241

4342
protected override ushort Concurrency { get; }
4443
public override string RoutingKey { get; }
4544
public override string ServiceName => "DICOM Export HL7 Service";
45+
private readonly IMllpService _mllpService;
4646

4747

4848
public Hl7ExportService(
4949
ILogger<Hl7ExportService> logger,
5050
IServiceScopeFactory serviceScopeFactory,
5151
IOptions<InformaticsGatewayConfiguration> configuration,
52-
IDicomToolkit dicomToolkit)
52+
IDicomToolkit dicomToolkit,
53+
IMllpService mllpService)
5354
: base(logger, configuration, serviceScopeFactory, dicomToolkit)
5455
{
5556
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
5657
_configuration = configuration.Value ?? throw new ArgumentNullException(nameof(configuration));
5758

58-
_mllpService = serviceScopeFactory.CreateScope().ServiceProvider.GetRequiredService<IMllpService>();
5959
RoutingKey = $"{configuration.Value.Messaging.Topics.ExportHL7}";
6060
ExportCompleteTopic = $"{configuration.Value.Messaging.Topics.ExportHl7Complete}";
6161
Concurrency = _configuration.Dicom.Scu.MaximumNumberOfAssociations;
62+
_mllpService = mllpService ?? throw new ArgumentNullException(nameof(mllpService));
6263
}
6364

6465

@@ -159,6 +160,5 @@ protected override Task<ExportRequestDataMessage> ExecuteOutputDataEngineCallbac
159160
{
160161
return Task.FromResult(exportDataRequest);
161162
}
162-
163163
}
164164
}
Lines changed: 10 additions & 256 deletions
Original file line numberDiff line numberDiff line change
@@ -1,267 +1,28 @@
1-
/*
2-
* Copyright 2022-2023 MONAI Consortium
3-
*
4-
* Licensed under the Apache License, Version 2.0 (the "License");
5-
* you may not use this file except in compliance with the License.
6-
* You may obtain a copy of the License at
7-
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
9-
*
10-
* Unless required by applicable law or agreed to in writing, software
11-
* distributed under the License is distributed on an "AS IS" BASIS,
12-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
* See the License for the specific language governing permissions and
14-
* limitations under the License.
15-
*/
16-
17-
using System;
18-
using System.Collections.Concurrent;
19-
using System.Collections.Generic;
20-
using System.IO.Abstractions;
21-
using System.Linq;
1+
using System;
222
using System.Net;
233
using System.Net.Sockets;
244
using System.Text;
255
using System.Threading;
266
using System.Threading.Tasks;
27-
using Ardalis.GuardClauses;
287
using HL7.Dotnetcore;
29-
using Microsoft.Extensions.DependencyInjection;
30-
using Microsoft.Extensions.Hosting;
318
using Microsoft.Extensions.Logging;
329
using Microsoft.Extensions.Options;
33-
using Monai.Deploy.InformaticsGateway.Api.PlugIns;
34-
using Monai.Deploy.InformaticsGateway.Api.Rest;
35-
using Monai.Deploy.InformaticsGateway.Api.Storage;
36-
using Monai.Deploy.InformaticsGateway.Common;
10+
using Monai.Deploy.InformaticsGateway.Api.Mllp;
3711
using Monai.Deploy.InformaticsGateway.Configuration;
38-
using Monai.Deploy.InformaticsGateway.Database.Api.Repositories;
3912
using Monai.Deploy.InformaticsGateway.Logging;
40-
using Monai.Deploy.InformaticsGateway.Services.Common;
41-
using Monai.Deploy.InformaticsGateway.Services.Connectors;
42-
using Monai.Deploy.InformaticsGateway.Services.HealthLevel7;
43-
using Monai.Deploy.InformaticsGateway.Services.Storage;
44-
using Monai.Deploy.Messaging.Events;
4513

46-
namespace Monai.Deploy.InformaticsGateway.Api.Mllp
14+
namespace Monai.Deploy.InformaticsGateway.Services.HealthLevel7
4715
{
48-
internal sealed class MllpService : IMllpService, IHostedService, IDisposable, IMonaiService
16+
internal class MllpService : IMllpService
4917
{
50-
private const int SOCKET_OPERATION_CANCELLED = 125;
51-
private bool _disposedValue;
52-
private readonly ITcpListener _tcpListener;
53-
private readonly IMllpClientFactory _mllpClientFactory;
54-
private readonly IObjectUploadQueue _uploadQueue;
55-
private readonly IPayloadAssembler _payloadAssembler;
56-
private readonly IFileSystem _fileSystem;
57-
private readonly ILoggerFactory _logginFactory;
58-
private readonly ILogger<MllpService> _logger;
59-
private readonly IOptions<InformaticsGatewayConfiguration> _configuration;
60-
private readonly IStorageInfoProvider _storageInfoProvider;
61-
private readonly ConcurrentDictionary<Guid, IMllpClient> _activeTasks;
62-
private readonly IMllpExtract _mIIpExtract;
63-
private readonly IInputHL7DataPlugInEngine _inputHL7DataPlugInEngine;
64-
private readonly IHl7ApplicationConfigRepository _hl7ApplicationConfigRepository;
65-
private DateTime _lastConfigRead = new(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc);
66-
67-
public int ActiveConnections
68-
{
69-
get
70-
{
71-
return _activeTasks.Count;
72-
}
73-
}
74-
75-
public ServiceStatus Status { get; set; } = ServiceStatus.Unknown;
76-
77-
public string ServiceName => "HL7 Service";
78-
79-
public MllpService(IServiceScopeFactory serviceScopeFactory, IOptions<InformaticsGatewayConfiguration> configuration)
80-
{
81-
ArgumentNullException.ThrowIfNull(serviceScopeFactory, nameof(serviceScopeFactory));
82-
83-
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
84-
85-
var serviceScope = serviceScopeFactory.CreateScope();
86-
_logginFactory = serviceScope.ServiceProvider.GetService<ILoggerFactory>() ?? throw new ServiceNotFoundException(nameof(ILoggerFactory));
87-
_logger = _logginFactory.CreateLogger<MllpService>();
88-
var tcpListenerFactory = serviceScope.ServiceProvider.GetService<ITcpListenerFactory>() ?? throw new ServiceNotFoundException(nameof(ITcpListenerFactory));
89-
_tcpListener = tcpListenerFactory.CreateTcpListener(System.Net.IPAddress.Any, _configuration.Value.Hl7.Port);
90-
_mllpClientFactory = serviceScope.ServiceProvider.GetService<IMllpClientFactory>() ?? throw new ServiceNotFoundException(nameof(IMllpClientFactory));
91-
_uploadQueue = serviceScope.ServiceProvider.GetService<IObjectUploadQueue>() ?? throw new ServiceNotFoundException(nameof(IObjectUploadQueue));
92-
_payloadAssembler = serviceScope.ServiceProvider.GetService<IPayloadAssembler>() ?? throw new ServiceNotFoundException(nameof(IPayloadAssembler));
93-
_fileSystem = serviceScope.ServiceProvider.GetService<IFileSystem>() ?? throw new ServiceNotFoundException(nameof(IFileSystem));
94-
_storageInfoProvider = serviceScope.ServiceProvider.GetService<IStorageInfoProvider>() ?? throw new ServiceNotFoundException(nameof(IStorageInfoProvider));
95-
_mIIpExtract = serviceScope.ServiceProvider.GetService<IMllpExtract>() ?? throw new ServiceNotFoundException(nameof(IMllpExtract));
96-
_activeTasks = new ConcurrentDictionary<Guid, IMllpClient>();
97-
_inputHL7DataPlugInEngine = serviceScope.ServiceProvider.GetService<IInputHL7DataPlugInEngine>() ?? throw new ServiceNotFoundException(nameof(IInputHL7DataPlugInEngine));
98-
_hl7ApplicationConfigRepository = serviceScope.ServiceProvider.GetService<IHl7ApplicationConfigRepository>() ?? throw new ServiceNotFoundException(nameof(IHl7ApplicationConfigRepository));
99-
}
100-
101-
public Task StartAsync(CancellationToken cancellationToken)
102-
{
103-
var task = Task.Run(async () =>
104-
{
105-
_tcpListener.Start();
106-
await BackgroundProcessing(cancellationToken).ConfigureAwait(true);
107-
}, CancellationToken.None);
108-
109-
Status = ServiceStatus.Running;
110-
_logger.ServiceRunning(ServiceName);
111-
_logger.Hl7ListeningOnPort(_configuration.Value.Hl7.Port);
112-
113-
if (task.IsCompleted)
114-
return task;
115-
return Task.CompletedTask;
116-
}
117-
118-
public Task StopAsync(CancellationToken cancellationToken)
119-
{
120-
_logger.ServiceStopping(ServiceName);
121-
_tcpListener.Stop();
122-
Status = ServiceStatus.Stopped;
123-
return Task.CompletedTask;
124-
}
125-
126-
private async Task BackgroundProcessing(CancellationToken cancellationToken)
127-
{
128-
while (!cancellationToken.IsCancellationRequested)
129-
{
130-
IMllpClient? mllpClient = null;
131-
try
132-
{
133-
WaitUntilAvailable(_configuration.Value.Hl7.MaximumNumberOfConnections);
134-
var client = await _tcpListener.AcceptTcpClientAsync(cancellationToken).ConfigureAwait(false);
135-
_logger.ClientConnected();
136-
137-
if (!_storageInfoProvider.HasSpaceAvailableToStore)
138-
{
139-
_logger.Hl7DisconnectedDueToLowStorageSpace(_storageInfoProvider.AvailableFreeSpace);
140-
client.Close();
141-
await Task.Delay(5000, cancellationToken).ConfigureAwait(false);
142-
continue;
143-
}
144-
145-
mllpClient = _mllpClientFactory.CreateClient(client, _configuration.Value.Hl7, _logginFactory.CreateLogger<MllpClient>());
146-
_ = mllpClient.Start(OnDisconnect, cancellationToken);
147-
_activeTasks.TryAdd(mllpClient.ClientId, mllpClient);
148-
}
149-
catch (System.Net.Sockets.SocketException ex)
150-
{
151-
_logger.Hl7SocketException(ex.Message);
152-
153-
if (mllpClient is not null)
154-
{
155-
mllpClient.Dispose();
156-
_activeTasks.Remove(mllpClient.ClientId, out _);
157-
}
158-
159-
if (ex.ErrorCode == SOCKET_OPERATION_CANCELLED)
160-
{
161-
break;
162-
}
163-
}
164-
catch (Exception ex)
165-
{
166-
_logger.ServiceInvalidOrCancelled(ServiceName, ex);
167-
}
168-
}
169-
Status = ServiceStatus.Cancelled;
170-
_logger.ServiceCancelled(ServiceName);
171-
}
172-
173-
private async Task OnDisconnect(IMllpClient client, MllpClientResult result)
174-
{
175-
Guard.Against.Null(client, nameof(client));
176-
Guard.Against.Null(result, nameof(result));
177-
178-
await ConfigurePlugInEngine().ConfigureAwait(false);
179-
180-
try
181-
{
182-
foreach (var message in result.Messages)
183-
{
184-
var newMessage = message;
185-
var hl7Filemetadata = new Hl7FileStorageMetadata(client.ClientId.ToString(), DataService.HL7, client.ClientIp);
186-
var configItem = await _mIIpExtract.GetConfigItem(message).ConfigureAwait(false);
187-
if (configItem is not null)
188-
{
189-
await _inputHL7DataPlugInEngine.ExecutePlugInsAsync(message, hl7Filemetadata, configItem).ConfigureAwait(false);
190-
newMessage = await _mIIpExtract.ExtractInfo(hl7Filemetadata, message, configItem).ConfigureAwait(false);
191-
192-
_logger.HL7MessageAfterPluginProcessing(newMessage.HL7Message, hl7Filemetadata.CorrelationId);
193-
}
194-
_logger.Hl7MessageReceieved(newMessage.HL7Message);
195-
await hl7Filemetadata.SetDataStream(newMessage.HL7Message, _configuration.Value.Storage.TemporaryDataStorage, _fileSystem, _configuration.Value.Storage.LocalTemporaryStoragePath).ConfigureAwait(false);
196-
var payloadId = await _payloadAssembler.Queue(client.ClientId.ToString(), hl7Filemetadata, new DataOrigin { DataService = DataService.HL7, Source = client.ClientIp, Destination = FileStorageMetadata.IpAddress() }).ConfigureAwait(false);
197-
hl7Filemetadata.PayloadId ??= payloadId.ToString();
198-
_uploadQueue.Queue(hl7Filemetadata);
199-
}
200-
}
201-
catch (Exception ex)
202-
{
203-
_logger.ErrorHandlingHl7Results(ex);
204-
}
205-
finally
206-
{
207-
_activeTasks.Remove(client.ClientId, out _);
208-
_logger.Hl7ClientRemoved(client.ClientId);
209-
client.Dispose();
210-
}
211-
}
21218

213-
private async Task ConfigurePlugInEngine()
214-
{
215-
var configs = await _hl7ApplicationConfigRepository.GetAllAsync().ConfigureAwait(false);
216-
if (configs is not null && configs.Count is not 0 && configs.Max(c => c.LastModified) > _lastConfigRead)
217-
{
218-
var pluginAssemblies = new List<string>();
219-
foreach (var config in configs.Where(p => p.PlugInAssemblies?.Count > 0))
220-
{
221-
try
222-
{
223-
pluginAssemblies.AddRange(config.PlugInAssemblies.Where(p => string.IsNullOrWhiteSpace(p) is false && pluginAssemblies.Contains(p) is false));
224-
}
225-
catch (Exception ex)
226-
{
227-
_logger.HL7PluginLoadingExceptions(ex);
228-
}
229-
}
230-
if (pluginAssemblies.Count is not 0)
231-
{
232-
_inputHL7DataPlugInEngine.Configure(pluginAssemblies);
233-
}
234-
}
235-
_lastConfigRead = DateTime.UtcNow;
236-
}
237-
238-
private void WaitUntilAvailable(int maximumNumberOfConnections)
239-
{
240-
var count = 0;
241-
while (ActiveConnections >= maximumNumberOfConnections)
242-
{
243-
if (++count % 25 == 1)
244-
{
245-
_logger.MaxedOutHl7Connections(maximumNumberOfConnections);
246-
}
247-
Thread.Sleep(100);
248-
}
249-
}
19+
private readonly ILogger<MllpService> _logger;
20+
private readonly InformaticsGatewayConfiguration _configuration;
25021

251-
private void Dispose(bool disposing)
22+
public MllpService(ILogger<MllpService> logger, IOptions<InformaticsGatewayConfiguration> configuration)
25223
{
253-
if (!_disposedValue)
254-
{
255-
if (disposing)
256-
{
257-
foreach (var client in _activeTasks.Values)
258-
{
259-
client.Dispose();
260-
}
261-
}
262-
263-
_disposedValue = true;
264-
}
24+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
25+
_configuration = configuration?.Value ?? throw new ArgumentNullException(nameof(configuration)); ;
26526
}
26627

26728
public async Task SendMllp(IPAddress address, int port, string hl7Message, CancellationToken cancellationToken)
@@ -312,7 +73,7 @@ private async Task WriteMessage(byte[] sendMessageByteBuffer, IPAddress address,
31273
private async Task EnsureAck(NetworkStream networkStream)
31374
{
31475
using var s_cts = new CancellationTokenSource();
315-
s_cts.CancelAfter(_configuration.Value.Hl7.ClientTimeoutMilliseconds);
76+
s_cts.CancelAfter(_configuration.Hl7.ClientTimeoutMilliseconds);
31677
var buffer = new byte[2048];
31778

31879
// get the SentHl7Message
@@ -344,12 +105,5 @@ private async Task EnsureAck(NetworkStream networkStream)
344105
}
345106
throw new Hl7SendException("ACK message contains no ACK!");
346107
}
347-
348-
public void Dispose()
349-
{
350-
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
351-
Dispose(disposing: true);
352-
GC.SuppressFinalize(this);
353-
}
354108
}
355109
}

0 commit comments

Comments
 (0)