public SearchOptions Create(string compartmentType, string compartmentId, string resourceType, IReadOnlyList <Tuple <string, string> > queryParameters)
        {
            var searchOptions = new SearchOptions();

            string continuationToken = null;

            var  searchParams = new SearchParams();
            var  unsupportedSearchParameters = new List <Tuple <string, string> >();
            bool setDefaultBundleTotal       = true;

            // Extract the continuation token, filter out the other known query parameters that's not search related.
            foreach (Tuple <string, string> query in queryParameters ?? Enumerable.Empty <Tuple <string, string> >())
            {
                if (query.Item1 == KnownQueryParameterNames.ContinuationToken)
                {
                    // This is an unreachable case. The mapping of the query parameters makes it so only one continuation token can exist.
                    if (continuationToken != null)
                    {
                        throw new InvalidSearchOperationException(
                                  string.Format(Core.Resources.MultipleQueryParametersNotAllowed, KnownQueryParameterNames.ContinuationToken));
                    }

                    try
                    {
                        continuationToken = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(query.Item2));
                    }
                    catch (FormatException)
                    {
                        throw new BadRequestException(Core.Resources.InvalidContinuationToken);
                    }

                    setDefaultBundleTotal = false;
                }
                else if (query.Item1 == KnownQueryParameterNames.Format)
                {
                    // TODO: We need to handle format parameter.
                }
                else if (string.Equals(query.Item1, KnownQueryParameterNames.Type, StringComparison.OrdinalIgnoreCase))
                {
                    var types    = query.Item2.SplitByOrSeparator();
                    var badTypes = types.Where(type => !ModelInfoProvider.IsKnownResource(type)).ToHashSet();

                    if (badTypes.Count != 0)
                    {
                        _contextAccessor.FhirRequestContext.BundleIssues.Add(
                            new OperationOutcomeIssue(
                                OperationOutcomeConstants.IssueSeverity.Warning,
                                OperationOutcomeConstants.IssueType.NotSupported,
                                string.Format(Core.Resources.InvalidTypeParameter, badTypes.OrderBy(x => x).Select(type => $"'{type}'").JoinByOrSeparator())));
                        if (badTypes.Count != types.Count)
                        {
                            // In case of we have acceptable types, we filter invalid types from search.
                            searchParams.Add(KnownQueryParameterNames.Type, types.Except(badTypes).JoinByOrSeparator());
                        }
                        else
                        {
                            // If all types are invalid, we add them to search params. If we remove them, we wouldn't filter by type, and return all types,
                            // which is incorrect behaviour. Optimally we should indicate in search options what it would yield nothing, and skip search,
                            // but there is no option for that right now.
                            searchParams.Add(KnownQueryParameterNames.Type, query.Item2);
                        }
                    }
                    else
                    {
                        searchParams.Add(KnownQueryParameterNames.Type, query.Item2);
                    }
                }
                else if (string.IsNullOrWhiteSpace(query.Item1) || string.IsNullOrWhiteSpace(query.Item2))
                {
                    // Query parameter with empty value is not supported.
                    unsupportedSearchParameters.Add(query);
                }
                else if (string.Compare(query.Item1, KnownQueryParameterNames.Total, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    if (Enum.TryParse <TotalType>(query.Item2, true, out var totalType))
                    {
                        ValidateTotalType(totalType);

                        searchOptions.IncludeTotal = totalType;
                        setDefaultBundleTotal      = false;
                    }
                    else
                    {
                        throw new BadRequestException(string.Format(Core.Resources.InvalidTotalParameter, query.Item2, SupportedTotalTypes));
                    }
                }
                else
                {
                    // Parse the search parameters.
                    try
                    {
                        // Basic format checking (e.g. integer value for _count key etc.).
                        searchParams.Add(query.Item1, query.Item2);
                    }
                    catch (Exception ex)
                    {
                        throw new BadRequestException(ex.Message);
                    }
                }
            }

            searchOptions.ContinuationToken = continuationToken;

            if (setDefaultBundleTotal)
            {
                ValidateTotalType(_featureConfiguration.IncludeTotalInBundle);
                searchOptions.IncludeTotal = _featureConfiguration.IncludeTotalInBundle;
            }

            // Check the item count.
            if (searchParams.Count != null)
            {
                searchOptions.MaxItemCountSpecifiedByClient = true;

                if (searchParams.Count > _featureConfiguration.MaxItemCountPerSearch)
                {
                    searchOptions.MaxItemCount = _featureConfiguration.MaxItemCountPerSearch;

                    _contextAccessor.FhirRequestContext.BundleIssues.Add(
                        new OperationOutcomeIssue(
                            OperationOutcomeConstants.IssueSeverity.Information,
                            OperationOutcomeConstants.IssueType.Informational,
                            string.Format(Core.Resources.SearchParamaterCountExceedLimit, _featureConfiguration.MaxItemCountPerSearch, searchParams.Count)));
                }
                else
                {
                    searchOptions.MaxItemCount = searchParams.Count.Value;
                }
            }
            else
            {
                searchOptions.MaxItemCount = _featureConfiguration.DefaultItemCountPerSearch;
            }

            searchOptions.IncludeCount = _featureConfiguration.DefaultIncludeCountPerSearch;

            // Check to see if only the count should be returned
            searchOptions.CountOnly = searchParams.Summary == SummaryType.Count;

            // If the resource type is not specified, then the common
            // search parameters should be used.
            ResourceType parsedResourceType = ResourceType.DomainResource;

            if (!string.IsNullOrWhiteSpace(resourceType) &&
                !Enum.TryParse(resourceType, out parsedResourceType))
            {
                throw new ResourceNotSupportedException(resourceType);
            }

            var searchExpressions = new List <Expression>();

            if (!string.IsNullOrWhiteSpace(resourceType))
            {
                searchExpressions.Add(Expression.SearchParameter(_resourceTypeSearchParameter, Expression.StringEquals(FieldName.TokenCode, null, resourceType, false)));
            }

            searchExpressions.AddRange(searchParams.Parameters.Select(
                                           q =>
            {
                try
                {
                    return(_expressionParser.Parse(parsedResourceType.ToString(), q.Item1, q.Item2));
                }
                catch (SearchParameterNotSupportedException)
                {
                    unsupportedSearchParameters.Add(q);

                    return(null);
                }
            })
                                       .Where(item => item != null));

            if (searchParams.Include?.Count > 0)
            {
                searchExpressions.AddRange(searchParams.Include.Select(
                                               q => _expressionParser.ParseInclude(parsedResourceType.ToString(), q, false /* not reversed */, false /* no iterate */))
                                           .Where(item => item != null));
            }

            if (searchParams.RevInclude?.Count > 0)
            {
                searchExpressions.AddRange(searchParams.RevInclude.Select(
                                               q => _expressionParser.ParseInclude(parsedResourceType.ToString(), q, true /* reversed */, false /* no iterate */))
                                           .Where(item => item != null));
            }

            // Parse _include:iterate (_include:recurse) parameters.
            // :iterate (:recurse) modifiers are not supported by Hl7.Fhir.Rest, hence not added to the Include collection and exist in the Parameters list.
            // See https://github.com/FirelyTeam/fhir-net-api/issues/222
            // _include:iterate (_include:recurse) expression may appear without a preceding _include parameter
            // when applied on a circular reference
            searchExpressions.AddRange(ParseIncludeIterateExpressions(searchParams));

            // remove _include:iterate and _revinclude:iterate parameters from unsupportedSearchParameters
            unsupportedSearchParameters.RemoveAll(p => AllIterateModifiers.Contains(p.Item1));

            if (!string.IsNullOrWhiteSpace(compartmentType))
            {
                if (Enum.TryParse(compartmentType, out CompartmentType parsedCompartmentType))
                {
                    if (string.IsNullOrWhiteSpace(compartmentId))
                    {
                        throw new InvalidSearchOperationException(Core.Resources.CompartmentIdIsInvalid);
                    }

                    searchExpressions.Add(Expression.CompartmentSearch(compartmentType, compartmentId));
                }
                else
                {
                    throw new InvalidSearchOperationException(string.Format(Core.Resources.CompartmentTypeIsInvalid, compartmentType));
                }
            }

            if (searchExpressions.Count == 1)
            {
                searchOptions.Expression = searchExpressions[0];
            }
            else if (searchExpressions.Count > 1)
            {
                searchOptions.Expression = Expression.And(searchExpressions.ToArray());
            }

            if (unsupportedSearchParameters.Any())
            {
                // TODO: Client can specify whether exception should be raised or not when it encounters unknown search parameters.
                // For now, we will ignore any unknown search parameters.
            }

            searchOptions.UnsupportedSearchParams = unsupportedSearchParameters;

            if (searchParams.Sort?.Count > 0)
            {
                var  sortings      = new List <(SearchParameterInfo, SortOrder)>(searchParams.Sort.Count);
                bool sortingsValid = true;

                foreach (Tuple <string, Hl7.Fhir.Rest.SortOrder> sorting in searchParams.Sort)
                {
                    try
                    {
                        SearchParameterInfo searchParameterInfo = _searchParameterDefinitionManager.GetSearchParameter(parsedResourceType.ToString(), sorting.Item1);
                        sortings.Add((searchParameterInfo, sorting.Item2.ToCoreSortOrder()));
                    }
                    catch (SearchParameterNotSupportedException)
                    {
                        sortingsValid = false;
                        _contextAccessor.FhirRequestContext.BundleIssues.Add(new OperationOutcomeIssue(
                                                                                 OperationOutcomeConstants.IssueSeverity.Warning,
                                                                                 OperationOutcomeConstants.IssueType.NotSupported,
                                                                                 string.Format(CultureInfo.InvariantCulture, Core.Resources.SearchParameterNotSupported, sorting.Item1, parsedResourceType.ToString())));
                    }
                }

                if (sortingsValid)
                {
                    if (!_sortingValidator.ValidateSorting(sortings, out IReadOnlyList <string> errorMessages))
                    {
                        if (errorMessages == null || errorMessages.Count == 0)
                        {
                            throw new InvalidOperationException($"Expected {_sortingValidator.GetType().Name} to return error messages when {nameof(_sortingValidator.ValidateSorting)} returns false");
                        }

                        sortingsValid = false;

                        foreach (var errorMessage in errorMessages)
                        {
                            _contextAccessor.FhirRequestContext.BundleIssues.Add(new OperationOutcomeIssue(
                                                                                     OperationOutcomeConstants.IssueSeverity.Warning,
                                                                                     OperationOutcomeConstants.IssueType.NotSupported,
                                                                                     errorMessage));
                        }
                    }
                }

                if (sortingsValid)
                {
                    searchOptions.Sort = sortings;
                }
            }

            if (searchOptions.Sort == null)
            {
                searchOptions.Sort = Array.Empty <(SearchParameterInfo searchParameterInfo, SortOrder sortOrder)>();
            }

            return(searchOptions);

            IEnumerable <IncludeExpression> ParseIncludeIterateExpressions(SearchParams searchParams)
            {
                return(searchParams.Parameters
                       .Where(p => p != null && AllIterateModifiers.Where(m => string.Equals(p.Item1, m, StringComparison.OrdinalIgnoreCase)).Any())
                       .Select(p =>
                {
                    var includeResourceType = p.Item2?.Split(':')[0];
                    if (!ModelInfoProvider.IsKnownResource(includeResourceType))
                    {
                        throw new ResourceNotSupportedException(includeResourceType);
                    }

                    var reversed = RevIncludeIterateModifiers.Contains(p.Item1);
                    var expression = _expressionParser.ParseInclude(includeResourceType, p.Item2, reversed, true);

                    // Reversed Iterate expressions (not wildcard) must specify target type if there is more than one possible target type
                    if (expression.Reversed && expression.Iterate && expression.TargetResourceType == null && expression.ReferenceSearchParameter?.TargetResourceTypes?.Count > 1)
                    {
                        throw new BadRequestException(string.Format(Core.Resources.RevIncludeIterateTargetTypeNotSpecified, p.Item2));
                    }

                    // For circular include iterate expressions, add an informational issue indicating that a single iteration is supported.
                    // See https://www.hl7.org/fhir/search.html#revinclude.
                    if (expression.Iterate && expression.CircularReference)
                    {
                        _contextAccessor.FhirRequestContext.BundleIssues.Add(
                            new OperationOutcomeIssue(
                                OperationOutcomeConstants.IssueSeverity.Information,
                                OperationOutcomeConstants.IssueType.Informational,
                                string.Format(Core.Resources.IncludeIterateCircularReferenceExecutedOnce, p.Item1, p.Item2)));
                    }

                    return expression;
                }));
            }

            void ValidateTotalType(TotalType totalType)
            {
                // Estimate is not yet supported.
                if (totalType == TotalType.Estimate)
                {
                    throw new SearchOperationNotSupportedException(string.Format(Core.Resources.UnsupportedTotalParameter, totalType, SupportedTotalTypes));
                }
            }
        }
Exemplo n.º 2
0
        public SearchOptions Create(string compartmentType, string compartmentId, string resourceType, IReadOnlyList <Tuple <string, string> > queryParameters)
        {
            var options = new SearchOptions();

            string continuationToken = null;

            var searchParams = new SearchParams();
            var unsupportedSearchParameters = new List <Tuple <string, string> >();

            // Extract the continuation token, filter out the other known query parameters that's not search related.
            foreach (Tuple <string, string> query in queryParameters ?? Enumerable.Empty <Tuple <string, string> >())
            {
                if (query.Item1 == KnownQueryParameterNames.ContinuationToken)
                {
                    if (continuationToken != null)
                    {
                        throw new InvalidSearchOperationException(
                                  string.Format(Core.Resources.MultipleQueryParametersNotAllowed, KnownQueryParameterNames.ContinuationToken));
                    }

                    continuationToken = query.Item2;
                }
                else if (query.Item1 == KnownQueryParameterNames.Format)
                {
                    // TODO: We need to handle format parameter.
                }
                else if (string.IsNullOrWhiteSpace(query.Item2))
                {
                    // Query parameter with empty value is not supported.
                    unsupportedSearchParameters.Add(query);
                }
                else
                {
                    // Parse the search parameters.
                    try
                    {
                        searchParams.Add(query.Item1, query.Item2);
                    }
                    catch (Exception ex)
                    {
                        _logger.LogInformation(ex, "Failed to parse the query parameter. Skipping.");

                        // There was a problem parsing the parameter. Add it to list of unsupported parameters.
                        unsupportedSearchParameters.Add(query);
                    }
                }
            }

            options.ContinuationToken = continuationToken;

            // Check the item count.
            if (searchParams.Count != null)
            {
                options.MaxItemCount = searchParams.Count.Value;
            }

            // Check to see if only the count should be returned
            options.CountOnly = searchParams.Summary == SummaryType.Count;

            // If the resource type is not specified, then the common
            // search parameters should be used.
            ResourceType parsedResourceType = ResourceType.DomainResource;

            if (!string.IsNullOrWhiteSpace(resourceType) &&
                !Enum.TryParse(resourceType, out parsedResourceType))
            {
                throw new ResourceNotSupportedException(resourceType);
            }

            var searchExpressions = new List <Expression>();

            if (!string.IsNullOrWhiteSpace(resourceType))
            {
                searchExpressions.Add(Expression.SearchParameter(_resourceTypeSearchParameter.ToInfo(), Expression.Equals(FieldName.TokenCode, null, resourceType)));
            }

            searchExpressions.AddRange(searchParams.Parameters.Select(
                                           q =>
            {
                try
                {
                    return(_expressionParser.Parse(parsedResourceType.ToString(), q.Item1, q.Item2));
                }
                catch (SearchParameterNotSupportedException)
                {
                    unsupportedSearchParameters.Add(q);

                    return(null);
                }
            })
                                       .Where(item => item != null));

            if (!string.IsNullOrWhiteSpace(compartmentType))
            {
                if (Enum.TryParse(compartmentType, out CompartmentType parsedCompartmentType))
                {
                    if (string.IsNullOrWhiteSpace(compartmentId))
                    {
                        throw new InvalidSearchOperationException(Core.Resources.CompartmentIdIsInvalid);
                    }

                    searchExpressions.Add(Expression.CompartmentSearch(compartmentType, compartmentId));
                }
                else
                {
                    throw new InvalidSearchOperationException(string.Format(Core.Resources.CompartmentTypeIsInvalid, compartmentType));
                }
            }

            if (searchExpressions.Count == 1)
            {
                options.Expression = searchExpressions[0];
            }
            else if (searchExpressions.Count > 1)
            {
                options.Expression = Expression.And(searchExpressions.ToArray());
            }

            if (unsupportedSearchParameters.Any())
            {
                // TODO: Client can specify whether exception should be raised or not when it encounters unknown search parameters.
                // For now, we will ignore any unknown search parameters.
            }

            options.UnsupportedSearchParams = unsupportedSearchParameters;

            return(options);
        }
        public SearchOptions Create(string compartmentType, string compartmentId, string resourceType, IReadOnlyList <Tuple <string, string> > queryParameters)
        {
            var searchOptions = new SearchOptions();

            string continuationToken = null;

            var searchParams = new SearchParams();
            var unsupportedSearchParameters = new List <Tuple <string, string> >();

            // Extract the continuation token, filter out the other known query parameters that's not search related.
            foreach (Tuple <string, string> query in queryParameters ?? Enumerable.Empty <Tuple <string, string> >())
            {
                if (query.Item1 == KnownQueryParameterNames.ContinuationToken)
                {
                    // This is an unreachable case. The mapping of the query parameters makes it so only one continuation token can exist.
                    if (continuationToken != null)
                    {
                        throw new InvalidSearchOperationException(
                                  string.Format(Core.Resources.MultipleQueryParametersNotAllowed, KnownQueryParameterNames.ContinuationToken));
                    }

                    // Checks if the continuation token is base 64 bit encoded. Needed for systems that have cached continuation tokens from before they were encoded.
                    if (Base64FormatRegex.IsMatch(query.Item2))
                    {
                        continuationToken = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(query.Item2));
                    }
                    else
                    {
                        continuationToken = query.Item2;
                    }
                }
                else if (query.Item1 == KnownQueryParameterNames.Format)
                {
                    // TODO: We need to handle format parameter.
                }
                else if (string.IsNullOrWhiteSpace(query.Item1) || string.IsNullOrWhiteSpace(query.Item2))
                {
                    // Query parameter with empty value is not supported.
                    unsupportedSearchParameters.Add(query);
                }
                else if (string.Compare(query.Item1, KnownQueryParameterNames.Total, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    // Estimate is not yet supported.
                    var supportedTotalTypes = new string($"'{TotalType.Accurate}', '{TotalType.None}'").ToLower(CultureInfo.CurrentCulture);

                    if (Enum.TryParse <TotalType>(query.Item2, true, out var totalType))
                    {
                        if (totalType == TotalType.Estimate)
                        {
                            throw new SearchOperationNotSupportedException(string.Format(Core.Resources.UnsupportedTotalParameter, query.Item2, supportedTotalTypes));
                        }

                        searchOptions.IncludeTotal = totalType;
                    }
                    else
                    {
                        throw new BadRequestException(string.Format(Core.Resources.InvalidTotalParameter, query.Item2, supportedTotalTypes));
                    }
                }
                else
                {
                    // Parse the search parameters.
                    try
                    {
                        // Basic format checking (e.g. integer value for _count key etc.).
                        searchParams.Add(query.Item1, query.Item2);
                    }
                    catch (Exception ex)
                    {
                        throw new BadRequestException(ex.Message);
                    }
                }
            }

            searchOptions.ContinuationToken = continuationToken;

            // Check the item count.
            if (searchParams.Count != null)
            {
                searchOptions.MaxItemCount = searchParams.Count.Value;
            }

            // Check to see if only the count should be returned
            searchOptions.CountOnly = searchParams.Summary == SummaryType.Count;

            // If the resource type is not specified, then the common
            // search parameters should be used.
            ResourceType parsedResourceType = ResourceType.DomainResource;

            if (!string.IsNullOrWhiteSpace(resourceType) &&
                !Enum.TryParse(resourceType, out parsedResourceType))
            {
                throw new ResourceNotSupportedException(resourceType);
            }

            var searchExpressions = new List <Expression>();

            if (!string.IsNullOrWhiteSpace(resourceType))
            {
                searchExpressions.Add(Expression.SearchParameter(_resourceTypeSearchParameter, Expression.StringEquals(FieldName.TokenCode, null, resourceType, false)));
            }

            searchExpressions.AddRange(searchParams.Parameters.Select(
                                           q =>
            {
                try
                {
                    return(_expressionParser.Parse(parsedResourceType.ToString(), q.Item1, q.Item2));
                }
                catch (SearchParameterNotSupportedException)
                {
                    unsupportedSearchParameters.Add(q);

                    return(null);
                }
            })
                                       .Where(item => item != null));

            if (searchParams.Include?.Count > 0)
            {
                searchExpressions.AddRange(searchParams.Include.Select(
                                               q => _expressionParser.ParseInclude(parsedResourceType.ToString(), q))
                                           .Where(item => item != null));
            }

            if (!string.IsNullOrWhiteSpace(compartmentType))
            {
                if (Enum.TryParse(compartmentType, out CompartmentType parsedCompartmentType))
                {
                    if (string.IsNullOrWhiteSpace(compartmentId))
                    {
                        throw new InvalidSearchOperationException(Core.Resources.CompartmentIdIsInvalid);
                    }

                    searchExpressions.Add(Expression.CompartmentSearch(compartmentType, compartmentId));
                }
                else
                {
                    throw new InvalidSearchOperationException(string.Format(Core.Resources.CompartmentTypeIsInvalid, compartmentType));
                }
            }

            if (searchExpressions.Count == 1)
            {
                searchOptions.Expression = searchExpressions[0];
            }
            else if (searchExpressions.Count > 1)
            {
                searchOptions.Expression = Expression.And(searchExpressions.ToArray());
            }

            if (unsupportedSearchParameters.Any())
            {
                // TODO: Client can specify whether exception should be raised or not when it encounters unknown search parameters.
                // For now, we will ignore any unknown search parameters.
            }

            searchOptions.UnsupportedSearchParams = unsupportedSearchParameters;

            if (searchParams.Sort?.Count > 0)
            {
                var sortings = new List <(SearchParameterInfo, SortOrder)>();
                List <(string parameterName, string reason)> unsupportedSortings = null;

                foreach (Tuple <string, Hl7.Fhir.Rest.SortOrder> sorting in searchParams.Sort)
                {
                    try
                    {
                        SearchParameterInfo searchParameterInfo = _searchParameterDefinitionManager.GetSearchParameter(parsedResourceType.ToString(), sorting.Item1);
                        sortings.Add((searchParameterInfo, sorting.Item2.ToCoreSortOrder()));
                    }
                    catch (SearchParameterNotSupportedException)
                    {
                        (unsupportedSortings ??= new List <(string parameterName, string reason)>()).Add((sorting.Item1, string.Format(Core.Resources.SearchParameterNotSupported, sorting.Item1, resourceType)));
                    }
                }

                searchOptions.Sort = sortings;
                searchOptions.UnsupportedSortingParams = (IReadOnlyList <(string parameterName, string reason)>)unsupportedSortings ?? Array.Empty <(string parameterName, string reason)>();
            }
            else
            {
                searchOptions.Sort = Array.Empty <(SearchParameterInfo searchParameterInfo, SortOrder sortOrder)>();
                searchOptions.UnsupportedSortingParams = Array.Empty <(string parameterName, string reason)>();
            }

            return(searchOptions);
        }
Exemplo n.º 4
0
    public void Test_TokenCaseCorrect()
    {
      CleanUpByIdentifier(ResourceType.Observation);

      Hl7.Fhir.Rest.FhirClient clientFhir = new Hl7.Fhir.Rest.FhirClient(StaticTestData.FhirEndpoint(), false);
      clientFhir.Timeout = 1000 * 720; // give the call a while to execute (particularly while debugging).

      //Add an Observation one with an Effective Period 10:00 AM to 11:00 AM 
      FhirDateTime ObsOneEffectiveStart = new FhirDateTime(new DateTimeOffset(2018, 08, 5, 10, 00, 00, new TimeSpan(8, 0, 0)));
      FhirDateTime ObsOneEffectiveEnd = new FhirDateTime(new DateTimeOffset(2018, 08, 5, 11, 00, 00, new TimeSpan(8, 0, 0)));
      string ObsOneResourceId = string.Empty;
      Observation ObsOne = new Observation();
      ObsOne.Identifier = new List<Identifier>(){
         new Identifier()
         {
            System = StaticTestData.TestIdentiferSystem,
            Value = Common.Tools.FhirGuid.FhirGuid.NewFhirGuid()
         }
      };
      ObsOne.Code = new CodeableConcept();
      ObsOne.Code.Coding = new List<Coding>();
      ObsOne.Code.Coding.Add(new Coding()
      {
        System = "http:/mytestcodesystem.com/system",
        Code = "ObsOne"
      });
      var EffectiveObsOnePeriod = new Period();
      EffectiveObsOnePeriod.StartElement = ObsOneEffectiveStart;
      EffectiveObsOnePeriod.EndElement = ObsOneEffectiveEnd;
      ObsOne.Effective = EffectiveObsOnePeriod;

      Observation ObsOneResult = null;
      try
      {
        ObsOneResult = clientFhir.Create(ObsOne);
      }
      catch (Exception Exec)
      {
        Assert.True(false, "Exception thrown on resource Create: " + Exec.Message);
      }
      Assert.NotNull(ObsOneResult, "Resource create by Updated returned resource of null");
      ObsOneResourceId = ObsOneResult.Id;
      ObsOneResult = null;

      //Add an Observation one with an Effective Period 10:30 AM to 11:30 AM 
      FhirDateTime ObsTwoEffectiveStart = new FhirDateTime(new DateTimeOffset(2018, 08, 5, 10, 30, 00, new TimeSpan(8, 0, 0)));
      FhirDateTime ObsTwoEffectiveEnd = new FhirDateTime(new DateTimeOffset(2018, 08, 5, 11, 30, 00, new TimeSpan(8, 0, 0)));
      string ObsTwoResourceId = string.Empty;
      Observation ObsTwo = new Observation();
      ObsTwo.Identifier = new List<Identifier>(){
         new Identifier()
         {
            System = StaticTestData.TestIdentiferSystem,
            Value = Common.Tools.FhirGuid.FhirGuid.NewFhirGuid()
         }
      };
      ObsTwo.Code = new CodeableConcept();
      ObsTwo.Code.Coding = new List<Coding>();
      ObsTwo.Code.Coding.Add(new Coding()
      {
        System = "http:/mytestcodesystem.com/system",
        Code = "ObsTwo"
      });
      var EffectiveObsTwoPeriod = new Period();
      EffectiveObsTwoPeriod.StartElement = ObsTwoEffectiveStart;
      EffectiveObsTwoPeriod.EndElement = ObsTwoEffectiveEnd;
      ObsTwo.Effective = EffectiveObsTwoPeriod;

      Observation ObsTwoResult = null;
      try
      {
        ObsTwoResult = clientFhir.Create(ObsTwo);
      }
      catch (Exception Exec)
      {
        Assert.True(false, "Exception thrown on resource Create: " + Exec.Message);
      }
      Assert.NotNull(ObsTwoResult, "Resource create by Updated returned resource of null");
      ObsTwoResourceId = ObsTwoResult.Id;
      ObsTwoResult = null;

      //Assert
      var SearchParam = new SearchParams();
      try
      {
        //ObsOne = 10:00 to 11:00
        //ObsTwo = 10:30 to 11:30
        SearchParam.Add("identifier", $"{StaticTestData.TestIdentiferSystem}|");
        SearchParam.Add("code", "http:/mytestcodesystem.com/system|ObsOne");
        clientFhir.PreferredParameterHandling = SearchParameterHandling.Strict;
        Bundle BundleResult = clientFhir.Search<Observation>(SearchParam);

        Assert.IsNotNull(BundleResult);
        Assert.IsNotNull(BundleResult.Entry);
        Assert.AreEqual(1, BundleResult.Entry.Count);
        Assert.AreEqual(ObsOneResourceId, BundleResult.Entry[0].Resource.Id);        
      }
      catch (Exception Exec)
      {
        Assert.True(false, "Exception thrown on resource Search: " + Exec.Message);
      }

      CleanUpByIdentifier(ResourceType.Observation);
    }
Exemplo n.º 5
0
    public void Test_TokenCaseInCorrect()
    {
      CleanUpByIdentifier(ResourceType.Observation);

      Hl7.Fhir.Rest.FhirClient clientFhir = new Hl7.Fhir.Rest.FhirClient(StaticTestData.FhirEndpoint(), false);
      clientFhir.Timeout = 1000 * 720; // give the call a while to execute (particularly while debugging).

      //Add an Observation one with an Effective Period 10:00 AM to 11:00 AM 
      FhirDateTime ObsOneEffectiveStart = new FhirDateTime(new DateTimeOffset(2018, 08, 5, 10, 00, 00, new TimeSpan(8, 0, 0)));
      FhirDateTime ObsOneEffectiveEnd = new FhirDateTime(new DateTimeOffset(2018, 08, 5, 11, 00, 00, new TimeSpan(8, 0, 0)));
      string ObsOneResourceId = string.Empty;
      Observation ObsOne = new Observation();
      ObsOne.Identifier = new List<Identifier>(){
         new Identifier()
         {
            System = StaticTestData.TestIdentiferSystem,
            Value = Common.Tools.FhirGuid.FhirGuid.NewFhirGuid()
         }
      };
      ObsOne.Code = new CodeableConcept();
      ObsOne.Code.Coding = new List<Coding>();
      ObsOne.Code.Coding.Add(new Coding()
      {
        System = "http:/mytestcodesystem.com/system",
        Code = "ObsOne"
      });
      var EffectiveObsOnePeriod = new Period();
      EffectiveObsOnePeriod.StartElement = ObsOneEffectiveStart;
      EffectiveObsOnePeriod.EndElement = ObsOneEffectiveEnd;
      ObsOne.Effective = EffectiveObsOnePeriod;

      Observation ObsOneResult = null;
      try
      {
        ObsOneResult = clientFhir.Create(ObsOne);
      }
      catch (Exception Exec)
      {
        Assert.True(false, "Exception thrown on resource Create: " + Exec.Message);
      }
      Assert.NotNull(ObsOneResult, "Resource create by Updated returned resource of null");
      ObsOneResourceId = ObsOneResult.Id;
      ObsOneResult = null;

      //Add an Observation one with an Effective Period 10:30 AM to 11:30 AM 
      FhirDateTime ObsTwoEffectiveStart = new FhirDateTime(new DateTimeOffset(2018, 08, 5, 10, 30, 00, new TimeSpan(8, 0, 0)));
      FhirDateTime ObsTwoEffectiveEnd = new FhirDateTime(new DateTimeOffset(2018, 08, 5, 11, 30, 00, new TimeSpan(8, 0, 0)));
      string ObsTwoResourceId = string.Empty;
      Observation ObsTwo = new Observation();
      ObsTwo.Identifier = new List<Identifier>(){
         new Identifier()
         {
            System = StaticTestData.TestIdentiferSystem,
            Value = Common.Tools.FhirGuid.FhirGuid.NewFhirGuid()
         }
      };
      ObsTwo.Code = new CodeableConcept();
      ObsTwo.Code.Coding = new List<Coding>();
      ObsTwo.Code.Coding.Add(new Coding()
      {
        System = "http:/mytestcodesystem.com/system",
        Code = "ObsTwo"
      });
      var EffectiveObsTwoPeriod = new Period();
      EffectiveObsTwoPeriod.StartElement = ObsTwoEffectiveStart;
      EffectiveObsTwoPeriod.EndElement = ObsTwoEffectiveEnd;
      ObsTwo.Effective = EffectiveObsTwoPeriod;

      Observation ObsTwoResult = null;
      try
      {
        ObsTwoResult = clientFhir.Create(ObsTwo);
      }
      catch (Exception Exec)
      {
        Assert.True(false, "Exception thrown on resource Create: " + Exec.Message);
      }
      Assert.NotNull(ObsTwoResult, "Resource create by Updated returned resource of null");
      ObsTwoResourceId = ObsTwoResult.Id;
      ObsTwoResult = null;

      //Assert
      var SearchParam = new SearchParams();
      try
      {
        //ObsOne = 10:00 to 11:00
        //ObsTwo = 10:30 to 11:30
        SearchParam.Add("identifier", $"{StaticTestData.TestIdentiferSystem}|");
        SearchParam.Add("code", "http:/mytestcodesystem.com/system|obsone");
        clientFhir.PreferredParameterHandling = SearchParameterHandling.Strict;
        Bundle BundleResult = clientFhir.Search<Observation>(SearchParam);

        //From thr R4 FHIR Spec
        //Note: There are many challenging issues around case senstivity and token searches. 
        //Some code systems are case sensitive(e.g.UCUM) while others are known not to be.For many 
        //code systems, it's ambiguous. Other kinds of values are also ambiguous. When in doubt, servers
        //SHOULD treat tokens in a case-insensitive manner, on the grounds that including undesired data 
        //has less safety implications than excluding desired behavior. Clients SHOULD always use the 
        //correct case when possible, and allow for the server to perform case-insensitive matching.

        //STU3 was a little vage on this point. Trying to conclude case sesativity based on CodeSystem is 
        //a more difficult goal and would slow down searches, do not plan on doing that any time soon.
        Assert.IsNotNull(BundleResult);
        Assert.IsNotNull(BundleResult.Entry);
        Assert.AreEqual(1, BundleResult.Entry.Count);
        Assert.AreEqual(ObsOneResourceId, BundleResult.Entry[0].Resource.Id);
      }
      catch (Exception Exec)
      {
        Assert.True(false, "Exception thrown on resource Search: " + Exec.Message);
      }

      CleanUpByIdentifier(ResourceType.Observation);
    }
Exemplo n.º 6
0
        public SearchOptions Create(string compartmentType, string compartmentId, string resourceType, IReadOnlyList <Tuple <string, string> > queryParameters)
        {
            var searchOptions = new SearchOptions();

            string continuationToken = null;

            var  searchParams = new SearchParams();
            var  unsupportedSearchParameters = new List <Tuple <string, string> >();
            bool setDefaultBundleTotal       = true;

            // Extract the continuation token, filter out the other known query parameters that's not search related.
            foreach (Tuple <string, string> query in queryParameters ?? Enumerable.Empty <Tuple <string, string> >())
            {
                if (query.Item1 == KnownQueryParameterNames.ContinuationToken)
                {
                    // This is an unreachable case. The mapping of the query parameters makes it so only one continuation token can exist.
                    if (continuationToken != null)
                    {
                        throw new InvalidSearchOperationException(
                                  string.Format(Core.Resources.MultipleQueryParametersNotAllowed, KnownQueryParameterNames.ContinuationToken));
                    }

                    try
                    {
                        continuationToken = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(query.Item2));
                    }
                    catch (FormatException)
                    {
                        throw new BadRequestException(Core.Resources.InvalidContinuationToken);
                    }

                    setDefaultBundleTotal = false;
                }
                else if (query.Item1 == KnownQueryParameterNames.Format || query.Item1 == KnownQueryParameterNames.Pretty)
                {
                    // _format and _pretty are not search parameters, so we can ignore them.
                }
                else if (string.Equals(query.Item1, KnownQueryParameterNames.Type, StringComparison.OrdinalIgnoreCase))
                {
                    if (string.IsNullOrWhiteSpace(query.Item2))
                    {
                        throw new BadRequestException(string.Format(Core.Resources.InvalidTypeParameter, query.Item2));
                    }

                    var types    = query.Item2.SplitByOrSeparator();
                    var badTypes = types.Where(type => !ModelInfoProvider.IsKnownResource(type)).ToHashSet();

                    if (badTypes.Count != 0)
                    {
                        _contextAccessor.RequestContext?.BundleIssues.Add(
                            new OperationOutcomeIssue(
                                OperationOutcomeConstants.IssueSeverity.Warning,
                                OperationOutcomeConstants.IssueType.NotSupported,
                                string.Format(Core.Resources.InvalidTypeParameter, badTypes.OrderBy(x => x).Select(type => $"'{type}'").JoinByOrSeparator())));
                        if (badTypes.Count != types.Count)
                        {
                            // In case of we have acceptable types, we filter invalid types from search.
                            searchParams.Add(KnownQueryParameterNames.Type, types.Except(badTypes).JoinByOrSeparator());
                        }
                        else
                        {
                            // If all types are invalid, we add them to search params. If we remove them, we wouldn't filter by type, and return all types,
                            // which is incorrect behaviour. Optimally we should indicate in search options what it would yield nothing, and skip search,
                            // but there is no option for that right now.
                            searchParams.Add(KnownQueryParameterNames.Type, query.Item2);
                        }
                    }
                    else
                    {
                        searchParams.Add(KnownQueryParameterNames.Type, query.Item2);
                    }
                }
                else if (string.IsNullOrWhiteSpace(query.Item1) || string.IsNullOrWhiteSpace(query.Item2))
                {
                    // Query parameter with empty value is not supported.
                    unsupportedSearchParameters.Add(query);
                }
                else if (string.Compare(query.Item1, KnownQueryParameterNames.Total, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    if (Enum.TryParse <TotalType>(query.Item2, true, out var totalType))
                    {
                        ValidateTotalType(totalType);

                        searchOptions.IncludeTotal = totalType;
                        setDefaultBundleTotal      = false;
                    }
                    else
                    {
                        throw new BadRequestException(string.Format(Core.Resources.InvalidTotalParameter, query.Item2, SupportedTotalTypes));
                    }
                }
                else
                {
                    // Parse the search parameters.
                    try
                    {
                        // Basic format checking (e.g. integer value for _count key etc.).
                        searchParams.Add(query.Item1, query.Item2);
                    }
                    catch (Exception ex)
                    {
                        throw new BadRequestException(ex.Message);
                    }
                }
            }

            searchOptions.ContinuationToken = continuationToken;

            if (setDefaultBundleTotal)
            {
                ValidateTotalType(_featureConfiguration.IncludeTotalInBundle);
                searchOptions.IncludeTotal = _featureConfiguration.IncludeTotalInBundle;
            }

            // Check the item count.
            if (searchParams.Count != null)
            {
                searchOptions.MaxItemCountSpecifiedByClient = true;

                if (searchParams.Count > _featureConfiguration.MaxItemCountPerSearch)
                {
                    searchOptions.MaxItemCount = _featureConfiguration.MaxItemCountPerSearch;

                    _contextAccessor.RequestContext?.BundleIssues.Add(
                        new OperationOutcomeIssue(
                            OperationOutcomeConstants.IssueSeverity.Information,
                            OperationOutcomeConstants.IssueType.Informational,
                            string.Format(Core.Resources.SearchParamaterCountExceedLimit, _featureConfiguration.MaxItemCountPerSearch, searchParams.Count)));
                }
                else
                {
                    searchOptions.MaxItemCount = searchParams.Count.Value;
                }
            }
            else
            {
                searchOptions.MaxItemCount = _featureConfiguration.DefaultItemCountPerSearch;
            }

            searchOptions.IncludeCount = _featureConfiguration.DefaultIncludeCountPerSearch;

            if (searchParams.Elements?.Any() == true && searchParams.Summary != null && searchParams.Summary != SummaryType.False)
            {
                // The search parameters _elements and _summarize cannot be specified for the same request.
                throw new BadRequestException(string.Format(Core.Resources.ElementsAndSummaryParametersAreIncompatible, KnownQueryParameterNames.Summary, KnownQueryParameterNames.Elements));
            }

            // Check to see if only the count should be returned
            searchOptions.CountOnly = searchParams.Summary == SummaryType.Count;

            // If the resource type is not specified, then the common
            // search parameters should be used.
            ResourceType[] parsedResourceTypes = new[] { ResourceType.DomainResource };

            var searchExpressions = new List <Expression>();

            if (string.IsNullOrWhiteSpace(resourceType))
            {
                // Try to parse resource types from _type Search Parameter
                // This will result in empty array if _type has any modifiers
                // Which is good, since :not modifier changes the meaning of the
                // search parameter and we can no longer use it to deduce types
                // (and should proceed with ResourceType.DomainResource in that case)
                var resourceTypes = searchParams.Parameters
                                    .Where(q => q.Item1 == KnownQueryParameterNames.Type) // <-- Equality comparison to avoid modifiers
                                    .SelectMany(q => q.Item2.SplitByOrSeparator())
                                    .Where(type => ModelInfoProvider.IsKnownResource(type))
                                    .Select(x =>
                {
                    if (!Enum.TryParse(x, out ResourceType parsedType))
                    {
                        // Should never get here
                        throw new ResourceNotSupportedException(x);
                    }

                    return(parsedType);
                })
                                    .Distinct();

                if (resourceTypes.Any())
                {
                    parsedResourceTypes = resourceTypes.ToArray();
                }
            }
            else
            {
                if (!Enum.TryParse(resourceType, out parsedResourceTypes[0]))
                {
                    throw new ResourceNotSupportedException(resourceType);
                }

                searchExpressions.Add(Expression.SearchParameter(_resourceTypeSearchParameter, Expression.StringEquals(FieldName.TokenCode, null, resourceType, false)));
            }

            var resourceTypesString = parsedResourceTypes.Select(x => x.ToString()).ToArray();

            searchExpressions.AddRange(searchParams.Parameters.Select(
                                           q =>
            {
                try
                {
                    return(_expressionParser.Parse(resourceTypesString, q.Item1, q.Item2));
                }
                catch (SearchParameterNotSupportedException)
                {
                    unsupportedSearchParameters.Add(q);

                    return(null);
                }
            })
                                       .Where(item => item != null));

            // Parse _include:iterate (_include:recurse) parameters.
            // _include:iterate (_include:recurse) expression may appear without a preceding _include parameter
            // when applied on a circular reference
            searchExpressions.AddRange(ParseIncludeIterateExpressions(searchParams.Include, resourceTypesString, false));
            searchExpressions.AddRange(ParseIncludeIterateExpressions(searchParams.RevInclude, resourceTypesString, true));

            if (!string.IsNullOrWhiteSpace(compartmentType))
            {
                if (Enum.TryParse(compartmentType, out CompartmentType parsedCompartmentType))
                {
                    if (string.IsNullOrWhiteSpace(compartmentId))
                    {
                        throw new InvalidSearchOperationException(Core.Resources.CompartmentIdIsInvalid);
                    }

                    searchExpressions.Add(Expression.CompartmentSearch(compartmentType, compartmentId));
                }
                else
                {
                    throw new InvalidSearchOperationException(string.Format(Core.Resources.CompartmentTypeIsInvalid, compartmentType));
                }
            }

            if (searchExpressions.Count == 1)
            {
                searchOptions.Expression = searchExpressions[0];
            }
            else if (searchExpressions.Count > 1)
            {
                searchOptions.Expression = Expression.And(searchExpressions.ToArray());
            }

            if (unsupportedSearchParameters.Any())
            {
                bool throwForUnsupported = false;
                if (_contextAccessor.RequestContext?.RequestHeaders != null &&
                    _contextAccessor.RequestContext.RequestHeaders.TryGetValue(KnownHeaders.Prefer, out var values))
                {
                    var handlingValue = values.FirstOrDefault(x => x.StartsWith("handling=", StringComparison.OrdinalIgnoreCase));
                    if (handlingValue != default)
                    {
                        handlingValue = handlingValue.Substring("handling=".Length);

                        if (string.IsNullOrWhiteSpace(handlingValue) || !Enum.TryParse <SearchParameterHandling>(handlingValue, true, out var handling))
                        {
                            throw new BadRequestException(string.Format(
                                                              Core.Resources.InvalidHandlingValue,
                                                              handlingValue,
                                                              string.Join(",", Enum.GetNames <SearchParameterHandling>())));
                        }

                        if (handling == SearchParameterHandling.Strict)
                        {
                            throwForUnsupported = true;
                        }
                    }
                }

                if (throwForUnsupported)
                {
                    throw new BadRequestException(string.Format(
                                                      Core.Resources.SearchParameterNotSupported,
                                                      string.Join(",", unsupportedSearchParameters.Select(x => x.Item1)),
                                                      string.Join(",", resourceTypesString)));
                }
                else
                {
                    foreach (var unsupported in unsupportedSearchParameters)
                    {
                        _contextAccessor.RequestContext?.BundleIssues.Add(new OperationOutcomeIssue(
                                                                              OperationOutcomeConstants.IssueSeverity.Warning,
                                                                              OperationOutcomeConstants.IssueType.NotSupported,
                                                                              string.Format(CultureInfo.InvariantCulture, Core.Resources.SearchParameterNotSupported, unsupported.Item1, string.Join(",", resourceTypesString))));
                    }
                }
            }

            searchOptions.UnsupportedSearchParams = unsupportedSearchParameters;

            if (searchParams.Sort?.Count > 0)
            {
                var  sortings      = new List <(SearchParameterInfo, SortOrder)>(searchParams.Sort.Count);
                bool sortingsValid = true;

                foreach ((string, Hl7.Fhir.Rest.SortOrder)sorting in searchParams.Sort)
                {
                    try
                    {
                        SearchParameterInfo searchParameterInfo = resourceTypesString.Select(t => _searchParameterDefinitionManager.GetSearchParameter(t, sorting.Item1)).Distinct().First();
                        sortings.Add((searchParameterInfo, sorting.Item2.ToCoreSortOrder()));
                    }
                    catch (SearchParameterNotSupportedException)
                    {
                        sortingsValid = false;
                        _contextAccessor.RequestContext?.BundleIssues.Add(new OperationOutcomeIssue(
                                                                              OperationOutcomeConstants.IssueSeverity.Warning,
                                                                              OperationOutcomeConstants.IssueType.NotSupported,
                                                                              string.Format(CultureInfo.InvariantCulture, Core.Resources.SearchParameterNotSupported, sorting.Item1, string.Join(", ", resourceTypesString))));
                    }
                }

                if (sortingsValid)
                {
                    if (!_sortingValidator.ValidateSorting(sortings, out IReadOnlyList <string> errorMessages))
                    {
                        if (errorMessages == null || errorMessages.Count == 0)
                        {
                            throw new InvalidOperationException($"Expected {_sortingValidator.GetType().Name} to return error messages when {nameof(_sortingValidator.ValidateSorting)} returns false");
                        }

                        sortingsValid = false;

                        foreach (var errorMessage in errorMessages)
                        {
                            _contextAccessor.RequestContext?.BundleIssues.Add(new OperationOutcomeIssue(
                                                                                  OperationOutcomeConstants.IssueSeverity.Warning,
                                                                                  OperationOutcomeConstants.IssueType.NotSupported,
                                                                                  errorMessage));
                        }
                    }
                }

                if (sortingsValid)
                {
                    searchOptions.Sort = sortings;
                }
            }

            if (searchOptions.Sort == null)
            {
                searchOptions.Sort = Array.Empty <(SearchParameterInfo searchParameterInfo, SortOrder sortOrder)>();
            }

            return(searchOptions);

            IEnumerable <IncludeExpression> ParseIncludeIterateExpressions(
                IList <(string query, IncludeModifier modifier)> includes, string[] typesString, bool isReversed)
            {
                return(includes.Select(p =>
                {
                    var includeResourceTypeList = typesString;
                    var iterate = p.modifier == IncludeModifier.Iterate || p.modifier == IncludeModifier.Recurse;

                    if (iterate)
                    {
                        var includeResourceType = p.query?.Split(':')[0];
                        if (!ModelInfoProvider.IsKnownResource(includeResourceType))
                        {
                            throw new ResourceNotSupportedException(includeResourceType);
                        }

                        includeResourceTypeList = new[] { includeResourceType };
                    }

                    var expression = _expressionParser.ParseInclude(includeResourceTypeList, p.query, isReversed, iterate);

                    // Reversed Iterate expressions (not wildcard) must specify target type if there is more than one possible target type
                    if (expression.Reversed && expression.Iterate && expression.TargetResourceType == null &&
                        expression.ReferenceSearchParameter?.TargetResourceTypes?.Count > 1)
                    {
                        throw new BadRequestException(
                            string.Format(Core.Resources.RevIncludeIterateTargetTypeNotSpecified, p.query));
                    }

                    // For circular include iterate expressions, add an informational issue indicating that a single iteration is supported.
                    // See https://www.hl7.org/fhir/search.html#revinclude.
                    if (expression.Iterate && expression.CircularReference)
                    {
                        var issueProperty = string.Concat(isReversed ? "_revinclude" : "_include", ":", p.modifier.ToString().ToLowerInvariant());
                        _contextAccessor.RequestContext?.BundleIssues.Add(
                            new OperationOutcomeIssue(
                                OperationOutcomeConstants.IssueSeverity.Information,
                                OperationOutcomeConstants.IssueType.Informational,
                                string.Format(Core.Resources.IncludeIterateCircularReferenceExecutedOnce, issueProperty, p.query)));
                    }

                    return expression;
                }));
            }

            void ValidateTotalType(TotalType totalType)
            {
                // Estimate is not yet supported.
                if (totalType == TotalType.Estimate)
                {
                    throw new SearchOperationNotSupportedException(string.Format(Core.Resources.UnsupportedTotalParameter, totalType, SupportedTotalTypes));
                }
            }
        }
Exemplo n.º 7
0
        public Hl7.Fhir.Model.Bundle GetPatientByIdentifier(string system, string value)
        {
            if (string.IsNullOrWhiteSpace(system))
            {
                throw new ArgumentException("message", nameof(system));
            }

            if (string.IsNullOrWhiteSpace(value))
            {
                throw new ArgumentException("message", nameof(value));
            }

            var activity = new ActivityLog
            {
                ActivityTypeDescriptorId = (int)Entities.Activity.ActivityType.GET_PACIENTE_EN_BUS_BY_IDENTIFIER
            };


            //var baseUrl = "https://testapp.hospitalitaliano.org.ar/masterfile-federacion-service/fhir";
            var baseUrl = _integrationServicesConfiguration.Services
                          .First(x => x.ServiceName == "BUS").BaseURL;

            var client = new FhirClient(baseUrl);

            client.PreferredFormat  = ResourceFormat.Json;
            client.OnBeforeRequest += (object sender, BeforeRequestEventArgs e) =>
            {
                var requestAdderss = e.RawRequest.Address.ToString();

                activity.RequestIsURL      = true;
                activity.ActivityRequestUI = requestAdderss;

                _logger.LogInformation($"Send Request Address:{requestAdderss}");
            };

            client.OnAfterResponse += (object sender, AfterResponseEventArgs e) =>
            {
                if (e.Body != null)
                {
                    var responseBody = Encoding.UTF8.GetString(e.Body, 0, e.Body.Length);

                    //Prettify !!!
                    responseBody = JToken.Parse(responseBody).ToString();

                    activity.ResponseIsJson       = true;
                    activity.ActivityResponse     = $"Status: {e.RawResponse.StatusCode}";
                    activity.ActivityResponseBody = responseBody;

                    _logger.LogInformation($"Received response with status: {e.RawResponse.StatusCode}");
                    _logger.LogInformation($"Received response with body: {responseBody}");
                }
            };


            Hl7.Fhir.Model.Bundle ret = null;

            try
            {
                var qp = new SearchParams();
                qp.Add("identifier", $"{system}|{value}");
                ret = client.Search <Hl7.Fhir.Model.Patient>(qp);
                _currentContext.RegisterActivityLog(activity);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.ToString());
                throw ex;
            }

            return(ret);
        }
Exemplo n.º 8
0
        /// <summary>
        /// Check for outgoing messages
        /// If new appts are found, they will be added, missing ones will be removed
        /// (not a flush and add in again - as this will lose data)
        /// </summary>
        /// <param name="model"></param>
        public static async System.Threading.Tasks.Task CheckAppointments(ArrivalsModel model)
        {
            var oldexptecting = model.Expecting.ToList();
            var oldwaiting    = model.Waiting.ToList();

            var server   = GetServerConnection();
            var criteria = new SearchParams();

            criteria.Add("date", "2020-03-19"); // TODO: Change this to today's date
            criteria.Include.Add("Appointment:actor");
            var bundle = await server.SearchAsync <Appointment>(criteria);

            // Debugging
            var doc = System.Xml.Linq.XDocument.Parse(new Hl7.Fhir.Serialization.FhirXmlSerializer().SerializeToString(bundle));

            Console.WriteLine(doc.ToString(System.Xml.Linq.SaveOptions.None));

            Func <ResourceReference, System.Threading.Tasks.Task <Resource> > resolveReference = async(reference) =>
            {
                if (string.IsNullOrEmpty(reference.Reference))
                {
                    return(null);
                }
                ResourceIdentity ri = new ResourceIdentity(reference.Reference);
                var resource        = bundle.Entry.FirstOrDefault(e => e.Resource.ResourceType.GetLiteral() == ri.ResourceType && e.Resource.Id == ri.Id)?.Resource;
                if (resource == null)
                {
                    // wasn't returned in the bundle, so go searching for it
                    resource = await server.ReadAsync <Resource>(reference.Reference);
                }
                return(resource);
            };

            foreach (var entry in bundle.Entry.Select(e => e.Resource as Appointment).Where(e => e != null))
            {
                if (entry.Status == Appointment.AppointmentStatus.Booked)
                {
                    PmsAppointment app = await ToPmsAppointment(entry, resolveReference);

                    if (app != null && !model.Expecting.Contains(app))
                    {
                        model.Expecting.Add(app);
                    }
                }
                if (entry.Status == Appointment.AppointmentStatus.Arrived)
                {
                    PmsAppointment app = await ToPmsAppointment(entry, resolveReference);

                    if (app != null && !model.Waiting.Contains(app))
                    {
                        model.Waiting.Add(app);
                    }
                }
            }

            // finished processing all the items, so remove any that are left
            foreach (var item in oldexptecting)
            {
                model.Expecting.Remove(item);
            }
            foreach (var item in oldwaiting)
            {
                model.Waiting.Remove(item);
            }
        }
        public SearchResourceViewModel GetResourceDetails(string patientId, string resouceInfo)
        {
            this.patientId   = patientId;
            this.resouceInfo = resouceInfo;
            SearchResourceViewModel vm = new SearchResourceViewModel();

            try
            {
                switch (resouceInfo)
                {
                //Patient Details
                case "PatientInfo":

                    //Creating the endpoint url with patient patient parameter
                    var ResourceUrl = ("/Patient/" + patientId);

                    //Reading the Patient details using the FHIR Client
                    var patientDetailsRead = FhirClient.Read <Patient>(ResourceUrl);

                    //Serializing the data to json string
                    string patientDetails = fhirJsonSerializer.SerializeToString(patientDetailsRead);

                    //deserializing the json string to patient model
                    vm.patient = (Patient)fhirJsonParser.Parse(patientDetails, typeof(Patient));

                    //Displaying the Div element of the text section
                    vm.operationOutcomeError = vm.patient.Text == null ? string.Empty : vm.patient.Text.Div;

                    //Displaying the RAW json data in the view
                    vm.ResourceRawJsonData = JValue.Parse(patientDetails).ToString();
                    break;

                //Patient Encounter Details
                case "EncounterInfo":

                    //Adding the Parameter to the URL to get the Encounter related to specific patient using the SearchParams
                    parms.Add("patient", patientId);

                    //Searching the Encounter for the patient
                    var encounterDetailsRead = FhirClient.Search <Encounter>(parms);

                    //Serializing the data to json string
                    string encounterDetails = fhirJsonSerializer.SerializeToString(encounterDetailsRead);

                    //Parsing the json string
                    Bundle encounters = (Bundle)fhirJsonParser.Parse(encounterDetails, typeof(Bundle));

                    //Parsing the data to the encounter model
                    vm.encounters = encounters.Entry.Select(Resource => (Encounter)Resource.Resource).ToList();

                    //Displaying the Div element of the text section
                    foreach (var enc in vm.encounters)
                    {
                        vm.operationOutcomeError = enc.Text == null ? string.Empty : enc.Text.Div;
                    }

                    //Displaying the RAW json data in the view
                    vm.ResourceRawJsonData = JValue.Parse(encounterDetails).ToString();
                    break;

                //Patient Medication Details
                case "MedicationRequest":

                    //Adding the Parameter to the URL to get the Medication related to specific patient using the SearchParams
                    var Status = "active";
                    parms.Add("patient", patientId);
                    parms.Add("status", Status);

                    //Searching the Medication Request for the patient
                    var medicationDetailsRead = FhirClient.Search <MedicationRequest>(parms);

                    //Serializing the data to json string
                    string medicationDetails = fhirJsonSerializer.SerializeToString(medicationDetailsRead);

                    //Parsing the json string
                    Bundle medications = (Bundle)fhirJsonParser.Parse(medicationDetails, typeof(Bundle));

                    //Parsing the data to the medication model
                    vm.medicationRequest = medications.Entry.Select(Resource => (MedicationRequest)Resource.Resource).ToList();

                    //Displaying the Div element of the text section
                    foreach (var medreq in vm.medicationRequest)
                    {
                        vm.operationOutcomeError = medreq.Text == null ? string.Empty : medreq.Text.Div;
                    }

                    //Displaying the RAW json data in the view
                    vm.ResourceRawJsonData = JValue.Parse(medicationDetails).ToString();
                    break;

                //Patient condition Details
                case "ConditionInfo":

                    //Adding the Parameter to the URL to get the Condition related to specific patient using the SearchParams
                    parms.Add("patient", patientId);

                    //Searching the Conditons for the patient
                    var conditionDetailsRead = FhirClient.Search <Condition>(parms);

                    //Serializing the data to json string
                    string conditionDetails = fhirJsonSerializer.SerializeToString(conditionDetailsRead);

                    //Parsing the json string
                    Bundle conditions = (Bundle)fhirJsonParser.Parse(conditionDetails, typeof(Bundle));

                    //Parsing the data to the condition model
                    vm.conditions = conditions.Entry.Select(Resource => (Condition)Resource.Resource).ToList();

                    //Displaying the Div element of the text section
                    foreach (var con in vm.conditions)
                    {
                        vm.operationOutcomeError = con.Text == null ? string.Empty : con.Text.Div;
                    }

                    //Displaying the RAW json data in the view
                    vm.ResourceRawJsonData = JValue.Parse(conditionDetails).ToString();
                    break;

                //Patient observation Details
                case "ObservationInfo":

                    //Adding the Parameter to the URL to get the Observation related to specific patient using the SearchParams
                    parms.Add("patient", patientId);

                    //Searching the Observation Request for the patient
                    var observationDetailsRead = FhirClient.Search <Observation>(parms);

                    //Serializing the data to json string
                    string observationDetails = fhirJsonSerializer.SerializeToString(observationDetailsRead);

                    //Parsing the json string
                    Bundle observations = (Bundle)fhirJsonParser.Parse(observationDetails, typeof(Bundle));

                    //Parsing the data to the Observation model
                    vm.observations = observations.Entry.Select(Resource => (Observation)Resource.Resource).ToList();

                    //Displaying the Div element of the text section
                    foreach (var obs in vm.observations)
                    {
                        vm.operationOutcomeError = obs.Text == null ? string.Empty : obs.Text.Div;
                    }

                    //Displaying the RAW json data in the view
                    vm.ResourceRawJsonData = JValue.Parse(observationDetails).ToString();
                    break;
                }
            }

            catch (FhirOperationException FhirOpExec)
            {
                var response           = FhirOpExec.Outcome;
                var errorDetails       = fhirJsonSerializer.SerializeToString(response);
                OperationOutcome error = (OperationOutcome)fhirJsonParser.Parse(errorDetails, typeof(OperationOutcome));
                vm.operationOutcomeError = error.Text == null ? string.Empty : error.Text.Div;
                vm.ResourceRawJsonData   = JValue.Parse(errorDetails).ToString();
            }
            catch (WebException ex)
            {
                var response = new StreamReader(ex.Response.GetResponseStream()).ReadToEnd();
                var error    = JsonConvert.DeserializeObject(response);
                vm.ResourceRawJsonData = error.ToString();
            }

            return(vm);
        }
        public System.Threading.Tasks.Task <Bundle> Search(IEnumerable <KeyValuePair <string, string> > parameters, int?Count, SummaryType summary)
        {
            Bundle result = new Bundle();

            result.Meta         = new Meta();
            result.Id           = new Uri("urn:uuid:" + Guid.NewGuid().ToString("n")).OriginalString;
            result.Type         = Bundle.BundleType.Searchset;
            result.ResourceBase = RequestDetails.BaseUri;
            result.Total        = 0;

            // If there was no count provided, we'll just default in a value
            if (!Count.HasValue)
            {
                Count = 40;
            }

            // TODO: Thread the requests...
            // System.Threading.Tasks.Parallel.ForEach(_members, async (member) =>
            foreach (var member in _members)
            {
                try
                {
                    // create a connection with the supported format type
                    FhirClient server = new FhirClient(member.Url);
                    member.PrepareFhirClientSecurity(server);
                    System.Diagnostics.Trace.WriteLine($"Searching {member.Url} {member.Name}");
                    server.PreferCompressedResponses = true;
                    server.PreferredFormat           = member.Format;

                    SearchParams sp = new SearchParams();
                    foreach (var item in parameters)
                    {
                        if (item.Key == "_include")
                        {
                            sp.Include.Add(item.Value);
                        }
                        else
                        {
                            sp.Add(item.Key, item.Value);
                        }
                    }
                    sp.Count   = Count;
                    sp.Summary = summary;
                    // Bundle partialResult = server.Search(sp, ResourceName);
                    Bundle partialResult = server.Search(sp, ResourceName);
                    lock (result)
                    {
                        if (partialResult.Total.HasValue)
                        {
                            result.Total += partialResult.Total;
                        }
                        foreach (var entry in partialResult.Entry)
                        {
                            result.Entry.Add(entry);
                            entry.Resource.ResourceBase = server.Endpoint;
                            if (entry.Resource.Meta == null)
                            {
                                entry.Resource.Meta = new Meta();
                            }
                            if (!string.IsNullOrEmpty(entry.Resource.Id))
                            {
                                entry.Resource.Meta.AddExtension("http://hl7.org/fhir/StructureDefinition/extension-Meta.source|3.2", new FhirUri(entry.Resource.ResourceIdentity(entry.Resource.ResourceBase).OriginalString));
                            }
                            var prov = member.CreateProvenance();
                            member.WithProvenance(prov, entry.Resource, entry.FullUrl);
                            result.Entry.Add(new Bundle.EntryComponent()
                            {
                                FullUrl = $"urn-uuid{Guid.NewGuid().ToString("D")}",
                                Search  = new Bundle.SearchComponent()
                                {
                                    Mode = Bundle.SearchEntryMode.Include
                                },
                                Resource = prov
                            });
                        }
                        OperationOutcome oe = new OperationOutcome();
                        oe.Issue.Add(new OperationOutcome.IssueComponent()
                        {
                            Severity    = OperationOutcome.IssueSeverity.Information,
                            Code        = OperationOutcome.IssueType.Informational,
                            Details     = new CodeableConcept(null, null, $"Searching {member.Name} found {partialResult.Total} results"),
                            Diagnostics = partialResult.SelfLink?.OriginalString
                        });
                        result.Entry.Add(new Bundle.EntryComponent()
                        {
                            Search = new Bundle.SearchComponent()
                            {
                                Mode = Bundle.SearchEntryMode.Outcome
                            },
                            Resource = oe
                        });
                    }
                }
                catch (FhirOperationException ex)
                {
                    if (ex.Outcome != null)
                    {
                        ex.Outcome.Issue.Insert(0, new OperationOutcome.IssueComponent()
                        {
                            Severity    = OperationOutcome.IssueSeverity.Information,
                            Code        = OperationOutcome.IssueType.Exception,
                            Details     = new CodeableConcept(null, null, $"Exception Searching {member.Name}"),
                            Diagnostics = member.Url
                        });
                        result.Entry.Add(new Bundle.EntryComponent()
                        {
                            Search = new Bundle.SearchComponent()
                            {
                                Mode = Bundle.SearchEntryMode.Outcome
                            },
                            Resource = ex.Outcome
                        });
                    }
                }
                catch (Exception ex)
                {
                    // some other weirdness went on
                    OperationOutcome oe = new OperationOutcome();
                    oe.Issue.Add(new OperationOutcome.IssueComponent()
                    {
                        Severity    = OperationOutcome.IssueSeverity.Information,
                        Code        = OperationOutcome.IssueType.Exception,
                        Details     = new CodeableConcept(null, null, $"Exception Searching {member.Name}"),
                        Diagnostics = member.Url
                    });
                    oe.Issue.Add(new OperationOutcome.IssueComponent()
                    {
                        Severity    = OperationOutcome.IssueSeverity.Error,
                        Code        = OperationOutcome.IssueType.Exception,
                        Diagnostics = ex.Message
                    });
                    result.Entry.Add(new Bundle.EntryComponent()
                    {
                        Search = new Bundle.SearchComponent()
                        {
                            Mode = Bundle.SearchEntryMode.Outcome
                        },
                        Resource = oe
                    });
                }
            }

            // TODO:Merge Sort the results?

            // TODO:Mess with the back/next links

            return(System.Threading.Tasks.Task.FromResult(result));
        }