-
Notifications
You must be signed in to change notification settings - Fork 581
Expand file tree
/
Copy pathSearchParameterStatusManager.cs
More file actions
308 lines (266 loc) · 16.4 KB
/
SearchParameterStatusManager.cs
File metadata and controls
308 lines (266 loc) · 16.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using EnsureThat;
using MediatR;
using Microsoft.Extensions.Logging;
using Microsoft.Health.Core;
using Microsoft.Health.Fhir.Core.Extensions;
using Microsoft.Health.Fhir.Core.Features.Definition;
using Microsoft.Health.Fhir.Core.Features.Search.Parameters;
using Microsoft.Health.Fhir.Core.Messages.Search;
using Microsoft.Health.Fhir.Core.Models;
namespace Microsoft.Health.Fhir.Core.Features.Search.Registry
{
public class SearchParameterStatusManager : INotificationHandler<SearchParameterDefinitionManagerInitialized>, ISearchParameterStatusManager
{
private readonly ISearchParameterStatusDataStore _searchParameterStatusDataStore;
private readonly ISearchParameterDefinitionManager _searchParameterDefinitionManager;
private readonly ISearchParameterSupportResolver _searchParameterSupportResolver;
private readonly IMediator _mediator;
private readonly ILogger<SearchParameterStatusManager> _logger;
private readonly List<string> enabledSortIndices = new List<string>() { "http://hl7.org/fhir/SearchParameter/individual-birthdate", "http://hl7.org/fhir/SearchParameter/individual-family", "http://hl7.org/fhir/SearchParameter/individual-given" };
public SearchParameterStatusManager(
ISearchParameterStatusDataStore searchParameterStatusDataStore,
ISearchParameterDefinitionManager searchParameterDefinitionManager,
ISearchParameterSupportResolver searchParameterSupportResolver,
IMediator mediator,
ILogger<SearchParameterStatusManager> logger)
{
EnsureArg.IsNotNull(searchParameterStatusDataStore, nameof(searchParameterStatusDataStore));
EnsureArg.IsNotNull(searchParameterDefinitionManager, nameof(searchParameterDefinitionManager));
EnsureArg.IsNotNull(searchParameterSupportResolver, nameof(searchParameterSupportResolver));
EnsureArg.IsNotNull(mediator, nameof(mediator));
EnsureArg.IsNotNull(logger, nameof(logger));
_searchParameterStatusDataStore = searchParameterStatusDataStore;
_searchParameterDefinitionManager = searchParameterDefinitionManager;
_searchParameterSupportResolver = searchParameterSupportResolver;
_mediator = mediator;
_logger = logger;
}
internal async Task TryLogEvent(string process, string status, string text, DateTime? startDate, CancellationToken cancellationToken)
{
await _searchParameterStatusDataStore.TryLogEvent(process, status, text, startDate, cancellationToken);
}
internal async Task EnsureInitializedAsync(CancellationToken cancellationToken)
{
var updated = new List<SearchParameterInfo>();
var searchParamResourceStatus = await _searchParameterStatusDataStore.GetSearchParameterStatuses(cancellationToken);
var parameters = searchParamResourceStatus
.ToDictionary(x => x.Uri?.OriginalString, StringComparer.Ordinal);
EnsureArg.IsNotNull(_searchParameterDefinitionManager.AllSearchParameters);
EnsureArg.IsTrue(_searchParameterDefinitionManager.AllSearchParameters.Any());
EnsureArg.IsTrue(parameters.Any());
// Set states of known parameters
foreach (SearchParameterInfo p in _searchParameterDefinitionManager.AllSearchParameters)
{
if (parameters.TryGetValue(p.Url?.OriginalString, out ResourceSearchParameterStatus result))
{
var tempStatus = EvaluateSearchParamStatus(result);
if (result.Status == SearchParameterStatus.Unsupported)
{
// Re-check if this parameter is now supported.
(bool Supported, bool IsPartiallySupported) supportedResult = CheckSearchParameterSupport(p);
tempStatus.IsSupported = supportedResult.Supported;
tempStatus.IsPartiallySupported = supportedResult.IsPartiallySupported;
}
if (p.IsSearchable != tempStatus.IsSearchable ||
p.IsSupported != tempStatus.IsSupported ||
p.IsPartiallySupported != tempStatus.IsPartiallySupported ||
p.SortStatus != result.SortStatus ||
p.SearchParameterStatus != result.Status)
{
p.IsSearchable = tempStatus.IsSearchable;
p.IsSupported = tempStatus.IsSupported;
p.IsPartiallySupported = tempStatus.IsPartiallySupported;
p.SortStatus = result.SortStatus;
p.SearchParameterStatus = result.Status;
updated.Add(p);
}
}
else
{
// ResourceTypeSearchParameter is a special hardcoded parameter added to
// AllSearchParameters by the UrlLookup registration. It has no entry in the
// status store and SearchParameterSupportResolver.IsSearchParameterSupported
// throws "No target resources defined" for it because it has no BaseResourceTypes
// or TargetResourceTypes. Force it to searchable/supported so background tasks
// (which use SearchableSearchParameterDefinitionManager with UsePartialSearchParams=false)
// don't throw SearchParameterNotSupportedException.
if (p.Url == SearchParameterNames.ResourceTypeUri)
{
p.IsSearchable = true;
p.IsSupported = true;
updated.Add(p);
continue;
}
p.IsSearchable = false;
// Check if this parameter is now supported.
(bool Supported, bool IsPartiallySupported) supportedResult = CheckSearchParameterSupport(p);
p.IsSupported = supportedResult.Supported;
p.IsPartiallySupported = supportedResult.IsPartiallySupported;
updated.Add(p);
}
}
var disableSortIndicesList = _searchParameterDefinitionManager.AllSearchParameters.Where(u => enabledSortIndices.Contains(u.Url.ToString()) && u.SortStatus != SortParameterStatus.Enabled);
if (disableSortIndicesList.Any())
{
_logger.LogError("SearchParameterStatusManager: Sort status is not enabled {Environment.NewLine} {Message}", Environment.NewLine, string.Join($"{Environment.NewLine} ", disableSortIndicesList.Select(u => "Url : " + u.Url.ToString() + ", Sort status : " + u.SortStatus.ToString())));
}
await _mediator.Publish(new SearchParametersUpdatedNotification(updated), cancellationToken);
await _mediator.Publish(new SearchParametersInitializedNotification(), cancellationToken);
}
public async Task Handle(SearchParameterDefinitionManagerInitialized notification, CancellationToken cancellationToken)
{
_logger.LogInformation("SearchParameterStatusManager: Search parameter definition manager initialized");
await EnsureInitializedAsync(cancellationToken);
}
public async Task UpdateSearchParameterStatusAsync(IReadOnlyCollection<string> searchParameterUris, SearchParameterStatus status, CancellationToken cancellationToken, bool ignoreSearchParameterNotSupportedException = false)
{
EnsureArg.IsNotNull(searchParameterUris);
if (searchParameterUris.Count == 0)
{
return;
}
var searchParameterStatusList = new List<ResourceSearchParameterStatus>();
var updated = new List<SearchParameterInfo>();
var parameters = (await _searchParameterStatusDataStore.GetSearchParameterStatuses(cancellationToken))
.ToDictionary(x => x.Uri.OriginalString, StringComparer.Ordinal);
foreach (string uri in searchParameterUris)
{
_logger.LogInformation("Setting the search parameter status of '{Uri}' to '{NewStatus}'", uri, status.ToString());
try
{
SearchParameterInfo paramInfo = _searchParameterDefinitionManager.GetSearchParameter(uri);
updated.Add(paramInfo);
paramInfo.IsSearchable = status == SearchParameterStatus.Enabled;
paramInfo.IsSupported = status == SearchParameterStatus.Supported || status == SearchParameterStatus.Enabled;
if (parameters.TryGetValue(uri, out var existingStatus))
{
existingStatus.Status = status;
if (paramInfo.IsSearchable && existingStatus.SortStatus == SortParameterStatus.Supported)
{
existingStatus.SortStatus = SortParameterStatus.Enabled;
paramInfo.SortStatus = SortParameterStatus.Enabled;
}
searchParameterStatusList.Add(existingStatus);
}
else
{
searchParameterStatusList.Add(new ResourceSearchParameterStatus
{
Status = status,
Uri = new Uri(uri),
});
}
}
catch (SearchParameterNotSupportedException ex)
{
_logger.LogError(ex, "The search parameter '{Uri}' not supported.", uri);
// Note: SearchParameterNotSupportedException can be thrown by SearchParameterDefinitionManager.GetSearchParameter
// when the given url is not found in its cache that can happen when the cache becomes out of sync with the store.
// Use this flag to ignore the exception and continue the update process for the rest of search parameters.
// (e.g. $bulk-delete ensuring deletion of as many search parameters as possible.)
if (!ignoreSearchParameterNotSupportedException)
{
throw;
}
}
}
await _searchParameterStatusDataStore.UpsertStatuses(searchParameterStatusList, cancellationToken);
await _mediator.Publish(new SearchParametersUpdatedNotification(updated), cancellationToken);
}
public async Task AddSearchParameterStatusAsync(IReadOnlyCollection<string> searchParamUris, CancellationToken cancellationToken)
{
// new search parameters are added as supported, until reindexing occurs, when
// they will be fully enabled
await UpdateSearchParameterStatusAsync(searchParamUris, SearchParameterStatus.Supported, cancellationToken);
}
public async Task DeleteSearchParameterStatusAsync(string url, CancellationToken cancellationToken)
{
var searchParamUris = new List<string>() { url };
await UpdateSearchParameterStatusAsync(searchParamUris, SearchParameterStatus.Deleted, cancellationToken);
}
public async Task<(IReadOnlyCollection<ResourceSearchParameterStatus> Statuses, DateTimeOffset? LastUpdated)> GetSearchParameterStatusUpdates(CancellationToken cancellationToken, DateTimeOffset? startLastUpdated = null)
{
var searchParamStatuses = await _searchParameterStatusDataStore.GetSearchParameterStatuses(cancellationToken, startLastUpdated);
var lastUpdated = searchParamStatuses.Any() ? searchParamStatuses.Max(_ => _.LastUpdated) : (DateTimeOffset?)null;
return (searchParamStatuses, lastUpdated);
}
// This is just a conveniance wrapper method that should not be going to store directly
public async Task<IReadOnlyCollection<ResourceSearchParameterStatus>> GetAllSearchParameterStatus(CancellationToken cancellationToken)
{
return (await GetSearchParameterStatusUpdates(cancellationToken)).Statuses;
}
/// <summary>
/// Used to apply search parameter status updates to the SearchParameterDefinitionManager.Used in reindex operation when checking every 10 minutes or so.
/// </summary>
/// <param name="updatedSearchParameterStatus">Collection of updated search parameter statuses</param>
/// <param name="cancellationToken">Cancellation Token</param>
public async Task ApplySearchParameterStatus(IReadOnlyCollection<ResourceSearchParameterStatus> updatedSearchParameterStatus, CancellationToken cancellationToken)
{
if (!updatedSearchParameterStatus.Any())
{
_logger.LogDebug("ApplySearchParameterStatus: No search parameter status updates to apply.");
return;
}
var updated = new List<SearchParameterInfo>();
foreach (var paramStatus in updatedSearchParameterStatus)
{
if (_searchParameterDefinitionManager.TryGetSearchParameter(paramStatus.Uri.OriginalString, out var param))
{
var tempStatus = EvaluateSearchParamStatus(paramStatus);
param.IsSearchable = tempStatus.IsSearchable;
param.IsSupported = tempStatus.IsSupported;
param.IsPartiallySupported = tempStatus.IsPartiallySupported;
param.SortStatus = paramStatus.SortStatus;
param.SearchParameterStatus = paramStatus.Status;
updated.Add(param);
}
else if (!updatedSearchParameterStatus.Any(p => p.Uri.Equals(paramStatus.Uri) && (p.Status == SearchParameterStatus.Deleted || p.Status == SearchParameterStatus.Disabled)))
{
// if we cannot find the search parameter in the search parameter definition manager
// and there is an entry in the list of updates with a delete status then it indicates
// the search parameter was deleted before it was added to this instance, and there is no issue
// however if there is no indication that the search parameter was deleted, then there is a problem
_logger.LogError(Core.Resources.UnableToUpdateSearchParameter, paramStatus.Uri);
}
}
_searchParameterStatusDataStore.SyncStatuses(updatedSearchParameterStatus);
_logger.LogDebug("ApplySearchParameterStatus: Synced params. Updated cache timestamp.");
await _mediator.Publish(new SearchParametersUpdatedNotification(updated), cancellationToken);
}
private (bool Supported, bool IsPartiallySupported) CheckSearchParameterSupport(SearchParameterInfo parameterInfo)
{
try
{
return _searchParameterSupportResolver.IsSearchParameterSupported(parameterInfo);
}
catch (Exception ex)
{
_logger.LogWarning("Unable to resolve search parameter {Code}. Exception: {Exception}", parameterInfo?.Code, ex);
return (false, false);
}
}
private static TempStatus EvaluateSearchParamStatus(ResourceSearchParameterStatus paramStatus)
{
TempStatus tempStatus;
tempStatus.IsSearchable = paramStatus.Status == SearchParameterStatus.Enabled;
tempStatus.IsSupported = paramStatus.Status == SearchParameterStatus.Supported || paramStatus.Status == SearchParameterStatus.Enabled;
tempStatus.IsPartiallySupported = paramStatus.IsPartiallySupported;
return tempStatus;
}
private struct TempStatus
{
public bool IsSearchable;
public bool IsSupported;
public bool IsPartiallySupported;
}
}
}