public HomeController(ICalculator calc, 
     IScoped scoped1,
     IScoped scoped2,
     ITransient transient1,
     ITransient transient2)
 {
     _Calculator = calc;
     _ScopedEqual = scoped1 == scoped2;
     _TransientsEqual = transient1 == transient2;
 }
        /// <summary>
        /// Initializes a new instance of the <see cref="CosmosFhirOperationDataStore"/> class.
        /// </summary>
        /// <param name="containerScope">The factory for <see cref="Container"/>.</param>
        /// <param name="cosmosDataStoreConfiguration">The data store configuration.</param>
        /// <param name="namedCosmosCollectionConfigurationAccessor">The IOptions accessor to get a named version.</param>
        /// <param name="retryExceptionPolicyFactory">The retry exception policy factory.</param>
        /// <param name="queryFactory">The Query Factory</param>
        /// <param name="logger">The logger.</param>
        public CosmosFhirOperationDataStore(
            IScoped <Container> containerScope,
            CosmosDataStoreConfiguration cosmosDataStoreConfiguration,
            IOptionsMonitor <CosmosCollectionConfiguration> namedCosmosCollectionConfigurationAccessor,
            RetryExceptionPolicyFactory retryExceptionPolicyFactory,
            ICosmosQueryFactory queryFactory,
            ILogger <CosmosFhirOperationDataStore> logger)
        {
            EnsureArg.IsNotNull(containerScope, nameof(containerScope));
            EnsureArg.IsNotNull(cosmosDataStoreConfiguration, nameof(cosmosDataStoreConfiguration));
            EnsureArg.IsNotNull(namedCosmosCollectionConfigurationAccessor, nameof(namedCosmosCollectionConfigurationAccessor));
            EnsureArg.IsNotNull(retryExceptionPolicyFactory, nameof(retryExceptionPolicyFactory));
            EnsureArg.IsNotNull(logger, nameof(logger));

            _containerScope = containerScope;
            _retryExceptionPolicyFactory = retryExceptionPolicyFactory;
            _queryFactory = queryFactory;
            _logger       = logger;

            CosmosCollectionConfiguration collectionConfiguration = namedCosmosCollectionConfigurationAccessor.Get(Constants.CollectionConfigurationName);

            DatabaseId   = cosmosDataStoreConfiguration.DatabaseId;
            CollectionId = collectionConfiguration.CollectionId;
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="CosmosFhirDataStore"/> class.
        /// </summary>
        /// <param name="containerScope">
        /// A function that returns an <see cref="Container"/>.
        /// Note that this is a function so that the lifetime of the instance is not directly controlled by the IoC container.
        /// </param>
        /// <param name="cosmosDataStoreConfiguration">The data store configuration.</param>
        /// <param name="namedCosmosCollectionConfigurationAccessor">The IOptions accessor to get a named version.</param>
        /// <param name="cosmosQueryFactory">The factory used to create the document query.</param>
        /// <param name="retryExceptionPolicyFactory">The retry exception policy factory.</param>
        /// <param name="logger">The logger instance.</param>
        /// <param name="coreFeatures">The core feature configuration</param>
        public CosmosFhirDataStore(
            IScoped <Container> containerScope,
            CosmosDataStoreConfiguration cosmosDataStoreConfiguration,
            IOptionsMonitor <CosmosCollectionConfiguration> namedCosmosCollectionConfigurationAccessor,
            ICosmosQueryFactory cosmosQueryFactory,
            RetryExceptionPolicyFactory retryExceptionPolicyFactory,
            ILogger <CosmosFhirDataStore> logger,
            IOptions <CoreFeatureConfiguration> coreFeatures)
        {
            EnsureArg.IsNotNull(containerScope, nameof(containerScope));
            EnsureArg.IsNotNull(cosmosDataStoreConfiguration, nameof(cosmosDataStoreConfiguration));
            EnsureArg.IsNotNull(namedCosmosCollectionConfigurationAccessor, nameof(namedCosmosCollectionConfigurationAccessor));
            EnsureArg.IsNotNull(cosmosQueryFactory, nameof(cosmosQueryFactory));
            EnsureArg.IsNotNull(retryExceptionPolicyFactory, nameof(retryExceptionPolicyFactory));
            EnsureArg.IsNotNull(logger, nameof(logger));
            EnsureArg.IsNotNull(coreFeatures, nameof(coreFeatures));

            _containerScope = containerScope;
            _cosmosDataStoreConfiguration = cosmosDataStoreConfiguration;
            _cosmosQueryFactory           = cosmosQueryFactory;
            _retryExceptionPolicyFactory  = retryExceptionPolicyFactory;
            _logger       = logger;
            _coreFeatures = coreFeatures.Value;
        }
Beispiel #4
0
        public ExportJobTask(
            Func <IScoped <IFhirOperationDataStore> > fhirOperationDataStoreFactory,
            IOptions <ExportJobConfiguration> exportJobConfiguration,
            Func <IScoped <ISearchService> > searchServiceFactory,
            IGroupMemberExtractor groupMemberExtractor,
            IResourceToByteArraySerializer resourceToByteArraySerializer,
            IExportDestinationClient exportDestinationClient,
            IResourceDeserializer resourceDeserializer,
            IScoped <IAnonymizerFactory> anonymizerFactory,
            IMediator mediator,
            IFhirRequestContextAccessor contextAccessor,
            ILogger <ExportJobTask> logger)
        {
            EnsureArg.IsNotNull(fhirOperationDataStoreFactory, nameof(fhirOperationDataStoreFactory));
            EnsureArg.IsNotNull(exportJobConfiguration?.Value, nameof(exportJobConfiguration));
            EnsureArg.IsNotNull(searchServiceFactory, nameof(searchServiceFactory));
            EnsureArg.IsNotNull(groupMemberExtractor, nameof(groupMemberExtractor));
            EnsureArg.IsNotNull(resourceToByteArraySerializer, nameof(resourceToByteArraySerializer));
            EnsureArg.IsNotNull(exportDestinationClient, nameof(exportDestinationClient));
            EnsureArg.IsNotNull(resourceDeserializer, nameof(resourceDeserializer));
            EnsureArg.IsNotNull(mediator, nameof(mediator));
            EnsureArg.IsNotNull(contextAccessor, nameof(contextAccessor));
            EnsureArg.IsNotNull(logger, nameof(logger));

            _fhirOperationDataStoreFactory = fhirOperationDataStoreFactory;
            _exportJobConfiguration        = exportJobConfiguration.Value;
            _searchServiceFactory          = searchServiceFactory;
            _groupMemberExtractor          = groupMemberExtractor;
            _resourceToByteArraySerializer = resourceToByteArraySerializer;
            _resourceDeserializer          = resourceDeserializer;
            _exportDestinationClient       = exportDestinationClient;
            _anonymizerFactory             = anonymizerFactory;
            _mediator        = mediator;
            _contextAccessor = contextAccessor;
            _logger          = logger;
        }
Beispiel #5
0
 public RedisCache(IScoped scopedFac, ILog log)
 {
     this._scoped = scopedFac.GetScoped();
     this._log    = log;
 }
Beispiel #6
0
 public async Task <T> Run <T>(IScoped <T, IAtomicContext> command)
 {
     return(await Run(command.Run));
 }
        private async Task RunExportSearch(
            ExportJobConfiguration exportJobConfiguration,
            ExportJobProgress progress,
            List <Tuple <string, string> > sharedQueryParametersList,
            CancellationToken cancellationToken)
        {
            EnsureArg.IsNotNull(exportJobConfiguration, nameof(exportJobConfiguration));
            EnsureArg.IsNotNull(progress, nameof(progress));
            EnsureArg.IsNotNull(sharedQueryParametersList, nameof(sharedQueryParametersList));

            // Current batch will be used to organize a set of search results into a group so that they can be committed together.
            string currentBatchId = progress.Page.ToString("d6");

            List <Tuple <string, string> > queryParametersList = new List <Tuple <string, string> >(sharedQueryParametersList);

            if (progress.ContinuationToken != null)
            {
                queryParametersList.Add(Tuple.Create(KnownQueryParameterNames.ContinuationToken, progress.ContinuationToken));
            }

            if (_exportJobRecord.ExportType == ExportJobType.Patient)
            {
                queryParametersList.Add(Tuple.Create(KnownQueryParameterNames.Type, KnownResourceTypes.Patient));
            }
            else if (_exportJobRecord.ExportType == ExportJobType.All && !string.IsNullOrEmpty(_exportJobRecord.ResourceType))
            {
                queryParametersList.Add(Tuple.Create(KnownQueryParameterNames.Type, _exportJobRecord.ResourceType));
            }

            // Process the export if:
            // 1. There is continuation token, which means there is more resource to be exported.
            // 2. There is no continuation token but the page is 0, which means it's the initial export.
            while (progress.ContinuationToken != null || progress.Page == 0)
            {
                SearchResult searchResult = null;

                // Search and process the results.
                switch (_exportJobRecord.ExportType)
                {
                case ExportJobType.All:
                case ExportJobType.Patient:
                    using (IScoped <ISearchService> searchService = _searchServiceFactory())
                    {
                        searchResult = await searchService.Value.SearchAsync(
                            resourceType : null,
                            queryParametersList,
                            cancellationToken);
                    }

                    break;

                case ExportJobType.Group:
                    searchResult = await GetGroupPatients(
                        _exportJobRecord.GroupId,
                        queryParametersList,
                        _exportJobRecord.QueuedTime,
                        cancellationToken);

                    break;
                }

                if (_exportJobRecord.ExportType == ExportJobType.Patient || _exportJobRecord.ExportType == ExportJobType.Group)
                {
                    uint resultIndex = 0;
                    foreach (SearchResultEntry result in searchResult.Results)
                    {
                        // If a job is resumed in the middle of processing patient compartment resources it will skip patients it has already exported compartment information for.
                        // This assumes the order of the search results is the same every time the same search is performed.
                        if (progress.SubSearch != null && result.Resource.ResourceId != progress.SubSearch.TriggeringResourceId)
                        {
                            resultIndex++;
                            continue;
                        }

                        if (progress.SubSearch == null)
                        {
                            progress.NewSubSearch(result.Resource.ResourceId);
                        }

                        await RunExportCompartmentSearch(exportJobConfiguration, progress.SubSearch, sharedQueryParametersList, cancellationToken, currentBatchId + ":" + resultIndex.ToString("d6"));

                        resultIndex++;

                        progress.ClearSubSearch();
                    }
                }

                await ProcessSearchResultsAsync(searchResult.Results, currentBatchId, cancellationToken);

                if (searchResult.ContinuationToken == null)
                {
                    // No more continuation token, we are done.
                    break;
                }

                await ProcessProgressChange(
                    exportJobConfiguration,
                    progress,
                    queryParametersList,
                    searchResult.ContinuationToken,
                    forceCommit : _exportJobRecord.ExportType == ExportJobType.Patient || _exportJobRecord.ExportType == ExportJobType.Group,
                    cancellationToken);

                currentBatchId = progress.Page.ToString("d6");
            }

            // Commit one last time for any pending changes.
            await _exportDestinationClient.CommitAsync(exportJobConfiguration, cancellationToken);
        }
 public ReindexSearchTests(FhirStorageTestsFixture fixture)
 {
     _scopedDataStore = fixture.DataStore.CreateMockScope();
     _searchService   = fixture.SearchService.CreateMockScope();
     _searchParameterDefinitionManager = fixture.SearchParameterDefinitionManager;
 }
Beispiel #9
0
 public async Task <T> Run <T>(IScoped <T, ITransactionContext> transaction)
 {
     return(await Run(transaction.Run));
 }
        /// <inheritdoc />
        public async Task ExecuteAsync(ReindexJobRecord reindexJobRecord, WeakETag weakETag, CancellationToken cancellationToken)
        {
            EnsureArg.IsNotNull(reindexJobRecord, nameof(reindexJobRecord));
            EnsureArg.IsNotNull(weakETag, nameof(weakETag));

            _reindexJobRecord = reindexJobRecord;
            _weakETag         = weakETag;
            var jobSemaphore = new SemaphoreSlim(1, 1);

            var existingFhirRequestContext = _contextAccessor.FhirRequestContext;

            try
            {
                // Add a request context so Datastore consumption can be added
                var fhirRequestContext = new FhirRequestContext(
                    method: OperationsConstants.Reindex,
                    uriString: "$reindex",
                    baseUriString: "$reindex",
                    correlationId: _reindexJobRecord.Id,
                    requestHeaders: new Dictionary <string, StringValues>(),
                    responseHeaders: new Dictionary <string, StringValues>())
                {
                    IsBackgroundTask = true,
                    AuditEventType   = OperationsConstants.Reindex,
                };

                _contextAccessor.FhirRequestContext = fhirRequestContext;

                using (IScoped <IFhirDataStore> store = _fhirDataStoreFactory())
                {
                    var provisionedCapacity = await store.Value.GetProvisionedDataStoreCapacityAsync(cancellationToken);

                    _throttleController.Initialize(_reindexJobRecord, provisionedCapacity);
                }

                if (_reindexJobRecord.Status != OperationStatus.Running ||
                    _reindexJobRecord.StartTime == null)
                {
                    // update job record to running
                    _reindexJobRecord.Status    = OperationStatus.Running;
                    _reindexJobRecord.StartTime = Clock.UtcNow;
                    await UpdateJobAsync(cancellationToken);
                }

                // If we are resuming a job, we can detect that by checking the progress info from the job record.
                // If no queries have been added to the progress then this is a new job
                if (_reindexJobRecord.QueryList?.Count == 0)
                {
                    // Build query based on new search params
                    // Find supported, but not yet searchable params
                    var notYetIndexedParams = _supportedSearchParameterDefinitionManager.GetSearchParametersRequiringReindexing();

                    // if there are not any parameters which are supported but not yet indexed, then we have nothing to do
                    if (!notYetIndexedParams.Any())
                    {
                        _reindexJobRecord.Error.Add(new OperationOutcomeIssue(
                                                        OperationOutcomeConstants.IssueSeverity.Information,
                                                        OperationOutcomeConstants.IssueType.Informational,
                                                        Resources.NoSearchParametersNeededToBeIndexed));
                        _reindexJobRecord.CanceledTime = DateTimeOffset.UtcNow;
                        await CompleteJobAsync(OperationStatus.Canceled, cancellationToken);

                        return;
                    }

                    // From the param list, get the list of necessary resources which should be
                    // included in our query
                    var resourceList = new HashSet <string>();
                    foreach (var param in notYetIndexedParams)
                    {
                        foreach (var baseResourceType in param.BaseResourceTypes)
                        {
                            if (baseResourceType == KnownResourceTypes.Resource)
                            {
                                resourceList.UnionWith(_modelInfoProvider.GetResourceTypeNames().ToHashSet());

                                // We added all possible resource types, so no need to continue
                                break;
                            }

                            if (baseResourceType == KnownResourceTypes.DomainResource)
                            {
                                var domainResourceChildResourceTypes = _modelInfoProvider.GetResourceTypeNames().ToHashSet();

                                // Remove types that inherit from Resource directly
                                domainResourceChildResourceTypes.Remove(KnownResourceTypes.Binary);
                                domainResourceChildResourceTypes.Remove(KnownResourceTypes.Bundle);
                                domainResourceChildResourceTypes.Remove(KnownResourceTypes.Parameters);

                                resourceList.UnionWith(domainResourceChildResourceTypes);
                            }
                            else
                            {
                                resourceList.UnionWith(new[] { baseResourceType });
                            }
                        }
                    }

                    _reindexJobRecord.Resources.AddRange(resourceList);
                    _reindexJobRecord.SearchParams.AddRange(notYetIndexedParams.Select(p => p.Url.ToString()));

                    await CalculateTotalAndResourceCounts(cancellationToken);

                    if (_reindexJobRecord.Count == 0)
                    {
                        _reindexJobRecord.Error.Add(new OperationOutcomeIssue(
                                                        OperationOutcomeConstants.IssueSeverity.Information,
                                                        OperationOutcomeConstants.IssueType.Informational,
                                                        Resources.NoResourcesNeedToBeReindexed));
                        await UpdateParametersAndCompleteJob(cancellationToken);

                        return;
                    }

                    // Generate separate queries for each resource type and add them to query list.
                    foreach (string resourceType in _reindexJobRecord.Resources)
                    {
                        // Checking resource specific counts is a performance improvement,
                        // so if an entry for this resource failed to get added to the count dictionary, run a query anyways
                        if (!_reindexJobRecord.ResourceCounts.ContainsKey(resourceType) || _reindexJobRecord.ResourceCounts[resourceType] > 0)
                        {
                            var query = new ReindexJobQueryStatus(resourceType, continuationToken: null)
                            {
                                LastModified = Clock.UtcNow,
                                Status       = OperationStatus.Queued,
                            };

                            _reindexJobRecord.QueryList.TryAdd(query, 1);
                        }
                    }

                    await UpdateJobAsync(cancellationToken);

                    _throttleController.UpdateDatastoreUsage();
                }

                var queryTasks = new List <Task <ReindexJobQueryStatus> >();
                var queryCancellationTokens = new Dictionary <ReindexJobQueryStatus, CancellationTokenSource>();

                // while not all queries are finished
                while (_reindexJobRecord.QueryList.Keys.Where(q =>
                                                              q.Status == OperationStatus.Queued ||
                                                              q.Status == OperationStatus.Running).Any())
                {
                    if (_reindexJobRecord.QueryList.Keys.Where(q => q.Status == OperationStatus.Queued).Any())
                    {
                        // grab the next query from the list which is labeled as queued and run it
                        var query = _reindexJobRecord.QueryList.Keys.Where(q => q.Status == OperationStatus.Queued).OrderBy(q => q.LastModified).FirstOrDefault();
                        CancellationTokenSource queryTokensSource = new CancellationTokenSource();
                        queryCancellationTokens.TryAdd(query, queryTokensSource);

#pragma warning disable CS4014 // Suppressed as we want to continue execution and begin processing the next query while this continues to run
                        queryTasks.Add(ProcessQueryAsync(query, jobSemaphore, queryTokensSource.Token));
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed

                        _logger.LogInformation($"Reindex job task created {queryTasks.Count} Tasks");
                    }

                    // reset stale queries to pending
                    var staleQueries = _reindexJobRecord.QueryList.Keys.Where(
                        q => q.Status == OperationStatus.Running && q.LastModified < Clock.UtcNow - _reindexJobConfiguration.JobHeartbeatTimeoutThreshold);
                    foreach (var staleQuery in staleQueries)
                    {
                        await jobSemaphore.WaitAsync();

                        try
                        {
                            // if this query has a created task, cancel it
                            if (queryCancellationTokens.TryGetValue(staleQuery, out var tokenSource))
                            {
                                try
                                {
                                    tokenSource.Cancel(false);
                                }
                                catch
                                {
                                    // may throw exception if the task is disposed
                                }
                            }

                            staleQuery.Status = OperationStatus.Queued;
                            await UpdateJobAsync(cancellationToken);
                        }
                        finally
                        {
                            jobSemaphore.Release();
                        }
                    }

                    var averageDbConsumption = _throttleController.UpdateDatastoreUsage();
                    _logger.LogInformation($"Reindex avaerage DB consumption: {averageDbConsumption}");
                    var throttleDelayTime = _throttleController.GetThrottleBasedDelay();
                    _logger.LogInformation($"Reindex throttle delay: {throttleDelayTime}");
                    await Task.Delay(_reindexJobRecord.QueryDelayIntervalInMilliseconds + throttleDelayTime);

                    // Remove all finished tasks from the collections of tasks
                    // and cancellationTokens
                    if (queryTasks.Count >= reindexJobRecord.MaximumConcurrency)
                    {
                        var taskArray = queryTasks.ToArray();
                        Task.WaitAny(taskArray);
                        var finishedTasks = queryTasks.Where(t => t.IsCompleted).ToArray();
                        foreach (var finishedTask in finishedTasks)
                        {
                            queryTasks.Remove(finishedTask);
                            queryCancellationTokens.Remove(await finishedTask);
                        }
                    }

                    // if our received CancellationToken is cancelled we should
                    // pass that cancellation request onto all the cancellationTokens
                    // for the currently executing threads
                    if (cancellationToken.IsCancellationRequested)
                    {
                        foreach (var tokenSource in queryCancellationTokens.Values)
                        {
                            tokenSource.Cancel(false);
                        }
                    }
                }

                Task.WaitAll(queryTasks.ToArray());

                await jobSemaphore.WaitAsync();

                try
                {
                    await CheckJobCompletionStatus(cancellationToken);
                }
                finally
                {
                    jobSemaphore.Release();
                }
            }
            catch (JobConflictException)
            {
                // The reindex job was updated externally.
                _logger.LogInformation("The job was updated by another process.");
            }
            catch (Exception ex)
            {
                await jobSemaphore.WaitAsync();

                try
                {
                    _reindexJobRecord.Error.Add(new OperationOutcomeIssue(
                                                    OperationOutcomeConstants.IssueSeverity.Error,
                                                    OperationOutcomeConstants.IssueType.Exception,
                                                    ex.Message));

                    _reindexJobRecord.FailureCount++;

                    _logger.LogError(ex, "Encountered an unhandled exception. The job failure count increased to {failureCount}.", _reindexJobRecord.FailureCount);

                    await UpdateJobAsync(cancellationToken);

                    if (_reindexJobRecord.FailureCount >= _reindexJobConfiguration.ConsecutiveFailuresThreshold)
                    {
                        await CompleteJobAsync(OperationStatus.Failed, cancellationToken);
                    }
                    else
                    {
                        _reindexJobRecord.Status = OperationStatus.Queued;
                        await UpdateJobAsync(cancellationToken);
                    }
                }
                finally
                {
                    jobSemaphore.Release();
                }
            }
            finally
            {
                jobSemaphore.Dispose();
                _contextAccessor.FhirRequestContext = existingFhirRequestContext;
            }
        }
Beispiel #11
0
        /// <inheritdoc />
        public async Task ExecuteAsync(ReindexJobRecord reindexJobRecord, WeakETag weakETag, CancellationToken cancellationToken)
        {
            EnsureArg.IsNotNull(reindexJobRecord, nameof(reindexJobRecord));
            EnsureArg.IsNotNull(weakETag, nameof(weakETag));
            if (_reindexJobRecord != null)
            {
                throw new NotSupportedException($"{nameof(ReindexJobTask)} can work only on one {nameof(reindexJobRecord)}. Please create new {nameof(ReindexJobTask)} to process this instance of {nameof(reindexJobRecord)}");
            }

            _reindexJobRecord  = reindexJobRecord;
            _weakETag          = weakETag;
            _jobSemaphore      = new SemaphoreSlim(1, 1);
            _cancellationToken = cancellationToken;

            var originalRequestContext = _contextAccessor.RequestContext;

            try
            {
                // Add a request context so Datastore consumption can be added
                var fhirRequestContext = new FhirRequestContext(
                    method: OperationsConstants.Reindex,
                    uriString: "$reindex",
                    baseUriString: "$reindex",
                    correlationId: _reindexJobRecord.Id,
                    requestHeaders: new Dictionary <string, StringValues>(),
                    responseHeaders: new Dictionary <string, StringValues>())
                {
                    IsBackgroundTask = true,
                    AuditEventType   = OperationsConstants.Reindex,
                };

                _contextAccessor.RequestContext = fhirRequestContext;

                if (reindexJobRecord.TargetDataStoreUsagePercentage != null &&
                    reindexJobRecord.TargetDataStoreUsagePercentage > 0)
                {
                    using (IScoped <IFhirDataStore> store = _fhirDataStoreFactory.Invoke())
                    {
                        var provisionedCapacity = await store.Value.GetProvisionedDataStoreCapacityAsync(_cancellationToken);

                        _throttleController.Initialize(_reindexJobRecord, provisionedCapacity);
                    }
                }
                else
                {
                    _throttleController.Initialize(_reindexJobRecord, null);
                }

                // If we are resuming a job, we can detect that by checking the progress info from the job record.
                // If no queries have been added to the progress then this is a new job
                if (_reindexJobRecord.QueryList?.Count == 0)
                {
                    if (!await TryPopulateNewJobFields())
                    {
                        return;
                    }
                }

                if (_reindexJobRecord.Status != OperationStatus.Running || _reindexJobRecord.StartTime == null)
                {
                    // update job record to running
                    _reindexJobRecord.Status    = OperationStatus.Running;
                    _reindexJobRecord.StartTime = Clock.UtcNow;
                    await UpdateJobAsync();
                }

                await ProcessJob();

                await _jobSemaphore.WaitAsync(_cancellationToken);

                try
                {
                    await CheckJobCompletionStatus();
                }
                finally
                {
                    _jobSemaphore.Release();
                }
            }
            catch (JobConflictException)
            {
                // The reindex job was updated externally.
                _logger.LogInformation("The job was updated by another process.");
            }
            catch (OperationCanceledException)
            {
                _logger.LogInformation("The reindex job was canceled.");
            }
            catch (Exception ex)
            {
                await HandleException(ex);
            }
            finally
            {
                _jobSemaphore.Dispose();
                _contextAccessor.RequestContext = originalRequestContext;
            }
        }
 public IncreaseService(ISingleton singleton, IScoped scoped, ITransient transient)
 {
     this.singleton = singleton;
     this.scoped    = scoped;
     this.transient = transient;
 }
        public ProductController(ITransient transient1, ITransient transient2, ISingleton singleton1, ISingleton singleton2, IScoped scoped1, IScoped scoped2)
        {
            _transient1 = transient1;
            _transient2 = transient2;

            _singleton1 = singleton1;
            _singleton2 = singleton2;

            _scoped1 = scoped1;
            _scoped2 = scoped2;
        }
 public HomeController(ITransient transientService1, ITransient transientService2, ISingleton singletonService1, ISingleton singletonService2, IScoped scopedService1, IScoped scopedService2)
 {
     this.transientService1 = transientService1;
     this.transientService2 = transientService2;
     this.singletonService1 = singletonService1;
     this.singletonService2 = singletonService2;
     this.scopedService1    = scopedService1;
     this.scopedService2    = scopedService2;
 }
Beispiel #15
0
 public DIService(IScoped scoped, ITransient transient, ISingleton singleton)
 {
     this.scoped    = scoped;
     this.transient = transient;
     this.singleton = singleton;
 }
Beispiel #16
0
 public async Task Run(IScoped <IAtomicContext> scoped)
 {
     await Run(scoped.Run);
 }
Beispiel #17
0
        public async Task <IReadOnlyCollection <ResourceSearchParameterStatus> > GetSearchParameterStatuses(CancellationToken cancellationToken)
        {
            // If the search parameter table in SQL does not yet contain status columns
            if (_schemaInformation.Current < SchemaVersionConstants.SearchParameterStatusSchemaVersion)
            {
                // Get status information from file.
                return(await _filebasedSearchParameterStatusDataStore.GetSearchParameterStatuses(cancellationToken));
            }

            using (IScoped <SqlConnectionWrapperFactory> scopedSqlConnectionWrapperFactory = _scopedSqlConnectionWrapperFactory())
                using (SqlConnectionWrapper sqlConnectionWrapper = await scopedSqlConnectionWrapperFactory.Value.ObtainSqlConnectionWrapperAsync(cancellationToken, true))
                    using (SqlCommandWrapper sqlCommandWrapper = sqlConnectionWrapper.CreateSqlCommand())
                    {
                        VLatest.GetSearchParamStatuses.PopulateCommand(sqlCommandWrapper);

                        var parameterStatuses = new List <ResourceSearchParameterStatus>();

                        using (SqlDataReader sqlDataReader = await sqlCommandWrapper.ExecuteReaderAsync(CommandBehavior.SequentialAccess, cancellationToken))
                        {
                            while (await sqlDataReader.ReadAsync(cancellationToken))
                            {
                                short          id;
                                string         uri;
                                string         stringStatus;
                                DateTimeOffset?lastUpdated;
                                bool?          isPartiallySupported;

                                ResourceSearchParameterStatus resourceSearchParameterStatus;

                                if (_schemaInformation.Current >= SchemaVersionConstants.SearchParameterSynchronizationVersion)
                                {
                                    (id, uri, stringStatus, lastUpdated, isPartiallySupported) = sqlDataReader.ReadRow(
                                        VLatest.SearchParam.SearchParamId,
                                        VLatest.SearchParam.Uri,
                                        VLatest.SearchParam.Status,
                                        VLatest.SearchParam.LastUpdated,
                                        VLatest.SearchParam.IsPartiallySupported);

                                    if (string.IsNullOrEmpty(stringStatus) || lastUpdated == null || isPartiallySupported == null)
                                    {
                                        // These columns are nullable because they are added to dbo.SearchParam in a later schema version.
                                        // They should be populated as soon as they are added to the table and should never be null.
                                        throw new SearchParameterNotSupportedException(Resources.SearchParameterStatusShouldNotBeNull);
                                    }

                                    var status = Enum.Parse <SearchParameterStatus>(stringStatus, true);

                                    resourceSearchParameterStatus = new SqlServerResourceSearchParameterStatus
                                    {
                                        Id     = id,
                                        Uri    = new Uri(uri),
                                        Status = status,
                                        IsPartiallySupported = (bool)isPartiallySupported,
                                        LastUpdated          = (DateTimeOffset)lastUpdated,
                                    };
                                }
                                else
                                {
                                    (uri, stringStatus, lastUpdated, isPartiallySupported) = sqlDataReader.ReadRow(
                                        VLatest.SearchParam.Uri,
                                        VLatest.SearchParam.Status,
                                        VLatest.SearchParam.LastUpdated,
                                        VLatest.SearchParam.IsPartiallySupported);

                                    if (string.IsNullOrEmpty(stringStatus) || lastUpdated == null || isPartiallySupported == null)
                                    {
                                        // These columns are nullable because they are added to dbo.SearchParam in a later schema version.
                                        // They should be populated as soon as they are added to the table and should never be null.
                                        throw new SearchParameterNotSupportedException(Resources.SearchParameterStatusShouldNotBeNull);
                                    }

                                    var status = Enum.Parse <SearchParameterStatus>(stringStatus, true);

                                    resourceSearchParameterStatus = new ResourceSearchParameterStatus
                                    {
                                        Uri    = new Uri(uri),
                                        Status = status,
                                        IsPartiallySupported = (bool)isPartiallySupported,
                                        LastUpdated          = (DateTimeOffset)lastUpdated,
                                    };
                                }

                                if (_schemaInformation.Current >= SchemaVersionConstants.AddMinMaxForDateAndStringSearchParamVersion)
                                {
                                    // For schema versions starting from AddMinMaxForDateAndStringSearchParamVersion we will check
                                    // whether the corresponding type of the search parameter is supported.
                                    SearchParameterInfo paramInfo = null;
                                    try
                                    {
                                        paramInfo = _searchParameterDefinitionManager.GetSearchParameter(resourceSearchParameterStatus.Uri.OriginalString);
                                    }
                                    catch (SearchParameterNotSupportedException)
                                    {
                                    }

                                    if (paramInfo != null && SqlServerSortingValidator.SupportedSortParamTypes.Contains(paramInfo.Type))
                                    {
                                        resourceSearchParameterStatus.SortStatus = SortParameterStatus.Enabled;
                                    }
                                    else
                                    {
                                        resourceSearchParameterStatus.SortStatus = SortParameterStatus.Disabled;
                                    }
                                }
                                else
                                {
                                    if (_sortingValidator.SupportedParameterUris.Contains(resourceSearchParameterStatus.Uri))
                                    {
                                        resourceSearchParameterStatus.SortStatus = SortParameterStatus.Enabled;
                                    }
                                    else
                                    {
                                        resourceSearchParameterStatus.SortStatus = SortParameterStatus.Disabled;
                                    }
                                }

                                parameterStatuses.Add(resourceSearchParameterStatus);
                            }
                        }

                        return(parameterStatuses);
                    }
        }
Beispiel #18
0
        private async Task ProcessJob()
        {
            var queryTasks = new List <Task <ReindexJobQueryStatus> >();
            var queryCancellationTokens = new Dictionary <ReindexJobQueryStatus, CancellationTokenSource>();

            // while not all queries are finished
            while (_reindexJobRecord.QueryList.Keys.Where(q =>
                                                          q.Status == OperationStatus.Queued ||
                                                          q.Status == OperationStatus.Running).Any())
            {
                if (_reindexJobRecord.QueryList.Keys.Where(q => q.Status == OperationStatus.Queued).Any())
                {
                    // grab the next query from the list which is labeled as queued and run it
                    var query = _reindexJobRecord.QueryList.Keys.Where(q => q.Status == OperationStatus.Queued).OrderBy(q => q.LastModified).FirstOrDefault();
                    CancellationTokenSource queryTokensSource = new CancellationTokenSource();
                    queryCancellationTokens.TryAdd(query, queryTokensSource);

                    // We don't await ProcessQuery, so query status can or can not be changed inside immediately
                    // In some cases we can go th6rough whole loop and pick same query from query list.
                    // To prevent that we marking query as running here and not inside ProcessQuery code.
                    query.Status       = OperationStatus.Running;
                    query.LastModified = Clock.UtcNow;
#pragma warning disable CS4014 // Suppressed as we want to continue execution and begin processing the next query while this continues to run
                    queryTasks.Add(ProcessQueryAsync(query, queryTokensSource.Token));
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed

                    _logger.LogInformation($"Reindex job task created {queryTasks.Count} Tasks");
                }

                // reset stale queries to pending
                var staleQueries = _reindexJobRecord.QueryList.Keys.Where(
                    q => q.Status == OperationStatus.Running && q.LastModified < Clock.UtcNow - _reindexJobConfiguration.JobHeartbeatTimeoutThreshold);
                foreach (var staleQuery in staleQueries)
                {
                    await _jobSemaphore.WaitAsync(_cancellationToken);

                    try
                    {
                        // if this query has a created task, cancel it
                        if (queryCancellationTokens.TryGetValue(staleQuery, out var tokenSource))
                        {
                            try
                            {
                                tokenSource.Cancel(false);
                            }
                            catch
                            {
                                // may throw exception if the task is disposed
                            }
                        }

                        staleQuery.Status = OperationStatus.Queued;
                        await UpdateJobAsync();
                    }
                    finally
                    {
                        _jobSemaphore.Release();
                    }
                }

                var averageDbConsumption = _throttleController.UpdateDatastoreUsage();
                _logger.LogInformation($"Reindex avaerage DB consumption: {averageDbConsumption}");
                var throttleDelayTime = _throttleController.GetThrottleBasedDelay();
                _logger.LogInformation($"Reindex throttle delay: {throttleDelayTime}");
                await Task.Delay(_reindexJobRecord.QueryDelayIntervalInMilliseconds + throttleDelayTime, _cancellationToken);

                // Remove all finished tasks from the collections of tasks
                // and cancellationTokens
                if (queryTasks.Count >= _reindexJobRecord.MaximumConcurrency)
                {
                    var taskArray = queryTasks.ToArray();
                    Task.WaitAny(taskArray, _cancellationToken);
                    var finishedTasks = queryTasks.Where(t => t.IsCompleted).ToArray();
                    foreach (var finishedTask in finishedTasks)
                    {
                        queryTasks.Remove(finishedTask);
                        queryCancellationTokens.Remove(await finishedTask);
                    }
                }

                // for most cases if another process updates the job (such as a DELETE request)
                // the _etag change will cause a JobConflict exception and this task will be aborted
                // but here we add one more check before attempting to mark the job as complete,
                // or starting another iteration of the loop
                await _jobSemaphore.WaitAsync();

                try
                {
                    using (IScoped <IFhirOperationDataStore> store = _fhirOperationDataStoreFactory.Invoke())
                    {
                        var wrapper = await store.Value.GetReindexJobByIdAsync(_reindexJobRecord.Id, _cancellationToken);

                        _weakETag = wrapper.ETag;
                        _reindexJobRecord.Status = wrapper.JobRecord.Status;
                    }
                }
                catch (Exception)
                {
                    // if something went wrong with fetching job status, we shouldn't fail process loop.
                }
                finally
                {
                    _jobSemaphore.Release();
                }

                // if our received CancellationToken is cancelled, or the job has been marked canceled we should
                // pass that cancellation request onto all the cancellationTokens
                // for the currently executing threads
                if (_cancellationToken.IsCancellationRequested || _reindexJobRecord.Status == OperationStatus.Canceled)
                {
                    foreach (var tokenSource in queryCancellationTokens.Values)
                    {
                        tokenSource.Cancel(false);
                    }

                    _logger.LogInformation("Reindex Job canceled.");
                    throw new OperationCanceledException("ReindexJob canceled.");
                }
            }

            Task.WaitAll(queryTasks.ToArray(), _cancellationToken);
        }
Beispiel #19
0
 public GenerateController(IScoped scoped, ITransient transient, ISingelton singelton)
 {
     _scoped    = scoped;
     _transient = transient;
     _singelton = singelton;
 }
Beispiel #20
0
 public ScopesController(IServiceOne testingServiceOne, IServiceTwo testingServiceTwo, ISingleton singleton, IScoped scoped, ITransient transient)
 {
     this.testingServiceOne = testingServiceOne;
     this.testingServiceTwo = testingServiceTwo;
     this.singleton         = singleton;
     this.scoped            = scoped;
     this.transient         = transient;
 }
Beispiel #21
0
        /// <inheritdoc />
        public async Task ExecuteAsync(ExportJobRecord exportJobRecord, WeakETag weakETag, CancellationToken cancellationToken)
        {
            EnsureArg.IsNotNull(exportJobRecord, nameof(exportJobRecord));

            _exportJobRecord = exportJobRecord;
            _weakETag        = weakETag;

            try
            {
                ExportJobConfiguration exportJobConfiguration = _exportJobConfiguration;

                string connectionHash = string.IsNullOrEmpty(_exportJobConfiguration.StorageAccountConnection) ?
                                        string.Empty :
                                        Microsoft.Health.Core.Extensions.StringExtensions.ComputeHash(_exportJobConfiguration.StorageAccountConnection);

                if (string.IsNullOrEmpty(exportJobRecord.StorageAccountUri))
                {
                    if (!string.Equals(exportJobRecord.StorageAccountConnectionHash, connectionHash, StringComparison.Ordinal))
                    {
                        throw new DestinationConnectionException("Storage account connection string was updated during an export job.", HttpStatusCode.BadRequest);
                    }
                }
                else
                {
                    exportJobConfiguration                   = new ExportJobConfiguration();
                    exportJobConfiguration.Enabled           = _exportJobConfiguration.Enabled;
                    exportJobConfiguration.StorageAccountUri = exportJobRecord.StorageAccountUri;
                }

                // Connect to export destination using appropriate client.
                await _exportDestinationClient.ConnectAsync(exportJobConfiguration, cancellationToken, _exportJobRecord.Id);

                // If we are resuming a job, we can detect that by checking the progress info from the job record.
                // If it is null, then we know we are processing a new job.
                if (_exportJobRecord.Progress == null)
                {
                    _exportJobRecord.Progress = new ExportJobProgress(continuationToken: null, page: 0);
                }

                ExportJobProgress progress = _exportJobRecord.Progress;

                // Current batch will be used to organize a set of search results into a group so that they can be committed together.
                uint currentBatchId = progress.Page;

                // The intial list of query parameters will not have a continutation token. We will add that later if we get one back
                // from the search result.
                var queryParametersList = new List <Tuple <string, string> >()
                {
                    Tuple.Create(KnownQueryParameterNames.Count, _exportJobConfiguration.MaximumNumberOfResourcesPerQuery.ToString(CultureInfo.InvariantCulture)),
                    Tuple.Create(KnownQueryParameterNames.LastUpdated, $"le{_exportJobRecord.QueuedTime.ToString("o", CultureInfo.InvariantCulture)}"),
                };

                if (_exportJobRecord.Since != null)
                {
                    queryParametersList.Add(Tuple.Create(KnownQueryParameterNames.LastUpdated, $"ge{_exportJobRecord.Since}"));
                }

                // Process the export if:
                // 1. There is continuation token, which means there is more resource to be exported.
                // 2. There is no continuation token but the page is 0, which means it's the initial export.
                while (progress.ContinuationToken != null || progress.Page == 0)
                {
                    SearchResult searchResult;

                    // Search and process the results.
                    using (IScoped <ISearchService> searchService = _searchServiceFactory())
                    {
                        searchResult = await searchService.Value.SearchAsync(
                            _exportJobRecord.ResourceType,
                            queryParametersList,
                            cancellationToken);
                    }

                    await ProcessSearchResultsAsync(searchResult.Results, currentBatchId, cancellationToken);

                    if (searchResult.ContinuationToken == null)
                    {
                        // No more continuation token, we are done.
                        break;
                    }

                    // Update the continuation token in local cache and queryParams.
                    // We will add or udpate the continuation token to the end of the query parameters list.
                    progress.UpdateContinuationToken(searchResult.ContinuationToken);
                    if (queryParametersList[queryParametersList.Count - 1].Item1 == KnownQueryParameterNames.ContinuationToken)
                    {
                        queryParametersList[queryParametersList.Count - 1] = Tuple.Create(KnownQueryParameterNames.ContinuationToken, progress.ContinuationToken);
                    }
                    else
                    {
                        queryParametersList.Add(Tuple.Create(KnownQueryParameterNames.ContinuationToken, progress.ContinuationToken));
                    }

                    if (progress.Page % _exportJobConfiguration.NumberOfPagesPerCommit == 0)
                    {
                        // Commit the changes.
                        await _exportDestinationClient.CommitAsync(exportJobConfiguration, cancellationToken);

                        // Update the job record.
                        await UpdateJobRecordAsync(cancellationToken);

                        currentBatchId = progress.Page;
                    }
                }

                // Commit one last time for any pending changes.
                await _exportDestinationClient.CommitAsync(exportJobConfiguration, cancellationToken);

                await CompleteJobAsync(OperationStatus.Completed, cancellationToken);

                _logger.LogTrace("Successfully completed the job.");
            }
            catch (JobConflictException)
            {
                // The export job was updated externally. There might be some additional resources that were exported
                // but we will not be updating the job record.
                _logger.LogTrace("The job was updated by another process.");
            }
            catch (DestinationConnectionException dce)
            {
                _logger.LogError(dce, "Can't connect to destination. The job will be marked as failed.");

                _exportJobRecord.FailureDetails = new JobFailureDetails(dce.Message, dce.StatusCode);
                await CompleteJobAsync(OperationStatus.Failed, cancellationToken);
            }
            catch (Exception ex)
            {
                // The job has encountered an error it cannot recover from.
                // Try to update the job to failed state.
                _logger.LogError(ex, "Encountered an unhandled exception. The job will be marked as failed.");

                _exportJobRecord.FailureDetails = new JobFailureDetails(Resources.UnknownError, HttpStatusCode.InternalServerError);
                await CompleteJobAsync(OperationStatus.Failed, cancellationToken);
            }
        }
Beispiel #22
0
        /// <inheritdoc />
        public async Task ExecuteAsync(ExportJobRecord exportJobRecord, WeakETag weakETag, CancellationToken cancellationToken)
        {
            EnsureArg.IsNotNull(exportJobRecord, nameof(exportJobRecord));

            _exportJobRecord = exportJobRecord;
            _weakETag        = weakETag;

            try
            {
                // Get destination type from secret store and connect to the destination using appropriate client.
                await GetDestinationInfoAndConnectAsync(cancellationToken);

                // If we are resuming a job, we can detect that by checking the progress info from the job record.
                // If it is null, then we know we are processing a new job.
                if (_exportJobRecord.Progress == null)
                {
                    _exportJobRecord.Progress = new ExportJobProgress(continuationToken: null, page: 0);
                }

                ExportJobProgress progress = _exportJobRecord.Progress;

                // Current batch will be used to organize a set of search results into a group so that they can be committed together.
                uint currentBatchId = progress.Page;

                // The first item is placeholder for continuation token so that it can be updated efficiently later.
                var queryParameters = new Tuple <string, string>[]
                {
                    Tuple.Create(KnownQueryParameterNames.ContinuationToken, progress.ContinuationToken),
                    Tuple.Create(KnownQueryParameterNames.Count, _exportJobConfiguration.MaximumNumberOfResourcesPerQuery.ToString(CultureInfo.InvariantCulture)),
                    Tuple.Create(KnownQueryParameterNames.LastUpdated, $"le{_exportJobRecord.QueuedTime.ToString("o", CultureInfo.InvariantCulture)}"),
                };

                // Process the export if:
                // 1. There is continuation token, which means there is more resource to be exported.
                // 2. There is no continuation token but the page is 0, which means it's the initial export.
                while (progress.ContinuationToken != null || progress.Page == 0)
                {
                    SearchResult searchResult;

                    // Search and process the results.
                    using (IScoped <ISearchService> searchService = _searchServiceFactory())
                    {
                        // If the continuation token is null, then we will exclude it. Calculate the offset and count to be passed in.
                        int offset = queryParameters[0].Item2 == null ? 1 : 0;

                        searchResult = await searchService.Value.SearchAsync(
                            _exportJobRecord.ResourceType,
                            new ArraySegment <Tuple <string, string> >(queryParameters, offset, queryParameters.Length - offset),
                            cancellationToken);
                    }

                    await ProcessSearchResultsAsync(searchResult.Results, currentBatchId, cancellationToken);

                    if (searchResult.ContinuationToken == null)
                    {
                        // No more continuation token, we are done.
                        break;
                    }

                    // Update the continuation token (local cache).
                    progress.UpdateContinuationToken(searchResult.ContinuationToken);
                    queryParameters[0] = Tuple.Create(KnownQueryParameterNames.ContinuationToken, progress.ContinuationToken);

                    if (progress.Page % _exportJobConfiguration.NumberOfPagesPerCommit == 0)
                    {
                        // Commit the changes.
                        await _exportDestinationClient.CommitAsync(cancellationToken);

                        // Update the job record.
                        await UpdateJobRecordAsync(cancellationToken);

                        currentBatchId = progress.Page;
                    }
                }

                // Commit one last time for any pending changes.
                await _exportDestinationClient.CommitAsync(cancellationToken);

                await CompleteJobAsync(OperationStatus.Completed, cancellationToken);

                _logger.LogTrace("Successfully completed the job.");

                try
                {
                    // Best effort to delete the secret. If it fails to delete, then move on.
                    await _secretStore.DeleteSecretAsync(_exportJobRecord.SecretName, cancellationToken);
                }
                catch (Exception ex)
                {
                    _logger.LogWarning(ex, "Failed to delete the secret.");
                }
            }
            catch (JobConflictException)
            {
                // The export job was updated externally. There might be some additional resources that were exported
                // but we will not be updating the job record.
                _logger.LogTrace("The job was updated by another process.");
            }
            catch (Exception ex)
            {
                // The job has encountered an error it cannot recover from.
                // Try to update the job to failed state.
                _logger.LogError(ex, "Encountered an unhandled exception. The job will be marked as failed.");

                await CompleteJobAsync(OperationStatus.Failed, cancellationToken);
            }
        }
Beispiel #23
0
 public async Task Run(IScoped <ITransactionContext> scoped)
 {
     await Run(scoped.Run);
 }
Beispiel #24
0
        private async Task SearchWithFilter(
            ExportJobConfiguration exportJobConfiguration,
            ExportJobProgress progress,
            string resourceType,
            List <Tuple <string, string> > queryParametersList,
            List <Tuple <string, string> > sharedQueryParametersList,
            IAnonymizer anonymizer,
            string batchIdPrefix,
            CancellationToken cancellationToken)
        {
            // Current batch will be used to organize a set of search results into a group so that they can be committed together.
            string currentBatchId = batchIdPrefix + progress.Page.ToString("d6");

            // Process the export if:
            // 1. There is continuation token, which means there is more resource to be exported.
            // 2. There is no continuation token but the page is 0, which means it's the initial export.
            while (progress.ContinuationToken != null || progress.Page == 0)
            {
                SearchResult searchResult = null;

                // Search and process the results.
                switch (_exportJobRecord.ExportType)
                {
                case ExportJobType.All:
                case ExportJobType.Patient:
                    using (IScoped <ISearchService> searchService = _searchServiceFactory())
                    {
                        searchResult = await searchService.Value.SearchAsync(
                            resourceType : resourceType,
                            queryParametersList,
                            cancellationToken);
                    }

                    break;

                case ExportJobType.Group:
                    searchResult = await GetGroupPatients(
                        _exportJobRecord.GroupId,
                        queryParametersList,
                        _exportJobRecord.QueuedTime,
                        cancellationToken);

                    break;
                }

                if (_exportJobRecord.ExportType == ExportJobType.Patient || _exportJobRecord.ExportType == ExportJobType.Group)
                {
                    uint resultIndex = 0;
                    foreach (SearchResultEntry result in searchResult.Results)
                    {
                        // If a job is resumed in the middle of processing patient compartment resources it will skip patients it has already exported compartment information for.
                        // This assumes the order of the search results is the same every time the same search is performed.
                        if (progress.SubSearch != null && result.Resource.ResourceId != progress.SubSearch.TriggeringResourceId)
                        {
                            resultIndex++;
                            continue;
                        }

                        if (progress.SubSearch == null)
                        {
                            progress.NewSubSearch(result.Resource.ResourceId);
                        }

                        await RunExportCompartmentSearch(exportJobConfiguration, progress.SubSearch, sharedQueryParametersList, anonymizer, cancellationToken, currentBatchId + ":" + resultIndex.ToString("d6"));

                        resultIndex++;

                        progress.ClearSubSearch();
                    }
                }

                // Skips processing top level search results if the job only requested resources from the compartments of patients, but didn't want the patients.
                if (_exportJobRecord.ExportType == ExportJobType.All ||
                    string.IsNullOrWhiteSpace(_exportJobRecord.ResourceType) ||
                    _exportJobRecord.ResourceType.Contains(KnownResourceTypes.Patient, StringComparison.OrdinalIgnoreCase))
                {
                    await ProcessSearchResultsAsync(searchResult.Results, currentBatchId, anonymizer, cancellationToken);
                }

                if (searchResult.ContinuationToken == null)
                {
                    // No more continuation token, we are done.
                    break;
                }

                await ProcessProgressChange(
                    exportJobConfiguration,
                    progress,
                    queryParametersList,
                    searchResult.ContinuationToken,
                    forceCommit : _exportJobRecord.ExportType == ExportJobType.Patient || _exportJobRecord.ExportType == ExportJobType.Group,
                    cancellationToken);

                currentBatchId = batchIdPrefix + progress.Page.ToString("d6");
            }

            // Commit one last time for any pending changes.
            await _exportDestinationClient.CommitAsync(exportJobConfiguration, cancellationToken);
        }
        public async Task ValidateSearchParamterInput(SearchParameter searchParam, string method, CancellationToken cancellationToken)
        {
            if (await _authorizationService.CheckAccess(DataActions.Reindex) != DataActions.Reindex)
            {
                throw new UnauthorizedFhirActionException();
            }

            // check if reindex job is running
            using (IScoped <IFhirOperationDataStore> fhirOperationDataStore = _fhirOperationDataStoreFactory())
            {
                (var activeReindexJobs, var reindexJobId) = await fhirOperationDataStore.Value.CheckActiveReindexJobsAsync(cancellationToken);

                if (activeReindexJobs)
                {
                    throw new JobConflictException(string.Format(Resources.ChangesToSearchParametersNotAllowedWhileReindexing, reindexJobId));
                }
            }

            var validationFailures = new List <ValidationFailure>();

            if (string.IsNullOrEmpty(searchParam.Url))
            {
                validationFailures.Add(
                    new ValidationFailure(nameof(Base.TypeName), Resources.SearchParameterDefinitionInvalidMissingUri));
            }
            else
            {
                try
                {
                    _searchParameterDefinitionManager.GetSearchParameter(new Uri(searchParam.Url));

                    // If a post, then it is a creation of a new search parameter
                    // only allow this if no other parameters exist with the same Uri
                    if (method.Equals(HttpPostName, StringComparison.OrdinalIgnoreCase))
                    {
                        // if no exception is thrown, then the search parameter with the same Uri was found
                        // and this is a conflict
                        validationFailures.Add(
                            new ValidationFailure(
                                nameof(searchParam.Url),
                                string.Format(Resources.SearchParameterDefinitionDuplicatedEntry, searchParam.Url)));
                    }
                }
                catch (FormatException)
                {
                    validationFailures.Add(
                        new ValidationFailure(
                            nameof(searchParam.Url),
                            string.Format(Resources.SearchParameterDefinitionInvalidDefinitionUri, searchParam.Url)));
                }
                catch (SearchParameterNotSupportedException)
                {
                    // if thrown, then the search parameter is not found
                    // if a PUT, then we should be updating an exsting paramter, but it was not found
                    if (method.Equals(HttpPutName, StringComparison.OrdinalIgnoreCase) ||
                        method.Equals(HttpDeleteName, StringComparison.OrdinalIgnoreCase))
                    {
                        // if an exception above was thrown, then the search parameter with the same Uri was not found
                        // and DELETE or PUT can only run on existing parameter
                        validationFailures.Add(
                            new ValidationFailure(
                                nameof(searchParam.Url),
                                string.Format(Resources.SearchParameterDefinitionNotFound, searchParam.Url)));
                    }
                }
            }

            // validate that the url does not correspond to a search param from the spec
            // TODO: still need a method to determine spec defined search params

            // validation of the fhir path
            // TODO: separate user story for this validation

            if (validationFailures.Any())
            {
                throw new ResourceNotValidException(validationFailures);
            }
        }
        public async Task ExecuteAsync(CancellationToken cancellationToken)
        {
            var runningTasks = new List <Task>();

            while (!cancellationToken.IsCancellationRequested)
            {
                if (_searchParametersInitialized)
                {
                    // Check for any changes to Search Parameters
                    try
                    {
                        await _searchParameterOperations.GetAndApplySearchParameterUpdates(cancellationToken);
                    }
                    catch (OperationCanceledException) when(cancellationToken.IsCancellationRequested)
                    {
                        _logger.LogDebug("Reindex job worker canceled.");
                    }
                    catch (Exception ex)
                    {
                        // The job failed.
                        _logger.LogError(ex, "Error querying latest SearchParameterStatus updates");
                    }

                    // Check for any new Reindex Jobs
                    try
                    {
                        // Remove all completed tasks.
                        runningTasks.RemoveAll(task => task.IsCompleted);

                        // Get list of available jobs.
                        if (runningTasks.Count < _reindexJobConfiguration.MaximumNumberOfConcurrentJobsAllowed)
                        {
                            using (IScoped <IFhirOperationDataStore> store = _fhirOperationDataStoreFactory.Invoke())
                            {
                                _logger.LogTrace("Querying datastore for reindex jobs.");

                                IReadOnlyCollection <ReindexJobWrapper> jobs = await store.Value.AcquireReindexJobsAsync(
                                    _reindexJobConfiguration.MaximumNumberOfConcurrentJobsAllowed,
                                    _reindexJobConfiguration.JobHeartbeatTimeoutThreshold,
                                    cancellationToken);

                                foreach (ReindexJobWrapper job in jobs)
                                {
                                    _logger.LogTrace("Picked up reindex job: {jobId}.", job.JobRecord.Id);

                                    runningTasks.Add(_reindexJobTaskFactory().ExecuteAsync(job.JobRecord, job.ETag, cancellationToken));
                                }
                            }
                        }
                    }
                    catch (OperationCanceledException) when(cancellationToken.IsCancellationRequested)
                    {
                        // End the execution of the task
                    }
                    catch (Exception ex)
                    {
                        // The job failed.
                        _logger.LogError(ex, "Error polling Reindex jobs.");
                    }
                }

                try
                {
                    await Task.Delay(_reindexJobConfiguration.JobPollingFrequency, cancellationToken);
                }
                catch (OperationCanceledException) when(cancellationToken.IsCancellationRequested)
                {
                    // End the execution of the task
                }
            }
        }
Beispiel #27
0
 public Transient(IScoped scoped)
 {
     ScopedDependency = scoped;
 }
Beispiel #28
0
        public ListSearchBehaviorTests()
        {
            _cancellationToken = _cancellationTokenSource.Token;

            var so = new SearchOptions();

            so.UnsupportedSearchParams  = new Tuple <string, string> [0];
            so.UnsupportedSortingParams = Array.Empty <(string searchParameterName, string reason)>();

            _searchOptionsFactory = Substitute.For <ISearchOptionsFactory>();
            _searchOptionsFactory.Create(Arg.Any <string>(), Arg.Any <IReadOnlyList <Tuple <string, string> > >()).Returns(so);

            _fhirDataStore = Substitute.For <IFhirDataStore>();

            // for an 'existing list' return a list with Patients
            _fhirDataStore.GetAsync(Arg.Is <ResourceKey>(x => x.Id == "existing-list"), Arg.Any <CancellationToken>()).Returns(
                x =>
            {
                var longList           = Samples.GetDefaultList();
                var rawResourceFactory = new RawResourceFactory(new FhirJsonSerializer());
                return(new ResourceWrapper(
                           longList,
                           rawResourceFactory.Create(longList),
                           new ResourceRequest(HttpMethod.Post, "http://fhir"),
                           false,
                           null,
                           null,
                           null));
            });

            _scopedDataStore = Substitute.For <IScoped <IFhirDataStore> >();
            _scopedDataStore.Value.Returns(_fhirDataStore);

            _nonEmptyBundle = new Bundle
            {
                Type  = Bundle.BundleType.Batch,
                Entry = new List <Bundle.EntryComponent>
                {
                    new Bundle.EntryComponent
                    {
                        Resource = Samples.GetDefaultObservation().ToPoco(),
                        Request  = new Bundle.RequestComponent
                        {
                            Method = Bundle.HTTPVerb.POST,
                            Url    = "Observation",
                        },
                    },
                    new Bundle.EntryComponent
                    {
                        Request = new Bundle.RequestComponent
                        {
                            Method = Bundle.HTTPVerb.GET,
                            Url    = "Patient?name=peter",
                        },
                    },
                },
            }.ToResourceElement();

            _bundleFactory = Substitute.For <IBundleFactory>();
            _bundleFactory.CreateSearchBundle(Arg.Any <SearchResult>()).Returns(_nonEmptyBundle);
        }
 public LoggerAsyncInterceptor(IScoped scoped, ILog log)
 {
     this._site = scoped.GetScoped();
     this._log  = log;
 }
        private async Task RunExportCompartmentSearch(
            ExportJobConfiguration exportJobConfiguration,
            ExportJobProgress progress,
            List <Tuple <string, string> > sharedQueryParametersList,
            CancellationToken cancellationToken,
            string batchIdPrefix = "")
        {
            EnsureArg.IsNotNull(exportJobConfiguration, nameof(exportJobConfiguration));
            EnsureArg.IsNotNull(progress, nameof(progress));
            EnsureArg.IsNotNull(sharedQueryParametersList, nameof(sharedQueryParametersList));

            // Current batch will be used to organize a set of search results into a group so that they can be committed together.
            string currentBatchId = batchIdPrefix + "-" + progress.Page.ToString("d6");

            List <Tuple <string, string> > queryParametersList = new List <Tuple <string, string> >(sharedQueryParametersList);

            if (progress.ContinuationToken != null)
            {
                queryParametersList.Add(Tuple.Create(KnownQueryParameterNames.ContinuationToken, progress.ContinuationToken));
            }

            if (!string.IsNullOrEmpty(_exportJobRecord.ResourceType))
            {
                queryParametersList.Add(Tuple.Create(KnownQueryParameterNames.Type, _exportJobRecord.ResourceType));
            }

            // Process the export if:
            // 1. There is continuation token, which means there is more resource to be exported.
            // 2. There is no continuation token but the page is 0, which means it's the initial export.
            while (progress.ContinuationToken != null || progress.Page == 0)
            {
                SearchResult searchResult = null;

                // Search and process the results.
                using (IScoped <ISearchService> searchService = _searchServiceFactory())
                {
                    searchResult = await searchService.Value.SearchCompartmentAsync(
                        compartmentType : KnownResourceTypes.Patient,
                        compartmentId : progress.TriggeringResourceId,
                        resourceType : null,
                        queryParametersList,
                        cancellationToken);
                }

                await ProcessSearchResultsAsync(searchResult.Results, currentBatchId, cancellationToken);

                if (searchResult.ContinuationToken == null)
                {
                    // No more continuation token, we are done.
                    break;
                }

                await ProcessProgressChange(exportJobConfiguration, progress, queryParametersList, searchResult.ContinuationToken, false, cancellationToken);

                currentBatchId = batchIdPrefix + '-' + progress.Page.ToString("d6");
            }

            // Commit one last time for any pending changes.
            await _exportDestinationClient.CommitAsync(exportJobConfiguration, cancellationToken);

            await UpdateJobRecordAsync(cancellationToken);
        }
        public async Task ValidateSearchParameterInput(SearchParameter searchParam, string method, CancellationToken cancellationToken)
        {
            if (await _authorizationService.CheckAccess(DataActions.Reindex, cancellationToken) != DataActions.Reindex)
            {
                throw new UnauthorizedFhirActionException();
            }

            // check if reindex job is running
            using (IScoped <IFhirOperationDataStore> fhirOperationDataStore = _fhirOperationDataStoreFactory())
            {
                (var activeReindexJobs, var reindexJobId) = await fhirOperationDataStore.Value.CheckActiveReindexJobsAsync(cancellationToken);

                if (activeReindexJobs)
                {
                    throw new JobConflictException(string.Format(Resources.ChangesToSearchParametersNotAllowedWhileReindexing, reindexJobId));
                }
            }

            var validationFailures = new List <ValidationFailure>();

            if (string.IsNullOrEmpty(searchParam.Url))
            {
                validationFailures.Add(
                    new ValidationFailure(nameof(Base.TypeName), Resources.SearchParameterDefinitionInvalidMissingUri));
            }
            else
            {
                try
                {
                    // If a search parameter with the same uri exists already
                    if (_searchParameterDefinitionManager.TryGetSearchParameter(new Uri(searchParam.Url), out _))
                    {
                        // And if this is a request to create a new search parameter
                        if (method.Equals(HttpPostName, StringComparison.OrdinalIgnoreCase))
                        {
                            // We have a conflict
                            validationFailures.Add(
                                new ValidationFailure(
                                    nameof(searchParam.Url),
                                    string.Format(Resources.SearchParameterDefinitionDuplicatedEntry, searchParam.Url)));
                        }
                        else if (method.Equals(HttpPutName, StringComparison.OrdinalIgnoreCase))
                        {
                            CheckForConflictingCodeValue(searchParam, validationFailures);
                        }
                    }
                    else
                    {
                        // Otherwise, no search parameters with a matching uri exist
                        // Ensure this isn't a request to modify an existing parameter
                        if (method.Equals(HttpPutName, StringComparison.OrdinalIgnoreCase) ||
                            method.Equals(HttpDeleteName, StringComparison.OrdinalIgnoreCase))
                        {
                            validationFailures.Add(
                                new ValidationFailure(
                                    nameof(searchParam.Url),
                                    string.Format(Resources.SearchParameterDefinitionNotFound, searchParam.Url)));
                        }

                        CheckForConflictingCodeValue(searchParam, validationFailures);
                    }
                }
                catch (FormatException)
                {
                    validationFailures.Add(
                        new ValidationFailure(
                            nameof(searchParam.Url),
                            string.Format(Resources.SearchParameterDefinitionInvalidDefinitionUri, searchParam.Url)));
                }
            }

            // validate that the url does not correspond to a search param from the spec
            // TODO: still need a method to determine spec defined search params

            // validation of the fhir path
            // TODO: separate user story for this validation

            if (validationFailures.Any())
            {
                throw new ResourceNotValidException(validationFailures);
            }
        }