Skip to content

Commit 3b928d1

Browse files
committed
adding externalApp plugins
Signed-off-by: Neil South <[email protected]>
1 parent 8501ed7 commit 3b928d1

17 files changed

+780
-0
lines changed

src/Api/ExportRequestDataMessage.cs

100644100755
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ public string ExportTaskId
4747
get { return _exportRequest.ExportTaskId; }
4848
}
4949

50+
public string WorkflowInstanceId
51+
{
52+
get { return _exportRequest.WorkflowInstanceId; }
53+
}
54+
5055
public string CorrelationId
5156
{
5257
get { return _exportRequest.CorrelationId; }

src/Api/Storage/RemoteAppExecution.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using FellowOakDicom;
4+
using Monai.Deploy.Messaging.Events;
5+
6+
namespace Monai.Deploy.InformaticsGateway.Api.Storage
7+
{
8+
public class RemoteAppExecution
9+
{
10+
public DateTime RequestTime { get; set; } = DateTime.UtcNow;
11+
public string ExportTaskId { get; set; } = string.Empty;
12+
public string WorkflowInstanceId { get; set; } = string.Empty;
13+
public string CorrelationId { get; set; } = string.Empty;
14+
public string? StudyUid { get; set; }
15+
public string? OutgoingStudyUid { get; set; }
16+
public List<DestinationApplicationEntity> ExportDetails { get; set; } = new();
17+
public List<string> Files { get; set; } = new();
18+
public FileExportStatus Status { get; set; }
19+
public Dictionary<DicomTag, string> OriginalValues { get; set; } = new();
20+
}
21+
}

src/Client/Test/packages.lock.json

100644100755
File mode changed.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+

2+
using Monai.Deploy.InformaticsGateway.Api.Storage;
3+
4+
namespace Monai.Deploy.InformaticsGateway.Database.Api.Repositories
5+
{
6+
public interface IRemoteAppExecutionRepository
7+
{
8+
Task<bool> AddAsync(RemoteAppExecution item, CancellationToken cancellationToken = default);
9+
10+
Task<RemoteAppExecution?> GetAsync(string OutgoingStudyUid, CancellationToken cancellationToken = default);
11+
12+
Task<int> RemoveAsync(string OutgoingStudyUid, CancellationToken cancellationToken = default);
13+
}
14+
}

src/Database/DatabaseManager.cs

100644100755
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ public static IServiceCollection ConfigureDatabase(this IServiceCollection servi
7878
services.AddScoped(typeof(IStorageMetadataRepository), typeof(EntityFramework.Repositories.StorageMetadataWrapperRepository));
7979
services.AddScoped(typeof(IPayloadRepository), typeof(EntityFramework.Repositories.PayloadRepository));
8080
services.AddScoped(typeof(IDicomAssociationInfoRepository), typeof(EntityFramework.Repositories.DicomAssociationInfoRepository));
81+
services.AddScoped(typeof(IRemoteAppExecutionRepository), typeof(EntityFramework.Repositories.RemoteAppExecutionRepository));
8182
return services;
8283

8384
case DbType_MongoDb:
@@ -91,6 +92,7 @@ public static IServiceCollection ConfigureDatabase(this IServiceCollection servi
9192
services.AddScoped(typeof(IStorageMetadataRepository), typeof(MongoDB.Repositories.StorageMetadataWrapperRepository));
9293
services.AddScoped(typeof(IPayloadRepository), typeof(MongoDB.Repositories.PayloadRepository));
9394
services.AddScoped(typeof(IDicomAssociationInfoRepository), typeof(MongoDB.Repositories.DicomAssociationInfoRepository));
95+
services.AddScoped(typeof(IRemoteAppExecutionRepository), typeof(MongoDB.Repositories.RemoteAppExecutionRepository));
9496

9597
return services;
9698

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Copyright 2022 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 Ardalis.GuardClauses;
18+
using Microsoft.EntityFrameworkCore;
19+
using Microsoft.Extensions.DependencyInjection;
20+
using Microsoft.Extensions.Logging;
21+
using Microsoft.Extensions.Options;
22+
using Monai.Deploy.InformaticsGateway.Api.Storage;
23+
using Monai.Deploy.InformaticsGateway.Configuration;
24+
using Monai.Deploy.InformaticsGateway.Database.Api.Logging;
25+
using Monai.Deploy.InformaticsGateway.Database.Api.Repositories;
26+
using Polly;
27+
using Polly.Retry;
28+
29+
namespace Monai.Deploy.InformaticsGateway.Database.EntityFramework.Repositories
30+
{
31+
public class RemoteAppExecutionRepository : IRemoteAppExecutionRepository, IDisposable
32+
{
33+
private readonly ILogger<PayloadRepository> _logger;
34+
private readonly IServiceScope _scope;
35+
private readonly InformaticsGatewayContext _informaticsGatewayContext;
36+
private readonly AsyncRetryPolicy _retryPolicy;
37+
private readonly DbSet<RemoteAppExecution> _dataset;
38+
private bool _disposedValue;
39+
40+
public RemoteAppExecutionRepository(
41+
IServiceScopeFactory serviceScopeFactory,
42+
ILogger<PayloadRepository> logger,
43+
IOptions<InformaticsGatewayConfiguration> options)
44+
{
45+
Guard.Against.Null(serviceScopeFactory, nameof(serviceScopeFactory));
46+
Guard.Against.Null(options, nameof(options));
47+
48+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
49+
50+
_scope = serviceScopeFactory.CreateScope();
51+
_informaticsGatewayContext = _scope.ServiceProvider.GetRequiredService<InformaticsGatewayContext>();
52+
_retryPolicy = Policy.Handle<Exception>().WaitAndRetryAsync(
53+
options.Value.Database.Retries.RetryDelays,
54+
(exception, timespan, count, context) => _logger.DatabaseErrorRetry(timespan, count, exception));
55+
_dataset = _informaticsGatewayContext.Set<RemoteAppExecution>();
56+
}
57+
58+
public async Task<bool> AddAsync(RemoteAppExecution item, CancellationToken cancellationToken = default)
59+
{
60+
Guard.Against.Null(item, nameof(item));
61+
62+
return await _retryPolicy.ExecuteAsync(async () =>
63+
{
64+
var result = await _dataset.AddAsync(item, cancellationToken).ConfigureAwait(false);
65+
await _informaticsGatewayContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
66+
return true;
67+
}).ConfigureAwait(false);
68+
}
69+
70+
public async Task<int> RemoveAsync(string OriginalStudyUid, CancellationToken cancellationToken = default)
71+
{
72+
Guard.Against.Null(OriginalStudyUid, nameof(OriginalStudyUid));
73+
74+
return await _retryPolicy.ExecuteAsync(async () =>
75+
{
76+
var result = await _dataset.SingleOrDefaultAsync(p => p.OutgoingStudyUid == OriginalStudyUid).ConfigureAwait(false);
77+
if (result is not null)
78+
{
79+
_dataset.Remove(result);
80+
await _informaticsGatewayContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
81+
return 1;
82+
}
83+
return 0;
84+
}).ConfigureAwait(false);
85+
}
86+
87+
public async Task<RemoteAppExecution?> GetAsync(string OutgoingStudyUid, CancellationToken cancellationToken = default)
88+
{
89+
Guard.Against.Null(OutgoingStudyUid, nameof(OutgoingStudyUid));
90+
91+
return await _retryPolicy.ExecuteAsync(async () =>
92+
{
93+
var result = await _dataset.SingleOrDefaultAsync(p => p.OutgoingStudyUid == OutgoingStudyUid).ConfigureAwait(false);
94+
if (result is not null)
95+
{
96+
return result;
97+
}
98+
return default;
99+
}).ConfigureAwait(false);
100+
}
101+
102+
103+
protected virtual void Dispose(bool disposing)
104+
{
105+
if (!_disposedValue)
106+
{
107+
if (disposing)
108+
{
109+
_informaticsGatewayContext.Dispose();
110+
_scope.Dispose();
111+
}
112+
113+
_disposedValue = true;
114+
}
115+
}
116+
117+
public void Dispose()
118+
{
119+
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
120+
Dispose(disposing: true);
121+
GC.SuppressFinalize(this);
122+
}
123+
}
124+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
using Ardalis.GuardClauses;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using Microsoft.Extensions.Logging;
4+
using Microsoft.Extensions.Options;
5+
using Monai.Deploy.InformaticsGateway.Api.Storage;
6+
using Monai.Deploy.InformaticsGateway.Configuration;
7+
using Monai.Deploy.InformaticsGateway.Database.Api.Logging;
8+
using Monai.Deploy.InformaticsGateway.Database.Api.Repositories;
9+
using Monai.Deploy.InformaticsGateway.Database.MongoDB.Configurations;
10+
using MongoDB.Driver;
11+
using Polly;
12+
using Polly.Retry;
13+
14+
namespace Monai.Deploy.InformaticsGateway.Database.MongoDB.Repositories
15+
{
16+
public class RemoteAppExecutionRepository : IRemoteAppExecutionRepository, IDisposable
17+
{
18+
private readonly ILogger<RemoteAppExecutionRepository> _logger;
19+
private readonly IServiceScope _scope;
20+
private readonly AsyncRetryPolicy _retryPolicy;
21+
private readonly IMongoCollection<RemoteAppExecution> _collection;
22+
private bool _disposedValue;
23+
24+
public RemoteAppExecutionRepository(IServiceScopeFactory serviceScopeFactory,
25+
ILogger<RemoteAppExecutionRepository> logger,
26+
IOptions<InformaticsGatewayConfiguration> options,
27+
IOptions<MongoDBOptions> mongoDbOptions)
28+
{
29+
Guard.Against.Null(serviceScopeFactory, nameof(serviceScopeFactory));
30+
Guard.Against.Null(options, nameof(options));
31+
Guard.Against.Null(mongoDbOptions, nameof(mongoDbOptions));
32+
33+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
34+
35+
_scope = serviceScopeFactory.CreateScope();
36+
_retryPolicy = Policy.Handle<Exception>().WaitAndRetryAsync(
37+
options.Value.Database.Retries.RetryDelays,
38+
(exception, timespan, count, context) => _logger.DatabaseErrorRetry(timespan, count, exception));
39+
40+
var mongoDbClient = _scope.ServiceProvider.GetRequiredService<IMongoClient>();
41+
var mongoDatabase = mongoDbClient.GetDatabase(mongoDbOptions.Value.DaatabaseName);
42+
_collection = mongoDatabase.GetCollection<RemoteAppExecution>(nameof(RemoteAppExecution));
43+
CreateIndexes();
44+
}
45+
46+
private void CreateIndexes()
47+
{
48+
var options = new CreateIndexOptions { Unique = true, ExpireAfter = TimeSpan.FromDays(7), Name = "RequestTime" };
49+
var indexDefinitionState = Builders<RemoteAppExecution>.IndexKeys.Ascending(_ => _.OutgoingStudyUid);
50+
var indexModel = new CreateIndexModel<RemoteAppExecution>(indexDefinitionState, options);
51+
52+
_collection.Indexes.CreateOne(indexModel);
53+
}
54+
55+
public async Task<bool> AddAsync(RemoteAppExecution item, CancellationToken cancellationToken = default)
56+
{
57+
Guard.Against.Null(item, nameof(item));
58+
59+
return await _retryPolicy.ExecuteAsync(async () =>
60+
{
61+
await _collection.InsertOneAsync(item, cancellationToken: cancellationToken).ConfigureAwait(false);
62+
return true;
63+
}).ConfigureAwait(false);
64+
}
65+
66+
public async Task<int> RemoveAsync(string OutgoingStudyUid, CancellationToken cancellationToken = default)
67+
{
68+
return await _retryPolicy.ExecuteAsync(async () =>
69+
{
70+
var results = await _collection.DeleteManyAsync(Builders<RemoteAppExecution>.Filter.Where(p => p.OutgoingStudyUid == OutgoingStudyUid), cancellationToken).ConfigureAwait(false);
71+
return Convert.ToInt32(results.DeletedCount);
72+
}).ConfigureAwait(false);
73+
}
74+
75+
public async Task<RemoteAppExecution?> GetAsync(string OutgoingStudyUid, CancellationToken cancellationToken = default)
76+
{
77+
return await _retryPolicy.ExecuteAsync(async () =>
78+
{
79+
return await _collection.Find(p => p.OutgoingStudyUid == OutgoingStudyUid).FirstOrDefaultAsync().ConfigureAwait(false);
80+
}).ConfigureAwait(false);
81+
}
82+
83+
84+
public void Dispose(bool disposing)
85+
{
86+
if (!_disposedValue)
87+
{
88+
if (disposing)
89+
{
90+
_scope.Dispose();
91+
}
92+
93+
_disposedValue = true;
94+
}
95+
}
96+
public void Dispose()
97+
{
98+
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
99+
Dispose(disposing: true);
100+
GC.SuppressFinalize(this);
101+
}
102+
103+
}
104+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using FellowOakDicom;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using Microsoft.Extensions.Logging;
6+
using Monai.Deploy.InformaticsGateway.Api;
7+
using Monai.Deploy.InformaticsGateway.Api.Storage;
8+
using Monai.Deploy.InformaticsGateway.Database.Api.Repositories;
9+
10+
namespace Monai.Deploy.InformaticsGateway.ExecutionPlugins
11+
{
12+
public class ExternalAppIncoming : IInputDataPlugin
13+
{
14+
private readonly ILogger<ExternalAppIncoming> _logger;
15+
private readonly IServiceScopeFactory _serviceScopeFactory;
16+
17+
public ExternalAppIncoming(
18+
ILogger<ExternalAppIncoming> logger,
19+
IServiceScopeFactory serviceScopeFactory)
20+
{
21+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
22+
_serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory));
23+
}
24+
25+
public async Task<(DicomFile dicomFile, FileStorageMetadata fileMetadata)> Execute(DicomFile dicomFile, FileStorageMetadata fileMetadata)
26+
{
27+
var scope = _serviceScopeFactory.CreateScope();
28+
var repository = scope.ServiceProvider.GetRequiredService<IRemoteAppExecutionRepository>();
29+
30+
var incommingStudyUid = dicomFile.Dataset.GetString(DicomTag.StudyInstanceUID);
31+
var remoteAppExecution = await repository.GetAsync(incommingStudyUid);
32+
if (remoteAppExecution is null)
33+
{
34+
_logger.LogOriginalStudyUidNotFound(incommingStudyUid);
35+
return (dicomFile, fileMetadata);
36+
}
37+
foreach (var key in remoteAppExecution.OriginalValues.Keys)
38+
{
39+
dicomFile.Dataset.AddOrUpdate(key, remoteAppExecution.OriginalValues[key]);
40+
}
41+
dicomFile.Dataset.AddOrUpdate(DicomTag.StudyInstanceUID, remoteAppExecution.StudyUid);
42+
fileMetadata.WorkflowInstanceId = remoteAppExecution.WorkflowInstanceId;
43+
fileMetadata.TaskId = remoteAppExecution.ExportTaskId;
44+
45+
return (dicomFile, fileMetadata);
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)