Skip to content

Commit

Permalink
adding externalApp plugins
Browse files Browse the repository at this point in the history
Signed-off-by: Neil South <[email protected]>
  • Loading branch information
neildsouth authored and mocsharp committed Aug 8, 2023
1 parent 96adc78 commit 0fa14e9
Show file tree
Hide file tree
Showing 17 changed files with 780 additions and 0 deletions.
5 changes: 5 additions & 0 deletions src/Api/ExportRequestDataMessage.cs
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ public string ExportTaskId
get { return _exportRequest.ExportTaskId; }
}

public string WorkflowInstanceId
{
get { return _exportRequest.WorkflowInstanceId; }
}

public string CorrelationId
{
get { return _exportRequest.CorrelationId; }
Expand Down
21 changes: 21 additions & 0 deletions src/Api/Storage/RemoteAppExecution.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using FellowOakDicom;
using Monai.Deploy.Messaging.Events;

namespace Monai.Deploy.InformaticsGateway.Api.Storage
{
public class RemoteAppExecution
{
public DateTime RequestTime { get; set; } = DateTime.UtcNow;
public string ExportTaskId { get; set; } = string.Empty;
public string WorkflowInstanceId { get; set; } = string.Empty;
public string CorrelationId { get; set; } = string.Empty;
public string? StudyUid { get; set; }
public string? OutgoingStudyUid { get; set; }
public List<DestinationApplicationEntity> ExportDetails { get; set; } = new();
public List<string> Files { get; set; } = new();
public FileExportStatus Status { get; set; }
public Dictionary<DicomTag, string> OriginalValues { get; set; } = new();
}
}
Empty file modified src/Client/Test/packages.lock.json
100644 → 100755
Empty file.
14 changes: 14 additions & 0 deletions src/Database/Api/Repositories/IRemoteAppExecutionRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

using Monai.Deploy.InformaticsGateway.Api.Storage;

namespace Monai.Deploy.InformaticsGateway.Database.Api.Repositories
{
public interface IRemoteAppExecutionRepository
{
Task<bool> AddAsync(RemoteAppExecution item, CancellationToken cancellationToken = default);

Task<RemoteAppExecution?> GetAsync(string OutgoingStudyUid, CancellationToken cancellationToken = default);

Task<int> RemoveAsync(string OutgoingStudyUid, CancellationToken cancellationToken = default);
}
}
2 changes: 2 additions & 0 deletions src/Database/DatabaseManager.cs
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public static IServiceCollection ConfigureDatabase(this IServiceCollection servi
services.AddScoped(typeof(IStorageMetadataRepository), typeof(EntityFramework.Repositories.StorageMetadataWrapperRepository));
services.AddScoped(typeof(IPayloadRepository), typeof(EntityFramework.Repositories.PayloadRepository));
services.AddScoped(typeof(IDicomAssociationInfoRepository), typeof(EntityFramework.Repositories.DicomAssociationInfoRepository));
services.AddScoped(typeof(IRemoteAppExecutionRepository), typeof(EntityFramework.Repositories.RemoteAppExecutionRepository));
return services;

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

return services;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Copyright 2022 MONAI Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using Ardalis.GuardClauses;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Monai.Deploy.InformaticsGateway.Api.Storage;
using Monai.Deploy.InformaticsGateway.Configuration;
using Monai.Deploy.InformaticsGateway.Database.Api.Logging;
using Monai.Deploy.InformaticsGateway.Database.Api.Repositories;
using Polly;
using Polly.Retry;

namespace Monai.Deploy.InformaticsGateway.Database.EntityFramework.Repositories
{
public class RemoteAppExecutionRepository : IRemoteAppExecutionRepository, IDisposable
{
private readonly ILogger<PayloadRepository> _logger;
private readonly IServiceScope _scope;
private readonly InformaticsGatewayContext _informaticsGatewayContext;
private readonly AsyncRetryPolicy _retryPolicy;
private readonly DbSet<RemoteAppExecution> _dataset;
private bool _disposedValue;

public RemoteAppExecutionRepository(
IServiceScopeFactory serviceScopeFactory,
ILogger<PayloadRepository> logger,
IOptions<InformaticsGatewayConfiguration> options)
{
Guard.Against.Null(serviceScopeFactory, nameof(serviceScopeFactory));
Guard.Against.Null(options, nameof(options));

_logger = logger ?? throw new ArgumentNullException(nameof(logger));

_scope = serviceScopeFactory.CreateScope();
_informaticsGatewayContext = _scope.ServiceProvider.GetRequiredService<InformaticsGatewayContext>();
_retryPolicy = Policy.Handle<Exception>().WaitAndRetryAsync(
options.Value.Database.Retries.RetryDelays,
(exception, timespan, count, context) => _logger.DatabaseErrorRetry(timespan, count, exception));
_dataset = _informaticsGatewayContext.Set<RemoteAppExecution>();
}

public async Task<bool> AddAsync(RemoteAppExecution item, CancellationToken cancellationToken = default)
{
Guard.Against.Null(item, nameof(item));

return await _retryPolicy.ExecuteAsync(async () =>
{
var result = await _dataset.AddAsync(item, cancellationToken).ConfigureAwait(false);
await _informaticsGatewayContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
return true;
}).ConfigureAwait(false);
}

public async Task<int> RemoveAsync(string OriginalStudyUid, CancellationToken cancellationToken = default)
{
Guard.Against.Null(OriginalStudyUid, nameof(OriginalStudyUid));

return await _retryPolicy.ExecuteAsync(async () =>
{
var result = await _dataset.SingleOrDefaultAsync(p => p.OutgoingStudyUid == OriginalStudyUid).ConfigureAwait(false);
if (result is not null)
{
_dataset.Remove(result);
await _informaticsGatewayContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
return 1;
}
return 0;
}).ConfigureAwait(false);
}

public async Task<RemoteAppExecution?> GetAsync(string OutgoingStudyUid, CancellationToken cancellationToken = default)
{
Guard.Against.Null(OutgoingStudyUid, nameof(OutgoingStudyUid));

return await _retryPolicy.ExecuteAsync(async () =>
{
var result = await _dataset.SingleOrDefaultAsync(p => p.OutgoingStudyUid == OutgoingStudyUid).ConfigureAwait(false);
if (result is not null)
{
return result;
}
return default;
}).ConfigureAwait(false);
}


protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
_informaticsGatewayContext.Dispose();
_scope.Dispose();
}

_disposedValue = true;
}
}

public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}
104 changes: 104 additions & 0 deletions src/Database/MongoDB/Repositories/RemoteAppExecutionRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using Ardalis.GuardClauses;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Monai.Deploy.InformaticsGateway.Api.Storage;
using Monai.Deploy.InformaticsGateway.Configuration;
using Monai.Deploy.InformaticsGateway.Database.Api.Logging;
using Monai.Deploy.InformaticsGateway.Database.Api.Repositories;
using Monai.Deploy.InformaticsGateway.Database.MongoDB.Configurations;
using MongoDB.Driver;
using Polly;
using Polly.Retry;

namespace Monai.Deploy.InformaticsGateway.Database.MongoDB.Repositories
{
public class RemoteAppExecutionRepository : IRemoteAppExecutionRepository, IDisposable
{
private readonly ILogger<RemoteAppExecutionRepository> _logger;
private readonly IServiceScope _scope;
private readonly AsyncRetryPolicy _retryPolicy;
private readonly IMongoCollection<RemoteAppExecution> _collection;
private bool _disposedValue;

public RemoteAppExecutionRepository(IServiceScopeFactory serviceScopeFactory,
ILogger<RemoteAppExecutionRepository> logger,
IOptions<InformaticsGatewayConfiguration> options,
IOptions<MongoDBOptions> mongoDbOptions)
{
Guard.Against.Null(serviceScopeFactory, nameof(serviceScopeFactory));
Guard.Against.Null(options, nameof(options));
Guard.Against.Null(mongoDbOptions, nameof(mongoDbOptions));

_logger = logger ?? throw new ArgumentNullException(nameof(logger));

_scope = serviceScopeFactory.CreateScope();
_retryPolicy = Policy.Handle<Exception>().WaitAndRetryAsync(
options.Value.Database.Retries.RetryDelays,
(exception, timespan, count, context) => _logger.DatabaseErrorRetry(timespan, count, exception));

var mongoDbClient = _scope.ServiceProvider.GetRequiredService<IMongoClient>();
var mongoDatabase = mongoDbClient.GetDatabase(mongoDbOptions.Value.DaatabaseName);
_collection = mongoDatabase.GetCollection<RemoteAppExecution>(nameof(RemoteAppExecution));
CreateIndexes();
}

private void CreateIndexes()
{
var options = new CreateIndexOptions { Unique = true, ExpireAfter = TimeSpan.FromDays(7), Name = "RequestTime" };
var indexDefinitionState = Builders<RemoteAppExecution>.IndexKeys.Ascending(_ => _.OutgoingStudyUid);
var indexModel = new CreateIndexModel<RemoteAppExecution>(indexDefinitionState, options);

_collection.Indexes.CreateOne(indexModel);
}

public async Task<bool> AddAsync(RemoteAppExecution item, CancellationToken cancellationToken = default)
{
Guard.Against.Null(item, nameof(item));

return await _retryPolicy.ExecuteAsync(async () =>
{
await _collection.InsertOneAsync(item, cancellationToken: cancellationToken).ConfigureAwait(false);
return true;
}).ConfigureAwait(false);
}

public async Task<int> RemoveAsync(string OutgoingStudyUid, CancellationToken cancellationToken = default)
{
return await _retryPolicy.ExecuteAsync(async () =>
{
var results = await _collection.DeleteManyAsync(Builders<RemoteAppExecution>.Filter.Where(p => p.OutgoingStudyUid == OutgoingStudyUid), cancellationToken).ConfigureAwait(false);
return Convert.ToInt32(results.DeletedCount);
}).ConfigureAwait(false);
}

public async Task<RemoteAppExecution?> GetAsync(string OutgoingStudyUid, CancellationToken cancellationToken = default)
{
return await _retryPolicy.ExecuteAsync(async () =>
{
return await _collection.Find(p => p.OutgoingStudyUid == OutgoingStudyUid).FirstOrDefaultAsync().ConfigureAwait(false);
}).ConfigureAwait(false);
}


public void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
_scope.Dispose();
}

_disposedValue = true;
}
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}

}
}
48 changes: 48 additions & 0 deletions src/InformaticsGateway/ExecutionPlugins/ExternalAppIncoming.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using System.Threading.Tasks;
using FellowOakDicom;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Monai.Deploy.InformaticsGateway.Api;
using Monai.Deploy.InformaticsGateway.Api.Storage;
using Monai.Deploy.InformaticsGateway.Database.Api.Repositories;

namespace Monai.Deploy.InformaticsGateway.ExecutionPlugins
{
public class ExternalAppIncoming : IInputDataPlugin
{
private readonly ILogger<ExternalAppIncoming> _logger;
private readonly IServiceScopeFactory _serviceScopeFactory;

public ExternalAppIncoming(
ILogger<ExternalAppIncoming> logger,
IServiceScopeFactory serviceScopeFactory)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory));
}

public async Task<(DicomFile dicomFile, FileStorageMetadata fileMetadata)> Execute(DicomFile dicomFile, FileStorageMetadata fileMetadata)
{
var scope = _serviceScopeFactory.CreateScope();
var repository = scope.ServiceProvider.GetRequiredService<IRemoteAppExecutionRepository>();

var incommingStudyUid = dicomFile.Dataset.GetString(DicomTag.StudyInstanceUID);
var remoteAppExecution = await repository.GetAsync(incommingStudyUid);
if (remoteAppExecution is null)
{
_logger.LogOriginalStudyUidNotFound(incommingStudyUid);
return (dicomFile, fileMetadata);
}
foreach (var key in remoteAppExecution.OriginalValues.Keys)
{
dicomFile.Dataset.AddOrUpdate(key, remoteAppExecution.OriginalValues[key]);
}
dicomFile.Dataset.AddOrUpdate(DicomTag.StudyInstanceUID, remoteAppExecution.StudyUid);
fileMetadata.WorkflowInstanceId = remoteAppExecution.WorkflowInstanceId;
fileMetadata.TaskId = remoteAppExecution.ExportTaskId;

return (dicomFile, fileMetadata);
}
}
}
Loading

0 comments on commit 0fa14e9

Please sign in to comment.