Skip to content

Commit fefb2ea

Browse files
authored
DicomAssociation Controller (#480)
* dicom association controller. Signed-off-by: Lillie Dae <[email protected]> --------- Signed-off-by: Lillie Dae <[email protected]>
1 parent 8ceee8a commit fefb2ea

22 files changed

+963
-1
lines changed

docs/api/rest/dicom-association.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<!--
2+
~ Copyright 2021-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+
# DICOM Association information
18+
19+
The `/dicom-associations' endpoint is for retrieving a list of information regarding dicom
20+
associations.
21+
22+
## GET /dicom-associations/
23+
24+
#### Query Parameters
25+
26+
| Name | Type | Description |
27+
|------------|----------|---------------------------------------------|
28+
| startTime | DateTime | (Optional) Start date to query from. |
29+
| endTime | DateTime | (Optional) End date to query from. |
30+
| pageNumber | Number | (Optional) Page number to query.(default 0) |
31+
| pageSize | Number | (Optional) Page size of query. |
32+
33+
Max & Defaults for PageSize can be set in appSettings.
34+
35+
```json
36+
"endpointSettings": {
37+
"defaultPageSize": number,
38+
"maxPageSize": number
39+
}
40+
```
41+
42+
Endpoint returns a paged result for example
43+
44+
```json
45+
{
46+
"PageNumber": 1,
47+
"PageSize": 10,
48+
"FirstPage": "/payload?pageNumber=1&pageSize=10",
49+
"LastPage": "/payload?pageNumber=1&pageSize=10",
50+
"TotalPages": 1,
51+
"TotalRecords": 3,
52+
"NextPage": null,
53+
"PreviousPage": null,
54+
"Data": [...]
55+
"Succeeded": true,
56+
"Errors": null,
57+
"Message": null
58+
}
59+
```
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2021-2023 MONAI Consortium
3+
* Copyright 2019-2021 NVIDIA Corporation
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
using Microsoft.Extensions.Configuration;
19+
20+
namespace Monai.Deploy.InformaticsGateway.Configuration
21+
{
22+
public class HttpEndpointSettings
23+
{
24+
[ConfigurationKeyName("defaultPageSize")]
25+
public int DefaultPageSize { get; set; } = 10;
26+
27+
[ConfigurationKeyName("maxPageSize")]
28+
public int MaxPageSize { get; set; } = 10;
29+
}
30+
}

src/Configuration/InformaticsGatewayConfiguration.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ public class InformaticsGatewayConfiguration
8282
[ConfigurationKeyName("plugins")]
8383
public PlugInConfiguration PlugInConfigurations { get; set; }
8484

85+
8586
public InformaticsGatewayConfiguration()
8687
{
8788
Dicom = new DicomConfiguration();

src/Database/Api/Repositories/IDicomAssociationInfoRepository.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,20 @@ public interface IDicomAssociationInfoRepository
2323
Task<List<DicomAssociationInfo>> ToListAsync(CancellationToken cancellationToken = default);
2424

2525
Task<DicomAssociationInfo> AddAsync(DicomAssociationInfo item, CancellationToken cancellationToken = default);
26+
27+
/// <summary>
28+
/// Retrieves a list of DicomAssociationInfo in the database.
29+
/// </summary>
30+
Task<IList<DicomAssociationInfo>> GetAllAsync(int skip,
31+
int? limit,
32+
DateTime startTime,
33+
DateTime endTime,
34+
CancellationToken cancellationToken);
35+
36+
/// <summary>
37+
/// Gets count of objects
38+
/// </summary>
39+
/// <returns>Count of objects.</returns>
40+
Task<long> CountAsync();
2641
}
2742
}

src/Database/EntityFramework/Repositories/DicomAssociationInfoRepository.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,24 @@ public async Task<DicomAssociationInfo> AddAsync(DicomAssociationInfo item, Canc
6767
}).ConfigureAwait(false);
6868
}
6969

70+
public async Task<IList<DicomAssociationInfo>> GetAllAsync(int skip,
71+
int? limit,
72+
DateTime startTime,
73+
DateTime endTime,
74+
CancellationToken cancellationToken)
75+
{
76+
return await _dataset
77+
.Where(t =>
78+
t.DateTimeDisconnected >= startTime.ToUniversalTime() &&
79+
t.DateTimeDisconnected <= endTime.ToUniversalTime())
80+
.Skip(skip)
81+
.Take(limit!.Value)
82+
.ToListAsync(cancellationToken)
83+
.ConfigureAwait(false);
84+
}
85+
86+
public Task<long> CountAsync() => _dataset.LongCountAsync();
87+
7088
public async Task<List<DicomAssociationInfo>> ToListAsync(CancellationToken cancellationToken = default)
7189
{
7290
return await _retryPolicy.ExecuteAsync(async () =>

src/Database/EntityFramework/Test/DicomAssociationInfoRepositoryTest.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,27 @@ public DicomAssociationInfoRepositoryTest(SqliteDatabaseFixture databaseFixture)
5959
_logger.Setup(p => p.IsEnabled(It.IsAny<LogLevel>())).Returns(true);
6060
}
6161

62+
[Fact]
63+
public async Task GivenDestinationApplicationEntitiesInTheDatabase_WhenGetAllAsyncCalled_ExpectLimitedEntitiesToBeReturned()
64+
{
65+
var store = new DicomAssociationInfoRepository(_serviceScopeFactory.Object, _logger.Object, _options);
66+
var startTime = DateTime.Now;
67+
var endTime = DateTime.MinValue;
68+
var filter = new Func<DicomAssociationInfo, bool>(t =>
69+
t.DateTimeDisconnected >= startTime.ToUniversalTime() &&
70+
t.DateTimeDisconnected <= endTime.ToUniversalTime());
71+
72+
var expected = _databaseFixture.DatabaseContext.Set<DicomAssociationInfo>()
73+
.Where(filter)
74+
.Skip(0)
75+
.Take(1)
76+
.ToList();
77+
var actual = await store.GetAllAsync(0, 1, startTime, endTime, default).ConfigureAwait(false);
78+
79+
Assert.NotNull(actual);
80+
Assert.Equal(expected, actual);
81+
}
82+
6283
[Fact]
6384
public async Task GivenADicomAssociationInfo_WhenAddingToDatabase_ExpectItToBeSaved()
6485
{

src/Database/MongoDB/Integration.Test/DicomAssociationInfoRepositoryTest.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,25 @@ public async Task GivenADicomAssociationInfo_WhenAddingToDatabase_ExpectItToBeSa
8989
actual!.DateTimeDisconnected.Should().BeCloseTo(association.DateTimeDisconnected, TimeSpan.FromMilliseconds(500));
9090
}
9191

92+
[Fact]
93+
public async Task GivenDestinationApplicationEntitiesInTheDatabase_WhenGetAllAsyncCalled_ExpectLimitedEntitiesToBeReturned()
94+
{
95+
var store = new DicomAssociationInfoRepository(_serviceScopeFactory.Object, _logger.Object, _options, _databaseFixture.Options);
96+
97+
var collection = _databaseFixture.Database.GetCollection<DicomAssociationInfo>(nameof(DicomAssociationInfo));
98+
var startTime = DateTime.Now;
99+
var endTime = DateTime.MinValue;
100+
var builder = Builders<DicomAssociationInfo>.Filter;
101+
var filter = builder.Empty;
102+
filter &= builder.Where(t => t.DateTimeDisconnected >= startTime.ToUniversalTime());
103+
filter &= builder.Where(t => t.DateTimeDisconnected <= endTime.ToUniversalTime());
104+
var expected = await collection.Find(filter).ToListAsync().ConfigureAwait(false);
105+
var actual = await store.GetAllAsync(0, 1, startTime, endTime, default).ConfigureAwait(false);
106+
107+
actual.Should().NotBeNull();
108+
actual.Should().BeEquivalentTo(expected, options => options.Excluding(p => p.DateTimeCreated));
109+
}
110+
92111
[Fact]
93112
public async Task GivenDestinationApplicationEntitiesInTheDatabase_WhenToListIsCalled_ExpectAllEntitiesToBeReturned()
94113
{

src/Database/MongoDB/Repositories/DicomAssociationInfoRepository.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
namespace Monai.Deploy.InformaticsGateway.Database.MongoDB.Repositories
3131
{
32-
public class DicomAssociationInfoRepository : IDicomAssociationInfoRepository, IDisposable
32+
public class DicomAssociationInfoRepository : MongoDBRepositoryBase, IDicomAssociationInfoRepository, IDisposable
3333
{
3434
private readonly ILogger<DicomAssociationInfoRepository> _logger;
3535
private readonly IServiceScope _scope;
@@ -78,6 +78,29 @@ public async Task<List<DicomAssociationInfo>> ToListAsync(CancellationToken canc
7878
}).ConfigureAwait(false);
7979
}
8080

81+
public Task<IList<DicomAssociationInfo>> GetAllAsync(int skip,
82+
int? limit,
83+
DateTime startTime,
84+
DateTime endTime,
85+
CancellationToken cancellationToken)
86+
{
87+
var builder = Builders<DicomAssociationInfo>.Filter;
88+
var filter = builder.Empty;
89+
filter &= builder.Where(t => t.DateTimeDisconnected >= startTime.ToUniversalTime());
90+
filter &= builder.Where(t => t.DateTimeDisconnected <= endTime.ToUniversalTime());
91+
92+
return GetAllAsync(_collection,
93+
filter,
94+
Builders<DicomAssociationInfo>.Sort.Descending(x => x.DateTimeDisconnected),
95+
skip,
96+
limit);
97+
}
98+
99+
public Task<long> CountAsync()
100+
{
101+
return _collection.CountDocumentsAsync(Builders<DicomAssociationInfo>.Filter.Empty);
102+
}
103+
81104
protected virtual void Dispose(bool disposing)
82105
{
83106
if (!_disposedValue)
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2021-2023 MONAI Consortium
3+
* Copyright 2019-2021 NVIDIA Corporation
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
using System.Linq.Expressions;
19+
using MongoDB.Driver;
20+
21+
namespace Monai.Deploy.InformaticsGateway.Database.MongoDB.Repositories
22+
{
23+
public abstract class MongoDBRepositoryBase
24+
{
25+
/// <summary>
26+
/// Get All T that match filters provided.
27+
/// </summary>
28+
/// <typeparam name="T"></typeparam>
29+
/// <param name="collection">Collection to run against.</param>
30+
/// <param name="filterFunction">Filter function you can filter on properties of T.</param>
31+
/// <param name="sortFunction">Function used to sort data.</param>
32+
/// <param name="skip">Items to skip.</param>
33+
/// <param name="limit">Items to limit results by.</param>
34+
/// <returns></returns>
35+
protected static async Task<IList<T>> GetAllAsync<T>(IMongoCollection<T> collection,
36+
Expression<Func<T, bool>>? filterFunction,
37+
SortDefinition<T> sortFunction,
38+
int? skip = null,
39+
int? limit = null)
40+
{
41+
return await collection
42+
.Find(filterFunction)
43+
.Skip(skip)
44+
.Limit(limit)
45+
.Sort(sortFunction)
46+
.ToListAsync().ConfigureAwait(false);
47+
}
48+
49+
protected static async Task<IList<T>> GetAllAsync<T>(IMongoCollection<T> collection,
50+
FilterDefinition<T> filterFunction,
51+
SortDefinition<T> sortFunction,
52+
int? skip = null,
53+
int? limit = null)
54+
{
55+
var result = await collection
56+
.Find(filterFunction)
57+
.Skip(skip)
58+
.Limit(limit)
59+
.Sort(sortFunction)
60+
.ToListAsync().ConfigureAwait(false);
61+
return result;
62+
}
63+
}
64+
}

src/InformaticsGateway/Logging/Log.8000.HttpServices.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,5 +173,11 @@ public static partial class Log
173173

174174
[LoggerMessage(EventId = 8204, Level = LogLevel.Error, Message = "Failed to store FHIR resource.")]
175175
public static partial void ErrorStoringFhirResource(this ILogger logger, Exception ex);
176+
177+
//
178+
// Dicom Associations Controller.
179+
//
180+
[LoggerMessage(EventId = 8300, Level = LogLevel.Error, Message = "Unexpected error occurred in GET /dicom-associations API..")]
181+
public static partial void DicomAssociationsControllerGetError(this ILogger logger, Exception ex);
176182
}
177183
}

src/InformaticsGateway/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ internal static IHostBuilder CreateHostBuilder(string[] args) =>
9797
.ConfigureServices((hostContext, services) =>
9898
{
9999
services.AddOptions<InformaticsGatewayConfiguration>().Bind(hostContext.Configuration.GetSection("InformaticsGateway"));
100+
services.AddOptions<HttpEndpointSettings>().Bind(hostContext.Configuration.GetSection("InformaticsGateway:httpEndpointSettings"));
100101
services.AddOptions<MessageBrokerServiceConfiguration>().Bind(hostContext.Configuration.GetSection("InformaticsGateway:messaging"));
101102
services.AddOptions<StorageServiceConfiguration>().Bind(hostContext.Configuration.GetSection("InformaticsGateway:storage"));
102103
services.AddOptions<AuthenticationOptions>().Bind(hostContext.Configuration.GetSection("MonaiDeployAuthentication"));

0 commit comments

Comments
 (0)