/// <inheritdoc /> public RawResource Create(ResourceElement resource, bool keepMeta) { EnsureArg.IsNotNull(resource, nameof(resource)); var poco = resource.ToPoco <Resource>(); poco.Meta = poco.Meta ?? new Meta(); var versionId = poco.Meta.VersionId; try { // Clear meta version if keepMeta is false since this is set based on generated values when saving the resource if (!keepMeta) { poco.Meta.VersionId = null; } else { // Assume it's 1, though it may get changed by the database. poco.Meta.VersionId = "1"; } return(new RawResource(_fhirJsonSerializer.SerializeToString(poco), FhirResourceFormat.Json, keepMeta)); } finally { if (!keepMeta) { poco.Meta.VersionId = versionId; } } }
private ResourceElement CreatePatientWithIdentity(ResourceElement patient, SearchResult results) { var searchMatchOnly = results.Results.Where(x => x.SearchEntryMode == ValueSets.SearchEntryMode.Match).ToList(); if (searchMatchOnly.Count > 1) { throw new MemberMatchMatchingException(Core.Resources.MemberMatchMultipleMatchesFound); } if (searchMatchOnly.Count == 0) { throw new MemberMatchMatchingException(Core.Resources.MemberMatchNoMatchFound); } var match = searchMatchOnly[0]; var element = _resourceDeserializer.Deserialize(match.Resource); var foundPatient = element.ToPoco <Patient>(); var id = foundPatient.Identifier.Where(x => x.Type != null && x.Type.Coding != null && x.Type.Coding.Exists(x => x.Code == "MB")).FirstOrDefault(); if (id == null) { throw new MemberMatchMatchingException(Core.Resources.MemberMatchNoMatchFound); } var resultPatient = patient.ToPoco <Patient>(); var resultId = new Identifier(id.System, id.Value); resultId.Type = new CodeableConcept("http://terminology.hl7.org/CodeSystem/v2-0203", "UMB", "Member Match"); resultPatient.Identifier.Add(resultId); var result = resultPatient.ToResourceElement(); return(result); }
/// <inheritdoc /> public IReadOnlyCollection <SearchIndexEntry> Extract(ResourceElement resource) { EnsureArg.IsNotNull(resource, nameof(resource)); var poco = resource.ToPoco(); var entries = new List <SearchIndexEntry>(); var context = new FhirEvaluationContext(poco); IEnumerable <SearchParameterInfo> searchParameters = _searchParameterDefinitionManager.GetSearchParameters(resource.InstanceType); foreach (SearchParameterInfo searchParameter in searchParameters) { if (searchParameter.Name == SearchParameterNames.ResourceType) { // We don't index the resource type value. We just use the property on the root document. continue; } if (searchParameter.Type == SearchParamType.Composite) { entries.AddRange(ProcessCompositeSearchParameter(searchParameter, poco, context)); } else { entries.AddRange(ProcessNonCompositeSearchParameter(searchParameter, poco, context)); } } return(entries); }
/// <inheritdoc /> public RawResource Create(ResourceElement resource) { EnsureArg.IsNotNull(resource, nameof(resource)); var poco = resource.ToPoco <Resource>(); var versionId = poco.Meta?.VersionId; var lastUpdated = poco.Meta?.LastUpdated; try { // Clear meta version and lastUpdated since these are set based on generated values when saving the resource if (poco.Meta != null) { poco.Meta.VersionId = null; poco.Meta.LastUpdated = null; } return(new RawResource(_fhirJsonSerializer.SerializeToString(poco), FhirResourceFormat.Json)); } finally { if (poco.Meta != null) { poco.Meta.VersionId = versionId; poco.Meta.LastUpdated = lastUpdated; } } }
public static ResourceElement UpdateLastUpdated(this ResourceElement resource, DateTimeOffset lastUpdated) { EnsureArg.IsNotNull(resource, nameof(resource)); var poco = resource.ToPoco(); poco.Meta.LastUpdated = lastUpdated; return(poco.ToResourceElement()); }
public static ResourceElement UpdateId(this ResourceElement resource, string newId) { EnsureArg.IsNotNull(resource, nameof(resource)); var poco = resource.ToPoco(); poco.Id = newId; return(poco.ToResourceElement()); }
public static ResourceElement UpdateVersion(this ResourceElement resource, string newVersion) { EnsureArg.IsNotNull(resource, nameof(resource)); var poco = resource.ToPoco(); poco.VersionId = newVersion; return(poco.ToResourceElement()); }
public void GivenASearchResult_WhenCreateSearchBundle_ThenCorrectBundleShouldBeReturned() { _urlResolver.ResolveResourceWrapperUrl(Arg.Any <ResourceWrapper>()).Returns(x => new Uri(string.Format(_resourceUrlFormat, x.ArgAt <ResourceWrapper>(0).ResourceId))); _urlResolver.ResolveRouteUrl(_unsupportedSearchParameters).Returns(_selfUrl); ResourceElement observation1 = Samples.GetDefaultObservation().UpdateId("123"); ResourceElement observation2 = Samples.GetDefaultObservation().UpdateId("abc"); var resourceWrappers = new SearchResultEntry[] { new SearchResultEntry(CreateResourceWrapper(observation1, HttpMethod.Post)), new SearchResultEntry(CreateResourceWrapper(observation2, HttpMethod.Post)), }; var searchResult = new SearchResult(resourceWrappers, continuationToken: null, sortOrder: null, unsupportedSearchParameters: _unsupportedSearchParameters); ResourceElement actual = null; using (Mock.Property(() => ClockResolver.UtcNowFunc, () => _dateTime)) { actual = _bundleFactory.CreateSearchBundle(searchResult); } // Since there is no continuation token, there should not be next link. Assert.Null(actual.Scalar <string>("Bundle.link.where(relation='next').url")); Assert.Collection( actual.ToPoco <Bundle>().Entry, async e => await ValidateEntry(observation1.ToPoco <Observation>(), e), async e => await ValidateEntry(observation2.ToPoco <Observation>(), e)); async Task ValidateEntry(Observation expected, Bundle.EntryComponent actualEntry) { Assert.NotNull(actualEntry); var raw = actualEntry as RawBundleEntryComponent; using (var ms = new MemoryStream()) using (var sr = new StreamReader(ms)) { await raw?.ResourceElement?.SerializeToStreamAsUtf8Json(ms); ms.Seek(0, SeekOrigin.Begin); var resourceData = await sr.ReadToEndAsync(); Assert.NotNull(resourceData); Resource resource; resource = new FhirJsonParser().Parse(resourceData) as Resource; Assert.Equal(expected.Id, resource.Id); Assert.Equal(string.Format(_resourceUrlFormat, expected.Id), raw.FullUrl); Assert.NotNull(raw.Search); Assert.Equal(Bundle.SearchEntryMode.Match, raw.Search.Mode); } } }
/// <inheritdoc /> public byte[] Serialize(ResourceElement resourceElement) { EnsureArg.IsNotNull(resourceElement, nameof(resourceElement)); string resourceData = _fhirJsonSerializer.SerializeToString(resourceElement.ToPoco <Resource>()); byte[] bytesToWrite = Encoding.UTF8.GetBytes($"{resourceData}\n"); return(bytesToWrite); }
private ResourceWrapper CreateResourceWrapper(ResourceElement resourceElement, HttpMethod httpMethod) { return(new ResourceWrapper( resourceElement, new RawResource(_fhirJsonSerializer.SerializeToString(resourceElement.ToPoco <Observation>()), FhirResourceFormat.Json, isMetaSet: false), new ResourceRequest(httpMethod, url: "http://test/Resource/resourceId"), false, null, null, null)); }
/// <inheritdoc /> public byte[] Serialize(ResourceWrapper resourceWrapper) { EnsureArg.IsNotNull(resourceWrapper, nameof(resourceWrapper)); ResourceElement resource = _resourceDeserializer.DeserializeRaw(resourceWrapper.RawResource, resourceWrapper.Version, resourceWrapper.LastModified); string resourceData = _fhirJsonSerializer.SerializeToString(resource.ToPoco <Resource>()); byte[] bytesToWrite = Encoding.UTF8.GetBytes($"{resourceData}\n"); return(bytesToWrite); }
/// <summary> /// Executes a FHIRPath Patch Delete operation. Delete operations will /// remove the element at the specified operation.Path. /// /// Deletion at a non-existing path is not supposed to result in an error. /// /// Fhir package has a built-in "Remove" operation which accomplishes /// this easily. /// </summary> /// <returns>Patched FHIR Resource as POCO.</returns> internal override Resource Execute() { try { Target.Parent.Remove(Target); return(ResourceElement.ToPoco <Resource>()); } catch (InvalidOperationException) { return(ResourceElement.ToPoco <Resource>()); } }
public void GivenASearchResult_WhenCreateSearchBundle_ThenCorrectBundleShouldBeReturned() { _urlResolver.ResolveResourceUrl(Arg.Any <ResourceElement>()).Returns(x => new Uri(string.Format(_resourceUrlFormat, x.ArgAt <ResourceElement>(0).Id))); _urlResolver.ResolveRouteUrl(_unsupportedSearchParameters).Returns(_selfUrl); ResourceElement observation1 = Samples.GetDefaultObservation().UpdateId("123"); ResourceElement observation2 = Samples.GetDefaultObservation().UpdateId("abc"); var resourceWrappers = new ResourceWrapper[] { CreateResourceWrapper(observation1), CreateResourceWrapper(observation2), }; var searchResult = new SearchResult(resourceWrappers, _unsupportedSearchParameters, continuationToken: null); ResourceElement actual = null; using (Mock.Property(() => Clock.UtcNowFunc, () => _dateTime)) { actual = _bundleFactory.CreateSearchBundle(searchResult); } // Since there is no continuation token, there should not be next link. Assert.Null(actual.Scalar <string>("Bundle.link.where(relation='next').url")); Assert.Collection( actual.ToPoco <Bundle>().Entry, e => ValidateEntry(observation1.ToPoco <Observation>(), e), e => ValidateEntry(observation2.ToPoco <Observation>(), e)); ResourceWrapper CreateResourceWrapper(ResourceElement resourceElement) { return(new ResourceWrapper( resourceElement, new RawResource(_fhirJsonSerializer.SerializeToString(resourceElement.ToPoco <Observation>()), FhirResourceFormat.Json), null, false, null, null, null)); } void ValidateEntry(Observation expected, Bundle.EntryComponent actualEntry) { Assert.NotNull(actualEntry); Assert.NotNull(actualEntry.Resource); Assert.Equal(expected.Id, actualEntry.Resource.Id); Assert.Equal(string.Format(_resourceUrlFormat, expected.Id), actualEntry.FullUrl); Assert.NotNull(actualEntry.Search); Assert.Equal(Bundle.SearchEntryMode.Match, actualEntry.Search.Mode); } }
/// <summary> /// Executes a FHIRPath Patch Move operation. Move operations will /// move an existing element inside a list at the specified /// operation.Path from the index of operation.Source to the index of /// operation.Destination. /// /// Fhir package does NOT have a built-in operation which accomplishes /// this. So we must inspect the existing list and recreate it with the /// correct elements in order. /// </summary> /// <returns>Patched FHIR Resource as POCO.</returns> internal override Resource Execute() { // Setup var targetParent = Target.Parent; var name = Target.Name; // Check indexes var targetLen = targetParent.Children(name).Count(); if (Operation.Source < 0 || Operation.Source >= targetLen) { throw new InvalidOperationException("Move source index out of bounds of target list"); } if (Operation.Destination < 0 || Operation.Destination >= targetLen) { throw new InvalidOperationException("Move destination index out of bounds of target list"); } // Remove specified element from the list var elementToMove = targetParent.AtIndex(name, Operation.Source ?? -1); if (!targetParent.Remove(elementToMove)) { throw new InvalidOperationException(); } // There is no easy "move" operation in the FHIR library, so we must // iterate over the list to reconstruct it. foreach (var child in targetParent.Children(name).ToList() .Select(x => x.ToElementNode()) .Select((value, index) => (value, index))) { // Add the new item at the correct index if (Operation.Destination == child.index) { targetParent.Add(Provider, elementToMove, name); } // Remove the old element from the list so the new order is used if (!targetParent.Remove(child.value)) { throw new InvalidOperationException(); } // Add the old element back to the list targetParent.Add(Provider, child.value, name); } return(ResourceElement.ToPoco <Resource>()); }
private void SetupDataStoreToReturnDummyResourceWrapper() { ResourceElement patientResourceElement = Samples.GetDefaultPatient(); Patient patientResource = patientResourceElement.ToPoco <Patient>(); RawResource rawResource = new RawResource(new FhirJsonSerializer().SerializeToString(patientResource), FhirResourceFormat.Json, isMetaSet: false); ResourceWrapper dummyResourceWrapper = new ResourceWrapper( patientResourceElement, rawResource, request: null, deleted: false, searchIndices: null, compartmentIndices: null, lastModifiedClaims: null); _fhirDataStore.GetAsync(Arg.Any <ResourceKey>(), _cancellationToken).Returns(Task.FromResult(dummyResourceWrapper)); }
public ResourceElement CreateSearchBundle(SearchResult result) { return(CreateBundle(result, Bundle.BundleType.Searchset, r => { ResourceElement resource = _deserializer.Deserialize(r.Resource); return new Bundle.EntryComponent { FullUrlElement = new FhirUri(_urlResolver.ResolveResourceUrl(resource)), Resource = resource.ToPoco <Resource>(), Search = new Bundle.SearchComponent { Mode = r.SearchEntryMode == SearchEntryMode.Match ? Bundle.SearchEntryMode.Match : Bundle.SearchEntryMode.Include, }, }; })); }
public async Task GivenAResourceTypeWithNoVersionVersioningPolicy_WhenSearchingHistory_ThenOnlyLatestVersionIsReturned() { // The FHIR storage fixture configures organization resources to have the "no-version" versioning policy RawResourceElement organizationResource = await Mediator.CreateResourceAsync(Samples.GetDefaultOrganization()); ResourceElement newResourceValues = Samples.GetDefaultOrganization().UpdateId(organizationResource.Id); SaveOutcome updateResult = await Mediator.UpsertResourceAsync(newResourceValues, WeakETag.FromVersionId(organizationResource.VersionId)); ResourceElement historyResults = await Mediator.SearchResourceHistoryAsync(KnownResourceTypes.Organization, updateResult.RawResourceElement.Id); // The history bundle only has one entry because resource history is not kept Bundle bundle = historyResults.ToPoco <Bundle>(); Assert.Single(bundle.Entry); Assert.Equal(WeakETag.FromVersionId(updateResult.RawResourceElement.VersionId).ToString(), bundle.Entry[0].Response.Etag); }
public async Task <SearchResourceResponse> Handle(SearchResourceRequest request, CancellationToken cancellationToken, RequestHandlerDelegate <SearchResourceResponse> next) { EnsureArg.IsNotNull(request, nameof(request)); Tuple <string, string> listParameter = request.Queries .FirstOrDefault(x => string.Equals(x.Item1, KnownQueryParameterNames.List, StringComparison.OrdinalIgnoreCase)); // if _list was not requested, or _list was requested with invalid value, continue... if ((listParameter == null) || string.IsNullOrWhiteSpace(listParameter.Item2)) { return(await next()); } ResourceWrapper listWrapper = await _dataStore.Value.GetAsync(new ResourceKey <Hl7.Fhir.Model.List>(listParameter.Item2), cancellationToken); // wanted list was not found if (listWrapper == null) { return(CreateEmptySearchResponse(request)); } ResourceElement list = _deserializer.Deserialize(listWrapper); IEnumerable <ReferenceSearchValue> references = list.ToPoco <Hl7.Fhir.Model.List>() .Entry .Where(x => x.Deleted != true) .Select(x => _referenceSearchValueParser.Parse(x.Item.Reference)) .Where(x => string.IsNullOrWhiteSpace(request.ResourceType) || string.Equals(request.ResourceType, x.ResourceType, StringComparison.Ordinal)) .ToArray(); // the requested resource was not found in the list if (!references.Any()) { return(CreateEmptySearchResponse(request)); } // Remove the 'list' params from the queries, to be later replaced with specific ids of resources IEnumerable <Tuple <string, string> > query = request.Queries.Except(new[] { listParameter }); query = query.Concat(new[] { Tuple.Create(KnownQueryParameterNames.Id, string.Join(",", references.Select(x => x.ResourceId))) }); request.Queries = query.ToArray(); return(await next()); }
public async Task GivenAResourceTypeWithVersionedUpdateVersioningPolicy_WhenSearchingHistory_ThenAllVersionsAreReturned() { // The FHIR storage fixture configures medication resources to have the "versioned-update" versioning policy RawResourceElement medicationResource = await Mediator.CreateResourceAsync(Samples.GetDefaultMedication()); ResourceElement newResourceValues = Samples.GetDefaultMedication().UpdateId(medicationResource.Id); SaveOutcome updateResult = await Mediator.UpsertResourceAsync(newResourceValues, WeakETag.FromVersionId(medicationResource.VersionId)); ResourceElement historyResults = await Mediator.SearchResourceHistoryAsync(KnownResourceTypes.Medication, updateResult.RawResourceElement.Id); // The history bundle has both versions because history is kept Bundle bundle = historyResults.ToPoco <Bundle>(); Assert.Equal(2, bundle.Entry.Count); Assert.Equal(WeakETag.FromVersionId(updateResult.RawResourceElement.VersionId).ToString(), bundle.Entry.Max(entry => entry.Response.Etag)); Assert.Equal(WeakETag.FromVersionId(medicationResource.VersionId).ToString(), bundle.Entry.Min(entry => entry.Response.Etag)); }
/// <summary> /// Executes a FHIRPath Patch Insert operation. Insert operations will /// add a new element to a list at the specified operation.Path with the /// index operation.index. /// /// Fhir package does NOT have a built-in operation which accomplishes /// this. So we must inspect the existing list and recreate it with the /// correct elements in order. /// </summary> /// <returns>Patched FHIR Resource as POCO.</returns> internal override Resource Execute() { // Setup; var targetParent = Target.Parent; var name = Target.Name; var listElements = targetParent.Children(name).ToList() .Select(x => x as ElementNode) .Select((value, index) => (value, index)) .ToList(); // Ensure index is in bounds if (Operation.Index < 0 || Operation.Index > listElements.Count) { throw new InvalidOperationException("Insert index out of bounds of target list"); } // There is no easy "insert" operation in the FHIR library, so we must // iterate over the list and recreate it. foreach (var child in listElements) { // Add the new item at the correct index if (Operation.Index == child.index) { targetParent.Add(Provider, ValueElementNode); } // Remove the old element from the list so the new order is used if (!targetParent.Remove(child.value)) { throw new InvalidOperationException(); } // Add the old element back to the list targetParent.Add(Provider, child.value, name); } return(ResourceElement.ToPoco <Resource>()); }
/// <inheritdoc /> public byte[] Serialize(ResourceWrapper resourceWrapper) { EnsureArg.IsNotNull(resourceWrapper, nameof(resourceWrapper)); string resourceData = null; if (resourceWrapper.RawResource.Format == Core.Models.FhirResourceFormat.Json) { // This is JSON already, we can write as is. resourceData = resourceWrapper.RawResource.Data; } else { // This is not JSON, so deserialize it and serialize it to JSON. ResourceElement resource = _resourceDeserializer.DeserializeRaw(resourceWrapper.RawResource, resourceWrapper.Version, resourceWrapper.LastModified); resourceData = _fhirJsonSerializer.SerializeToString(resource.ToPoco <Resource>()); } byte[] bytesToWrite = Encoding.UTF8.GetBytes($"{resourceData}\n"); return(bytesToWrite); }
/// <inheritdoc /> public IReadOnlyCollection <SearchIndexEntry> Extract(ResourceElement resource) { EnsureArg.IsNotNull(resource, nameof(resource)); Resource poco = resource.ToPoco(); var entries = new List <SearchIndexEntry>(); var context = new FhirEvaluationContext(poco) { // Allows the indexer to parse the 'resolve' function i.e. CarePlan.subject.where(resolve() is Patient) ElementResolver = _referenceToElementResolver.Resolve, }; IEnumerable <SearchParameterInfo> searchParameters = _searchParameterDefinitionManager.GetSearchParameters(resource.InstanceType); foreach (SearchParameterInfo searchParameter in searchParameters) { if (searchParameter.Code == SearchParameterNames.ResourceType) { // We don't index the resource type value. We just use the property on the root document. continue; } if (searchParameter.Type == SearchParamType.Composite) { entries.AddRange(ProcessCompositeSearchParameter(searchParameter, poco, context)); } else { entries.AddRange(ProcessNonCompositeSearchParameter(searchParameter, poco, context)); } } return(entries); }
public async Task GivenAResourceTypeWithVersionedVersioningPolicy_WhenSearchingHistory_ThenAllVersionsAreReturned() { // The FHIR storage fixture configures observation resources to have the "versioned" versioning policy RawResourceElement observationResource = await Mediator.CreateResourceAsync(Samples.GetDefaultObservation()); ResourceElement newResourceValues = Samples.GetDefaultObservation().UpdateId(observationResource.Id); newResourceValues.ToPoco <Observation>().Text = new Narrative { Status = Narrative.NarrativeStatus.Generated, Div = $"<div>{ContentUpdated}</div>", }; SaveOutcome updateResult = await Mediator.UpsertResourceAsync(newResourceValues, WeakETag.FromVersionId(observationResource.VersionId)); ResourceElement historyResults = await Mediator.SearchResourceHistoryAsync(KnownResourceTypes.Observation, updateResult.RawResourceElement.Id); // The history bundle has both versions because history is kept Bundle bundle = historyResults.ToPoco <Bundle>(); Assert.Equal(2, bundle.Entry.Count); Assert.Equal(WeakETag.FromVersionId(updateResult.RawResourceElement.VersionId).ToString(), bundle.Entry.Max(entry => entry.Response.Etag)); Assert.Equal(WeakETag.FromVersionId(observationResource.VersionId).ToString(), bundle.Entry.Min(entry => entry.Response.Etag)); }
/// <summary> /// Executes a FHIRPath Patch Add operation. Add operations will add a new /// element of the specified opearation.Name at the specified operation.Path. /// /// Adding of a non-existent element will create the new element. Add on /// a list will append the list. /// /// Fhir package has a built-in "Add" operation which accomplishes this /// easily. /// </summary> /// <returns>Patched FHIR Resource as POCO.</returns> internal override Resource Execute() { Target.Add(Provider, ValueElementNode); return(ResourceElement.ToPoco <Resource>()); }
public bool TryValidate(ResourceElement value, ICollection <ValidationResult> validationResults = null, bool recurse = false) { return(DotNetAttributeValidation.TryValidate(value.ToPoco(), validationResults, recurse)); }