internal RetryableFhirTransactionPipeline( IFhirTransactionPipeline fhirTransactionPipeline, IExceptionStore exceptionStore, int maxRetryCount) { EnsureArg.IsNotNull(fhirTransactionPipeline, nameof(fhirTransactionPipeline)); EnsureArg.IsNotNull(exceptionStore, nameof(exceptionStore)); _fhirTransactionPipeline = fhirTransactionPipeline; _exceptionStore = exceptionStore; _retryPolicy = Policy .Handle <RetryableException>() .WaitAndRetryAsync( maxRetryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (exception, timeSpan, retryCount, context) => { ChangeFeedEntry changeFeedEntry = (ChangeFeedEntry)context[nameof(ChangeFeedEntry)]; return(_exceptionStore.WriteRetryableExceptionAsync( changeFeedEntry, retryCount, exception)); }); }
public async Task GivenAnInstance_WhenRetrievingChangeFeedWithoutMetadata_ThenMetadataIsNotReturned() { var studyInstanceUid = TestUidGenerator.Generate(); var seriesInstanceUid = TestUidGenerator.Generate(); var sopInstanceUid = TestUidGenerator.Generate(); await CreateFile(studyInstanceUid, seriesInstanceUid, sopInstanceUid); long initialSequence; using (DicomWebResponse <ChangeFeedEntry> response = await _client.GetChangeFeedLatest("?includemetadata=false")) { ChangeFeedEntry changeFeedEntry = await response.GetValueAsync(); initialSequence = changeFeedEntry.Sequence; Assert.Equal(studyInstanceUid, changeFeedEntry.StudyInstanceUid); Assert.Equal(seriesInstanceUid, changeFeedEntry.SeriesInstanceUid); Assert.Equal(sopInstanceUid, changeFeedEntry.SopInstanceUid); Assert.Null(changeFeedEntry.Metadata); } using (DicomWebAsyncEnumerableResponse <ChangeFeedEntry> response = await _client.GetChangeFeed($"?offset={initialSequence - 1}&includemetadata=false")) { ChangeFeedEntry[] changeFeedResults = await response.ToArrayAsync(); Assert.Single(changeFeedResults); Assert.Null(changeFeedResults[0].Metadata); Assert.Equal(ChangeFeedState.Current, changeFeedResults[0].State); } }
/// <inheritdoc/> public async Task WriteRetryableExceptionAsync(ChangeFeedEntry changeFeedEntry, int retryNum, Exception exceptionToStore, CancellationToken cancellationToken) { string tableName = Constants.TransientRetryTableName; DicomDataset dataset = changeFeedEntry.Metadata; string studyUid = dataset.GetSingleValue <string>(DicomTag.StudyInstanceUID); string seriesUid = dataset.GetSingleValue <string>(DicomTag.SeriesInstanceUID); string instanceUid = dataset.GetSingleValue <string>(DicomTag.SOPInstanceUID); long changeFeedSequence = changeFeedEntry.Sequence; CloudTable table = _client.GetTableReference(tableName); TableEntity entity = new RetryableEntity(studyUid, seriesUid, instanceUid, changeFeedSequence, retryNum, exceptionToStore); TableOperation operation = TableOperation.InsertOrMerge(entity); try { await table.ExecuteAsync(operation, cancellationToken); _logger.LogInformation("Retryable error when processsing changefeed entry: {ChangeFeedSequence} for DICOM instance with StudyUID: {StudyUID}, SeriesUID: {SeriesUID}, InstanceUID: {InstanceUID}. Tried {retryNum} time(s). Stored into table: {Table} in table storage.", changeFeedSequence, studyUid, seriesUid, instanceUid, retryNum, tableName); } catch { _logger.LogInformation("Retryable error when processsing changefeed entry: {ChangeFeedSequence} for DICOM instance with StudyUID: {StudyUID}, SeriesUID: {SeriesUID}, InstanceUID: {InstanceUID}. Tried {retryNum} time(s). Failed to store to table storage.", changeFeedSequence, studyUid, seriesUid, instanceUid, retryNum); throw; } }
/// <inheritdoc/> public Task ProcessAsync(ChangeFeedEntry changeFeedEntry, CancellationToken cancellationToken) { var context = new Context { { nameof(ChangeFeedEntry), changeFeedEntry }, }; return(_timeoutPolicy.WrapAsync(_retryPolicy).ExecuteAsync( async(ctx, tkn) => { try { await _fhirTransactionPipeline.ProcessAsync(changeFeedEntry, cancellationToken); } catch (TaskCanceledException ex) when(!cancellationToken.IsCancellationRequested) { throw new RetryableException(ex); } catch (HttpRequestException ex) { throw new RetryableException(ex); } }, context, cancellationToken)); }
/// <inheritdoc/> public async Task WriteRetryableExceptionAsync(ChangeFeedEntry changeFeedEntry, int retryNum, TimeSpan nextDelayTimeSpan, Exception exceptionToStore, CancellationToken cancellationToken) { EnsureArg.IsNotNull(changeFeedEntry, nameof(changeFeedEntry)); DicomDataset dataset = changeFeedEntry.Metadata; string studyInstanceUid = dataset.GetSingleValue <string>(DicomTag.StudyInstanceUID); string seriesInstanceUid = dataset.GetSingleValue <string>(DicomTag.SeriesInstanceUID); string sopInstanceUid = dataset.GetSingleValue <string>(DicomTag.SOPInstanceUID); long changeFeedSequence = changeFeedEntry.Sequence; var tableClient = _tableServiceClient.GetTableClient(Constants.TransientRetryTableName); var entity = new RetryableEntity(studyInstanceUid, seriesInstanceUid, sopInstanceUid, changeFeedSequence, retryNum, exceptionToStore); try { await tableClient.UpsertEntityAsync(entity, cancellationToken : cancellationToken); _logger.LogInformation("Retryable error when processing changefeed entry: {ChangeFeedSequence} for DICOM instance with StudyUID: {StudyInstanceUid}, SeriesUID: {SeriesInstanceUid}, InstanceUID: {SopInstanceUid}. Tried {RetryNum} time(s). Waiting {Milliseconds} milliseconds . Stored into table: {Table} in table storage.", changeFeedSequence, studyInstanceUid, seriesInstanceUid, sopInstanceUid, retryNum, nextDelayTimeSpan.TotalMilliseconds, Constants.TransientRetryTableName); } catch { _logger.LogInformation("Retryable error when processing changefeed entry: {ChangeFeedSequence} for DICOM instance with StudyUID: {StudyInstanceUid}, SeriesUID: {SeriesInstanceUid}, InstanceUID: {SopInstanceUid}. Tried {RetryNum} time(s). Failed to store to table storage.", changeFeedSequence, studyInstanceUid, seriesInstanceUid, sopInstanceUid, retryNum); throw; } }
private async Task <FhirTransactionRequestEntry> PrepareRequestAsync(ChangeFeedEntry changeFeedEntry, string patientResourceId) { _fhirTransactionContext = new FhirTransactionContext(changeFeedEntry); _fhirTransactionContext.Request.Patient = FhirTransactionRequestEntryGenerator.GenerateDefaultNoChangeRequestEntry <Patient>(new ServerResourceId(ResourceType.Patient, patientResourceId)); return(await _imagingStudyDeleteHandler.BuildAsync(_fhirTransactionContext, CancellationToken.None)); }
private async Task ExecuteAndValidateRetryThenThrowTimeOut(Exception ex) { ChangeFeedEntry changeFeedEntry = ChangeFeedGenerator.Generate(); _fhirTransactionPipeline.ProcessAsync(changeFeedEntry, DefaultCancellationToken).Throws(ex); await Assert.ThrowsAsync <TimeoutRejectedException>(() => _retryableFhirTransactionPipeline.ProcessAsync(changeFeedEntry, DefaultCancellationToken)); }
/// <inheritdoc/> public async Task ProcessAsync(ChangeFeedEntry changeFeedEntry, CancellationToken cancellationToken) { EnsureArg.IsNotNull(changeFeedEntry, nameof(changeFeedEntry)); // Create a context used throughout this process. var context = new FhirTransactionContext(changeFeedEntry); // Prepare all required objects for the transaction. foreach (IFhirTransactionPipelineStep pipeline in _fhirTransactionPipelines) { await pipeline.PrepareRequestAsync(context, cancellationToken); } // Check to see if any resource needs to be created/updated. var bundle = new Bundle() { Type = Bundle.BundleType.Transaction, }; var usedPropertyAccessors = new List <FhirTransactionRequestResponsePropertyAccessor>(_requestResponsePropertyAccessors.Count); foreach (FhirTransactionRequestResponsePropertyAccessor propertyAccessor in _requestResponsePropertyAccessors) { FhirTransactionRequestEntry requestEntry = propertyAccessor.RequestEntryGetter(context.Request); if (requestEntry == null || requestEntry.RequestMode == FhirTransactionRequestMode.None) { // No associated request, skip it. continue; } // There is a associated request, add to the list so it gets processed. usedPropertyAccessors.Add(propertyAccessor); bundle.Entry.Add(CreateRequestBundleEntryComponent(requestEntry)); } if (!bundle.Entry.Any()) { // Nothing to update. return; } // Execute the transaction. Bundle responseBundle = await _fhirTransactionExecutor.ExecuteTransactionAsync(bundle, cancellationToken); // Process the response. for (int i = 0; i < usedPropertyAccessors.Count; i++) { FhirTransactionResponseEntry responseEntry = CreateResponseEntry(responseBundle.Entry[i]); usedPropertyAccessors[i].ResponseEntrySetter(context.Response, responseEntry); } // Execute any additional checks of the response. foreach (IFhirTransactionPipelineStep pipeline in _fhirTransactionPipelines) { pipeline.ProcessResponse(context); }
/// <inheritdoc/> public async Task <FhirTransactionRequestEntry> BuildAsync(FhirTransactionContext context, CancellationToken cancellationToken) { EnsureArg.IsNotNull(context, nameof(context)); EnsureArg.IsNotNull(context.ChangeFeedEntry, nameof(context.ChangeFeedEntry)); EnsureArg.IsNotNull(context.Request, nameof(context.Request)); IResourceId patientId = context.Request.Patient.ResourceId; ChangeFeedEntry changeFeedEntry = context.ChangeFeedEntry; Identifier imagingStudyIdentifier = ImagingStudyIdentifierUtility.CreateIdentifier(changeFeedEntry.StudyInstanceUid); ImagingStudy existingImagingStudy = await _fhirService.RetrieveImagingStudyAsync(imagingStudyIdentifier, cancellationToken); ImagingStudy imagingStudy = (ImagingStudy)existingImagingStudy?.DeepCopy(); FhirTransactionRequestMode requestMode = FhirTransactionRequestMode.None; if (existingImagingStudy == null) { imagingStudy = new ImagingStudy() { Status = ImagingStudy.ImagingStudyStatus.Available, Subject = patientId.ToResourceReference(), }; imagingStudy.Identifier.Add(imagingStudyIdentifier); requestMode = FhirTransactionRequestMode.Create; } SynchronizeImagingStudyProperties(context, imagingStudy); if (requestMode != FhirTransactionRequestMode.Create && !existingImagingStudy.IsExactly(imagingStudy)) { requestMode = FhirTransactionRequestMode.Update; } Bundle.RequestComponent request = requestMode switch { FhirTransactionRequestMode.Create => ImagingStudyPipelineHelper.GenerateCreateRequest(imagingStudyIdentifier), FhirTransactionRequestMode.Update => ImagingStudyPipelineHelper.GenerateUpdateRequest(imagingStudy), _ => null, }; IResourceId resourceId = requestMode switch { FhirTransactionRequestMode.Create => new ClientResourceId(), _ => existingImagingStudy.ToServerResourceId(), }; return(new FhirTransactionRequestEntry( requestMode, request, resourceId, imagingStudy)); }
public FhirTransactionContext(ChangeFeedEntry changeFeedEntry) { EnsureArg.IsNotNull(changeFeedEntry, nameof(changeFeedEntry)); ChangeFeedEntry = changeFeedEntry; Request = new FhirTransactionRequest(); Response = new FhirTransactionResponse(); }
private async Task ExecuteAndValidate(Exception ex, int expectedNumberOfCalls) { ChangeFeedEntry changeFeedEntry = ChangeFeedGenerator.Generate(); _fhirTransactionPipeline.ProcessAsync(changeFeedEntry, DefaultCancellationToken).Throws(ex); await Assert.ThrowsAsync(ex.GetType(), () => _retryableFhirTransactionPipeline.ProcessAsync(changeFeedEntry, DefaultCancellationToken)); await _fhirTransactionPipeline.Received(expectedNumberOfCalls).ProcessAsync(changeFeedEntry, DefaultCancellationToken); }
private async Task <FhirTransactionRequestEntry> BuildImagingStudyEntryComponent(string studyInstanceUid, string seriesInstanceUid, string sopInstanceUid, string patientResourceId, bool addMetadata = true) { ChangeFeedEntry changeFeedEntry = ChangeFeedGenerator.Generate( action: ChangeFeedAction.Create, studyInstanceUid: studyInstanceUid, seriesInstanceUid: seriesInstanceUid, sopInstanceUid: sopInstanceUid, metadata: addMetadata ? FhirTransactionContextBuilder.CreateDicomDataset() : null); return(await PrepareRequestAsync(changeFeedEntry, patientResourceId)); }
public Task WriteRetryableExceptionAsync(ChangeFeedEntry changeFeedEntry, int retryNum, Exception exceptionToStore, CancellationToken cancellationToken = default) { DicomDataset dataset = changeFeedEntry.Metadata; string studyUid = dataset.GetSingleValue <string>(DicomTag.StudyInstanceUID); string seriesUid = dataset.GetSingleValue <string>(DicomTag.SeriesInstanceUID); string instanceUid = dataset.GetSingleValue <string>(DicomTag.SOPInstanceUID); long changeFeedSequence = changeFeedEntry.Sequence; _logger.LogInformation("Retryable error when processsing changefeed entry: {ChangeFeedSequence} for DICOM instance with StudyUID: {StudyUID}, SeriesUID: {SeriesUID}, InstanceUID: {InstanceUID}. Tried {retryNum} time(s).", changeFeedSequence, studyUid, seriesUid, instanceUid, retryNum); return(Task.CompletedTask); }
/// <inheritdoc/> public async Task WriteExceptionAsync(ChangeFeedEntry changeFeedEntry, Exception exceptionToStore, ErrorType errorType, CancellationToken cancellationToken) { CloudTable table; TableEntity entity; string tableName; EnsureArg.IsNotNull(changeFeedEntry, nameof(changeFeedEntry)); switch (errorType) { case ErrorType.FhirError: tableName = Constants.FhirExceptionTableName; break; case ErrorType.DicomError: tableName = Constants.DicomExceptionTableName; break; case ErrorType.DicomValidationError: tableName = Constants.DicomValidationTableName; break; case ErrorType.TransientFailure: tableName = Constants.TransientFailureTableName; break; default: return; } DicomDataset dataset = changeFeedEntry.Metadata; string studyUid = dataset.GetSingleValue <string>(DicomTag.StudyInstanceUID); string seriesUid = dataset.GetSingleValue <string>(DicomTag.SeriesInstanceUID); string instanceUid = dataset.GetSingleValue <string>(DicomTag.SOPInstanceUID); long changeFeedSequence = changeFeedEntry.Sequence; table = _client.GetTableReference(tableName); entity = new IntransientEntity(studyUid, seriesUid, instanceUid, changeFeedSequence, exceptionToStore); var operation = TableOperation.InsertOrMerge(entity); try { await table.ExecuteAsync(operation, cancellationToken); _logger.LogInformation("Error when processsing changefeed entry: {ChangeFeedSequence} for DICOM instance with StudyUID: {StudyUID}, SeriesUID: {SeriesUID}, InstanceUID: {InstanceUID}. Stored into table: {Table} in table storage.", changeFeedSequence, studyUid, seriesUid, instanceUid, tableName); } catch { _logger.LogInformation("Error when processsing changefeed entry: {ChangeFeedSequence} for DICOM instance with StudyUID: {StudyUID}, SeriesUID: {SeriesUID}, InstanceUID: {InstanceUID}. Failed to store to table storage.", changeFeedSequence, studyUid, seriesUid, instanceUid); throw; } }
/// <inheritdoc/> public async Task <FhirTransactionRequestEntry> BuildAsync(FhirTransactionContext context, CancellationToken cancellationToken) { EnsureArg.IsNotNull(context, nameof(context)); EnsureArg.IsNotNull(context.ChangeFeedEntry, nameof(context.ChangeFeedEntry)); ChangeFeedEntry changeFeedEntry = context.ChangeFeedEntry; Identifier imagingStudyIdentifier = ImagingStudyIdentifierUtility.CreateIdentifier(changeFeedEntry.StudyInstanceUid); ImagingStudy imagingStudy = await _fhirService.RetrieveImagingStudyAsync(imagingStudyIdentifier, cancellationToken); // Returns null if imagingStudy does not exists for given studyInstanceUid if (imagingStudy == null) { return(null); } string imagingStudySource = imagingStudy.Meta.Source; ImagingStudy.SeriesComponent series = ImagingStudyPipelineHelper.GetSeriesWithinAStudy(changeFeedEntry.SeriesInstanceUid, imagingStudy.Series); ImagingStudy.InstanceComponent instance = ImagingStudyPipelineHelper.GetInstanceWithinASeries(changeFeedEntry.SopInstanceUid, series); // Return null if the given instance is not present in ImagingStudy if (instance == null) { return(null); } // Removes instance from series collection series.Instance.Remove(instance); // Removes series from ImagingStudy if its instance collection is empty if (series.Instance.Count == 0) { imagingStudy.Series.Remove(series); } if (imagingStudy.Series.Count == 0 && _dicomWebEndpoint.Equals(imagingStudySource, System.StringComparison.Ordinal)) { return(new FhirTransactionRequestEntry( FhirTransactionRequestMode.Delete, ImagingStudyPipelineHelper.GenerateDeleteRequest(imagingStudy), imagingStudy.ToServerResourceId(), imagingStudy)); } return(new FhirTransactionRequestEntry( FhirTransactionRequestMode.Update, ImagingStudyPipelineHelper.GenerateUpdateRequest(imagingStudy), imagingStudy.ToServerResourceId(), imagingStudy)); }
public async Task GivenAValidCreateChangeFeed_WhenBuilt_ThenCorrectEntryComponentShouldBeCreated() { const string studyInstanceUid = "1"; const string seriesInstanceUid = "2"; const string sopInstanceUid = "3"; const string patientResourceId = "p1"; ChangeFeedEntry changeFeedEntry = ChangeFeedGenerator.Generate( action: ChangeFeedAction.Create, studyInstanceUid: studyInstanceUid, seriesInstanceUid: seriesInstanceUid, sopInstanceUid: sopInstanceUid); FhirTransactionRequestEntry entry = await BuildImagingStudyEntryComponent(studyInstanceUid, seriesInstanceUid, sopInstanceUid, patientResourceId); ValidationUtility.ValidateRequestEntryMinimumRequirementForWithChange(FhirTransactionRequestMode.Create, "ImagingStudy", Bundle.HTTPVerb.POST, entry); ImagingStudy imagingStudy = Assert.IsType <ImagingStudy>(entry.Resource); string jsonString; jsonString = JsonSerializer.Serialize(entry); Assert.IsType <ClientResourceId>(entry.ResourceId); Assert.Equal(ImagingStudy.ImagingStudyStatus.Available, imagingStudy.Status); Assert.Null(entry.Request.IfMatch); ValidationUtility.ValidateResourceReference("Patient/p1", imagingStudy.Subject); Assert.Collection( imagingStudy.Identifier, identifier => ValidationUtility.ValidateIdentifier("urn:dicom:uid", $"urn:oid:{studyInstanceUid}", identifier), identifier => ValidationUtility.ValidateAccessionNumber(null, FhirTransactionContextBuilder.DefaultAccessionNumber, identifier)); Assert.Collection( imagingStudy.Series, series => { Assert.Equal(seriesInstanceUid, series.Uid); Assert.Collection( series.Instance, instance => Assert.Equal(sopInstanceUid, instance.Uid)); }); ValidateDicomFilePropertiesAreCorrectlyMapped(imagingStudy, series: imagingStudy.Series.First(), instance: imagingStudy.Series.First().Instance.First()); }
/// <inheritdoc/> public async Task PrepareRequestAsync(FhirTransactionContext context, CancellationToken cancellationToken) { EnsureArg.IsNotNull(context, nameof(context)); ChangeFeedEntry changeFeedEntry = context.ChangeFeedEntry; context.Request.ImagingStudy = changeFeedEntry.Action switch { ChangeFeedAction.Create => await _imagingStudyUpsertHandler.BuildAsync(context, cancellationToken), ChangeFeedAction.Delete => await _imagingStudyDeleteHandler.BuildAsync(context, cancellationToken), _ => throw new NotSupportedException( string.Format( CultureInfo.InvariantCulture, DicomCastCoreResource.NotSupportedChangeFeedAction, changeFeedEntry.Action)), }; }
public async Task GivenASetOfDicomInstances_WhenRetrievingChangeFeed_ThenTheExpectedInstanceAreReturned() { var studyInstanceUid = TestUidGenerator.Generate(); var seriesInstanceUid = TestUidGenerator.Generate(); var sopInstanceUids = Enumerable.Range(1, 10).Select(x => TestUidGenerator.Generate()).ToArray(); long initialSequence = -1; for (int i = 0; i < 10; i++) { await CreateFile(studyInstanceUid, seriesInstanceUid, sopInstanceUids[i]); if (initialSequence == -1) { using DicomWebResponse <ChangeFeedEntry> latestResponse = await _client.GetChangeFeedLatest(); ChangeFeedEntry result = await latestResponse.GetValueAsync(); initialSequence = result.Sequence; Assert.Equal(studyInstanceUid, result.StudyInstanceUid); Assert.Equal(seriesInstanceUid, result.SeriesInstanceUid); Assert.Equal(sopInstanceUids[i], result.SopInstanceUid); Assert.Equal(ChangeFeedAction.Create, result.Action); Assert.Equal(ChangeFeedState.Current, result.State); } } using (DicomWebAsyncEnumerableResponse <ChangeFeedEntry> response = await _client.GetChangeFeed()) { Assert.Equal(10, await response.CountAsync()); } using (DicomWebAsyncEnumerableResponse <ChangeFeedEntry> response = await _client.GetChangeFeed($"?offset={initialSequence - 1}")) { ChangeFeedEntry[] changeFeedResults = await response.ToArrayAsync(); Assert.Equal(10, changeFeedResults.Length); for (int i = 0; i < 10; i++) { Assert.Equal(studyInstanceUid, changeFeedResults[i].StudyInstanceUid); Assert.Equal(seriesInstanceUid, changeFeedResults[i].SeriesInstanceUid); Assert.Equal(sopInstanceUids[i], changeFeedResults[i].SopInstanceUid); Assert.NotNull(changeFeedResults[i].Metadata); } } }
public async Task <IEnumerable <FhirTransactionRequestEntry> > BuildAsync(FhirTransactionContext context, CancellationToken cancellationToken) { EnsureArg.IsNotNull(context?.ChangeFeedEntry, nameof(context.ChangeFeedEntry)); EnsureArg.IsNotNull(context.Request, nameof(context.Request)); IResourceId patientId = context.Request.Patient.ResourceId; IResourceId imagingStudyId = context.Request.ImagingStudy.ResourceId; ChangeFeedEntry changeFeedEntry = context.ChangeFeedEntry; Identifier identifier = IdentifierUtility.CreateIdentifier(changeFeedEntry.StudyInstanceUid); IReadOnlyCollection <Observation> observations = _observationParser.Parse(changeFeedEntry.Metadata, patientId.ToResourceReference(), imagingStudyId.ToResourceReference(), identifier); if (observations.Count == 0) { return(Enumerable.Empty <FhirTransactionRequestEntry>()); } Identifier imagingStudyIdentifier = imagingStudyId.ToResourceReference().Identifier; IEnumerable <Observation> existingDoseSummariesAsync = imagingStudyIdentifier != null ? await _fhirService .RetrieveObservationsAsync( imagingStudyId.ToResourceReference().Identifier, cancellationToken) : new List <Observation>(); // TODO: Figure out a way to match existing observations with newly created ones. List <FhirTransactionRequestEntry> fhirRequests = new List <FhirTransactionRequestEntry>(); foreach (var observation in observations) { fhirRequests.Add(new FhirTransactionRequestEntry( FhirTransactionRequestMode.Create, new Bundle.RequestComponent() { Method = Bundle.HTTPVerb.POST, Url = ResourceType.Observation.GetLiteral() }, new ClientResourceId(), observation)); } return(fhirRequests); }
public async Task GivenAChangeFeedEntryForCreate_WhenPreparingTheRequest_ThenCreateHandlerIsCalled() { const string studyInstanceUid = "1"; const string seriesInstanceUid = "2"; const string sopInstanceUid = "3"; ChangeFeedEntry changeFeed = ChangeFeedGenerator.Generate( action: ChangeFeedAction.Create, studyInstanceUid: studyInstanceUid, seriesInstanceUid: seriesInstanceUid, sopInstanceUid: sopInstanceUid); var fhirTransactionContext = new FhirTransactionContext(changeFeed); await _imagingStudyPipeline.PrepareRequestAsync(fhirTransactionContext, DefaultCancellationToken); await _imagingStudyUpsertHandler.Received(1).BuildAsync(fhirTransactionContext, DefaultCancellationToken); }
public async Task PrepareRequestAsync(FhirTransactionContext context, CancellationToken cancellationToken = default) { EnsureArg.IsNotNull(context, nameof(context)); if (_dicomCastConfiguration.Features.GenerateObservations) { ChangeFeedEntry changeFeedEntry = context.ChangeFeedEntry; context.Request.Observation = changeFeedEntry.Action switch { ChangeFeedAction.Create => await _observationUpsertHandler.BuildAsync(context, cancellationToken), ChangeFeedAction.Delete => await _observationDeleteHandler.BuildAsync(context, cancellationToken), _ => throw new NotSupportedException( string.Format( CultureInfo.InvariantCulture, DicomCastCoreResource.NotSupportedChangeFeedAction, changeFeedEntry.Action)) }; } }
private async Task AddSeriesToImagingStudyAsync(FhirTransactionContext context, ImagingStudy imagingStudy, CancellationToken cancellationToken) { ChangeFeedEntry changeFeedEntry = context.ChangeFeedEntry; List <ImagingStudy.SeriesComponent> existingSeriesCollection = imagingStudy.Series; ImagingStudy.InstanceComponent instance = new ImagingStudy.InstanceComponent() { Uid = changeFeedEntry.SopInstanceUid, }; // Checks if the given series already exists within a study ImagingStudy.SeriesComponent series = ImagingStudyPipelineHelper.GetSeriesWithinAStudy(changeFeedEntry.SeriesInstanceUid, existingSeriesCollection); if (series == null) { series = new ImagingStudy.SeriesComponent() { Uid = changeFeedEntry.SeriesInstanceUid, }; series.Instance.Add(instance); imagingStudy.Series.Add(series); } else { ImagingStudy.InstanceComponent existingInstance = ImagingStudyPipelineHelper.GetInstanceWithinASeries(changeFeedEntry.SopInstanceUid, series); if (existingInstance == null) { series.Instance.Add(instance); } else { instance = existingInstance; } } await _imagingStudySynchronizer.SynchronizeSeriesPropertiesAsync(context, series, cancellationToken); await _imagingStudySynchronizer.SynchronizeInstancePropertiesAsync(context, instance, cancellationToken); }
/// <inheritdoc/> public Task ProcessAsync(ChangeFeedEntry changeFeedEntry, CancellationToken cancellationToken) { Context context = new Context(); context[nameof(ChangeFeedEntry)] = changeFeedEntry; return(_retryPolicy.ExecuteAsync( (ctx, tkn) => { try { return _fhirTransactionPipeline.ProcessAsync(changeFeedEntry, cancellationToken); } catch (TaskCanceledException ex) when(!cancellationToken.IsCancellationRequested) { throw new RetryableException(ex); } }, context, cancellationToken)); }
/// <inheritdoc/> public async Task WriteExceptionAsync(ChangeFeedEntry changeFeedEntry, Exception exceptionToStore, ErrorType errorType, CancellationToken cancellationToken) { EnsureArg.IsNotNull(changeFeedEntry, nameof(changeFeedEntry)); string tableName = errorType switch { ErrorType.FhirError => Constants.FhirExceptionTableName, ErrorType.DicomError => Constants.DicomExceptionTableName, ErrorType.DicomValidationError => Constants.DicomValidationTableName, ErrorType.TransientFailure => Constants.TransientFailureTableName, _ => null, }; if (tableName == null) { return; } DicomDataset dataset = changeFeedEntry.Metadata; string studyInstanceUid = dataset.GetSingleValue <string>(DicomTag.StudyInstanceUID); string seriesInstanceUid = dataset.GetSingleValue <string>(DicomTag.SeriesInstanceUID); string sopInstanceUid = dataset.GetSingleValue <string>(DicomTag.SOPInstanceUID); long changeFeedSequence = changeFeedEntry.Sequence; var tableClient = _tableServiceClient.GetTableClient(tableName); var entity = new IntransientEntity(studyInstanceUid, seriesInstanceUid, sopInstanceUid, changeFeedSequence, exceptionToStore); try { await tableClient.UpsertEntityAsync(entity, cancellationToken : cancellationToken); _logger.LogInformation("Error when processing changefeed entry: {ChangeFeedSequence} for DICOM instance with StudyUID: {StudyInstanceUid}, SeriesUID: {SeriesInstanceUid}, InstanceUID: {SopInstanceUid}. Stored into table: {Table} in table storage.", changeFeedSequence, studyInstanceUid, seriesInstanceUid, sopInstanceUid, tableName); } catch { _logger.LogInformation("Error when processing changefeed entry: {ChangeFeedSequence} for DICOM instance with StudyUID: {StudyInstanceUid}, SeriesUID: {SeriesInstanceUid}, InstanceUID: {SopInstanceUid}. Failed to store to table storage.", changeFeedSequence, studyInstanceUid, seriesInstanceUid, sopInstanceUid); throw; } }
public async Task GivenAChangeFeedEntryForDelete_WhenBuilt_ThenDeleteHandlerIsCalled() { const string studyInstanceUid = "1"; const string seriesInstanceUid = "2"; const string sopInstanceUid = "3"; const string patientResourceId = "p1"; ChangeFeedEntry changeFeed = ChangeFeedGenerator.Generate( action: ChangeFeedAction.Delete, studyInstanceUid: studyInstanceUid, seriesInstanceUid: seriesInstanceUid, sopInstanceUid: sopInstanceUid); var fhirTransactionContext = new FhirTransactionContext(changeFeed); fhirTransactionContext.Request.Patient = FhirTransactionRequestEntryGenerator.GenerateDefaultNoChangeRequestEntry <Patient>( new ServerResourceId(ResourceType.Patient, patientResourceId)); await _imagingStudyPipeline.PrepareRequestAsync(fhirTransactionContext, DefaultCancellationToken); await _imagingStudyDeleteHandler.Received(1).BuildAsync(fhirTransactionContext, DefaultCancellationToken); }
public static ChangeFeedEntry Generate(long?sequence = null, ChangeFeedAction?action = null, string studyInstanceUid = null, string seriesInstanceUid = null, string sopInstanceUid = null, ChangeFeedState?state = null, DicomDataset metadata = null) { if (sequence == null) { sequence = 1; } if (action == null) { action = ChangeFeedAction.Create; } if (string.IsNullOrEmpty(studyInstanceUid)) { studyInstanceUid = DicomUID.Generate().UID; } if (string.IsNullOrEmpty(seriesInstanceUid)) { seriesInstanceUid = DicomUID.Generate().UID; } if (string.IsNullOrEmpty(sopInstanceUid)) { sopInstanceUid = DicomUID.Generate().UID; } if (state == null) { state = ChangeFeedState.Current; } var changeFeedEntry = new ChangeFeedEntry(sequence.Value, DateTime.UtcNow, action.Value, studyInstanceUid, seriesInstanceUid, sopInstanceUid, state.Value) { Metadata = metadata, }; return(changeFeedEntry); }
public async Task GivenAnInstance_WhenRetrievingChangeFeedWithPartition_ThenPartitionNameIsReturned() { var newPartition = TestUidGenerator.Generate(); string studyInstanceUID = TestUidGenerator.Generate(); string seriesInstanceUID = TestUidGenerator.Generate(); string sopInstanceUID = TestUidGenerator.Generate(); DicomFile dicomFile = Samples.CreateRandomDicomFile(studyInstanceUID, seriesInstanceUID, sopInstanceUID); using DicomWebResponse <DicomDataset> response1 = await _instancesManager.StoreAsync(new[] { dicomFile }, partitionName : newPartition); Assert.True(response1.IsSuccessStatusCode); long initialSequence; using (DicomWebResponse <ChangeFeedEntry> response = await _client.GetChangeFeedLatest("?includemetadata=false")) { ChangeFeedEntry changeFeedEntry = await response.GetValueAsync(); initialSequence = changeFeedEntry.Sequence; Assert.Equal(newPartition, changeFeedEntry.PartitionName); Assert.Equal(studyInstanceUID, changeFeedEntry.StudyInstanceUid); Assert.Equal(seriesInstanceUID, changeFeedEntry.SeriesInstanceUid); Assert.Equal(sopInstanceUID, changeFeedEntry.SopInstanceUid); } using (DicomWebAsyncEnumerableResponse <ChangeFeedEntry> response = await _client.GetChangeFeed($"?offset={initialSequence - 1}&includemetadata=false")) { ChangeFeedEntry[] changeFeedResults = await response.ToArrayAsync(); Assert.Single(changeFeedResults); Assert.Null(changeFeedResults[0].Metadata); Assert.Equal(newPartition, changeFeedResults[0].PartitionName); Assert.Equal(ChangeFeedState.Current, changeFeedResults[0].State); } }
/// <inheritdoc/> public async Task ProcessAsync(ChangeFeedEntry changeFeedEntry, CancellationToken cancellationToken) { EnsureArg.IsNotNull(changeFeedEntry, nameof(changeFeedEntry)); using (LogProcessingDelegate(_logger, changeFeedEntry.Sequence)) { try { await _fhirTransactionPipeline.ProcessAsync(changeFeedEntry, cancellationToken); LogSuccessfullyProcessedDelegate(_logger, null); } catch (OperationCanceledException) when(cancellationToken.IsCancellationRequested) { // Cancel requested throw; } catch (Exception ex) { LogExceptionDelegate(_logger, ex); throw; } } }
private async Task <FhirTransactionRequestEntry> BuildImagingStudyEntryComponent(string studyInstanceUid, string seriesInstanceUid, string sopInstanceUid, string patientResourceId) { ChangeFeedEntry changeFeedEntry = ChangeFeedGenerator.Generate(action: ChangeFeedAction.Delete, studyInstanceUid: studyInstanceUid, seriesInstanceUid: seriesInstanceUid, sopInstanceUid: sopInstanceUid); return(await PrepareRequestAsync(changeFeedEntry, patientResourceId)); }
public ChangeFeedLatestResponse(ChangeFeedEntry entry) { Entry = entry; }