private IEnumerable <SearchParameter> BuildSearchParameterDefinition(
            ILookup <string, SearchParameter> searchParametersLookup,
            string resourceType)
        {
            if (_resourceTypeDictionary.TryGetValue(resourceType, out IDictionary <string, SearchParameter> cachedSearchParameters))
            {
                return(cachedSearchParameters.Values);
            }

            IEnumerable <SearchParameter> results = Enumerable.Empty <SearchParameter>();

            Type type = ModelInfoProvider.GetTypeForFhirType(resourceType);

            Debug.Assert(type != null, "The type should not be null.");

            string baseType = ModelInfo.GetFhirTypeNameForType(type.BaseType);

            if (baseType != null && Enum.TryParse(baseType, out ResourceType baseResourceType))
            {
                results = BuildSearchParameterDefinition(searchParametersLookup, baseType);
            }

            Debug.Assert(results != null, "The results should not be null.");

            results = results.Concat(searchParametersLookup[resourceType]);

            Dictionary <string, SearchParameter> searchParameterDictionary = results.ToDictionary(
                r => r.Name,
                r => r,
                StringComparer.Ordinal);

            _resourceTypeDictionary.Add(resourceType, searchParameterDictionary);

            return(searchParameterDictionary.Values);
        }
Exemple #2
0
        public void Build(IListedCapabilityStatement statement)
        {
            EnsureArg.IsNotNull(statement, nameof(statement));

            statement.SupportsInclude = true;

            foreach (var resource in ModelInfoProvider.GetResourceTypeNames())
            {
                statement.BuildRestResourceComponent(resource, builder =>
                {
                    builder.AddResourceVersionPolicy(ResourceVersionPolicy.NoVersion);
                    builder.AddResourceVersionPolicy(ResourceVersionPolicy.Versioned);
                    builder.AddResourceVersionPolicy(ResourceVersionPolicy.VersionedUpdate);
                    builder.ReadHistory  = true;
                    builder.UpdateCreate = true;
                });
            }

            if (_coreFeatures.SupportsBatch)
            {
                // Batch supported added in listedCapability
                statement.TryAddRestInteraction(SystemRestfulInteraction.Batch);
            }

            if (_coreFeatures.SupportsTransaction)
            {
                // Transaction supported added in listedCapability
                statement.TryAddRestInteraction(SystemRestfulInteraction.Transaction);
            }
        }
Exemple #3
0
        /// <summary>
        /// This method provides temporary compatibility while STU3/R4 compatibility is added
        /// </summary>
        public static void SetModelInfoProvider()
        {
#if Stu3
            ModelInfoProvider.SetProvider(new Stu3ModelInfoProvider());
#elif R4
            ModelInfoProvider.SetProvider(new R4ModelInfoProvider());
#endif
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="CompartmentSearchExpression"/> class.
        /// </summary>
        /// <param name="compartmentType">The compartment type.</param>
        /// <param name="compartmentId">The compartment id.</param>
        public CompartmentSearchExpression(string compartmentType, string compartmentId)
        {
            EnsureArg.IsTrue(ModelInfoProvider.IsKnownCompartmentType(compartmentType), nameof(compartmentType));
            EnsureArg.IsNotNullOrWhiteSpace(compartmentId, nameof(compartmentId));

            CompartmentType = compartmentType;
            CompartmentId   = compartmentId;
        }
        public void GivenAValidSearchParameterDefinitionFile_WhenBuilt_ThenAllResourceTypesShouldBeIncluded()
        {
            _builderWithValidEntries.Build();

            Assert.Equal(
                ModelInfoProvider.GetResourceTypeNames().OrderBy(x => x),
                _builderWithValidEntries.ResourceTypeDictionary.Select(x => x.Key).OrderBy(x => x).ToArray());
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="CompartmentSearchExpression"/> class.
        /// </summary>
        /// <param name="compartmentType">The compartment type.</param>
        /// <param name="compartmentId">The compartment id.</param>
        /// <param name="filteredResourceTypes">Resource types to filter</param>
        public CompartmentSearchExpression(string compartmentType, string compartmentId, params string[] filteredResourceTypes)
        {
            EnsureArg.IsTrue(ModelInfoProvider.IsKnownCompartmentType(compartmentType), nameof(compartmentType));
            EnsureArg.IsNotNullOrWhiteSpace(compartmentId, nameof(compartmentId));

            CompartmentType       = compartmentType;
            CompartmentId         = compartmentId;
            FilteredResourceTypes = filteredResourceTypes ?? Array.Empty <string>();
        }
Exemple #7
0
        public ResourceKey(string resourceType, string id, string versionId = null)
        {
            EnsureArg.IsNotNullOrEmpty(resourceType, nameof(resourceType));
            EnsureArg.IsNotNullOrEmpty(id, nameof(id));
            EnsureArg.IsTrue(ModelInfoProvider.IsKnownResource(resourceType), nameof(resourceType));

            Id           = id;
            VersionId    = versionId;
            ResourceType = resourceType;
        }
Exemple #8
0
        public void GivenAValidSearchParameterDefinitionFile_WhenBuilt_ThenAllResourceTypesShouldBeIncluded()
        {
            var bundle = SearchParameterDefinitionBuilder.ReadEmbeddedSearchParameters(
                _validEntriesFile,
                ModelInfoProvider.Instance,
                $"{typeof(Definitions).Namespace}.DefinitionFiles",
                typeof(EmbeddedResourceManager).Assembly);

            SearchParameterDefinitionBuilder.Build(bundle.Entries.Select(e => e.Resource).ToList(), _uriDictionary, _resourceTypeDictionary, ModelInfoProvider.Instance);

            Assert.Equal(
                ModelInfoProvider.GetResourceTypeNames().Concat(new[] { "Resource", "DomainResource" }).OrderBy(x => x).ToArray(),
                _resourceTypeDictionary.Select(x => x.Key).OrderBy(x => x).ToArray());
        }
        public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
        {
            EnsureArg.IsNotNull(httpContext, nameof(httpContext));
            EnsureArg.IsNotNull(route, nameof(route));
            EnsureArg.IsNotNullOrEmpty(routeKey, nameof(routeKey));
            EnsureArg.IsNotNull(values, nameof(values));

            if (values.TryGetValue(KnownActionParameterNames.ResourceType, out var resourceTypeObj) && resourceTypeObj is string resourceType && !string.IsNullOrEmpty(resourceType))
            {
                return(ModelInfoProvider.IsKnownResource(resourceType));
            }

            return(false);
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="ReferenceSearchValue"/> class.
        /// </summary>
        /// <param name="referenceKind">The kind of reference.</param>
        /// <param name="baseUri">The base URI of the resource.</param>
        /// <param name="resourceType">The resource type.</param>
        /// <param name="resourceId">The resource id.</param>
        public ReferenceSearchValue(ReferenceKind referenceKind, Uri baseUri, string resourceType, string resourceId)
        {
            if (baseUri != null)
            {
                EnsureArg.IsNotNullOrWhiteSpace(resourceType, nameof(resourceType));
            }

            EnsureArg.IsNotNullOrWhiteSpace(resourceId, nameof(resourceId));
            ModelInfoProvider.EnsureValidResourceType(resourceType, nameof(resourceType));

            Kind         = referenceKind;
            BaseUri      = baseUri;
            ResourceType = resourceType;
            ResourceId   = resourceId;
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="ChainedExpression"/> class.
        /// </summary>
        /// <param name="resourceType">The resource type that supports this search expression.</param>
        /// <param name="paramName">The search parameter name.</param>
        /// <param name="targetResourceType">The target resource type.</param>
        /// <param name="expression">The search expression.</param>
        public ChainedExpression(
            string resourceType,
            string paramName,
            string targetResourceType,
            Expression expression)
        {
            EnsureArg.IsTrue(ModelInfoProvider.IsKnownResource(resourceType), nameof(resourceType));
            EnsureArg.IsNotNullOrWhiteSpace(paramName, nameof(paramName));
            EnsureArg.IsTrue(ModelInfoProvider.IsKnownResource(targetResourceType), nameof(targetResourceType));
            EnsureArg.IsNotNull(expression, nameof(expression));

            ResourceType       = resourceType;
            ParamName          = paramName;
            TargetResourceType = targetResourceType;
            Expression         = expression;
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="ChainedExpression"/> class.
        /// </summary>
        /// <param name="resourceType">The resource type that supports this search expression.</param>
        /// <param name="referenceSearchParameter">The search parameter that establishes the reference</param>
        /// <param name="targetResourceType">The target resource type.</param>
        /// <param name="expression">The search expression.</param>
        public ChainedExpression(
            string resourceType,
            SearchParameterInfo referenceSearchParameter,
            string targetResourceType,
            Expression expression)
        {
            EnsureArg.IsTrue(ModelInfoProvider.IsKnownResource(resourceType), nameof(resourceType));
            EnsureArg.IsNotNull(referenceSearchParameter, nameof(referenceSearchParameter));
            EnsureArg.IsTrue(ModelInfoProvider.IsKnownResource(targetResourceType), nameof(targetResourceType));
            EnsureArg.IsNotNull(expression, nameof(expression));

            ResourceType             = resourceType;
            ReferenceSearchParameter = referenceSearchParameter;
            TargetResourceType       = targetResourceType;
            Expression = expression;
        }
        public void Build(IListedCapabilityStatement statement)
        {
            EnsureArg.IsNotNull(statement, nameof(statement));

            foreach (var resource in ModelInfoProvider.GetResourceTypeNames())
            {
                statement.BuildRestResourceComponent(resource, builder =>
                {
                    builder.AddResourceVersionPolicy(ResourceVersionPolicy.NoVersion);
                    builder.AddResourceVersionPolicy(ResourceVersionPolicy.Versioned);
                    builder.AddResourceVersionPolicy(ResourceVersionPolicy.VersionedUpdate);
                    builder.ReadHistory  = true;
                    builder.UpdateCreate = true;
                });
            }
        }
Exemple #14
0
        /// <summary>
        /// Initializes a new instance of the <see cref="ChainedExpression"/> class.
        /// </summary>
        /// <param name="resourceTypes">The resource type that supports this search expression.</param>
        /// <param name="referenceSearchParameter">The search parameter that establishes the reference</param>
        /// <param name="targetResourceTypes">The target resource type.</param>
        /// <param name="reversed">If this is a reversed chained expression.</param>
        /// <param name="expression">The search expression.</param>
        public ChainedExpression(
            string[] resourceTypes,
            SearchParameterInfo referenceSearchParameter,
            string[] targetResourceTypes,
            bool reversed,
            Expression expression)
        {
            EnsureArg.IsNotNull(resourceTypes, nameof(resourceTypes));
            EnsureArg.IsTrue(resourceTypes.All(x => ModelInfoProvider.IsKnownResource(x)), nameof(resourceTypes));
            EnsureArg.IsNotNull(referenceSearchParameter, nameof(referenceSearchParameter));
            EnsureArg.IsNotNull(targetResourceTypes, nameof(targetResourceTypes));
            EnsureArg.IsTrue(targetResourceTypes.All(x => ModelInfoProvider.IsKnownResource(x)), nameof(targetResourceTypes));
            EnsureArg.IsNotNull(expression, nameof(expression));

            ResourceTypes            = resourceTypes;
            ReferenceSearchParameter = referenceSearchParameter;
            TargetResourceTypes      = targetResourceTypes;
            Reversed   = reversed;
            Expression = expression;
        }
 public static Type ToResourceModelType(this ResourceElement resourceType)
 {
     return(ModelInfoProvider.GetTypeForFhirType(resourceType.InstanceType));
 }
Exemple #16
0
 public static Type ToResourceModelType(this ResourceElement resourceType)
 {
     EnsureArg.IsNotNull(resourceType, nameof(resourceType));
     return(ModelInfoProvider.GetTypeForFhirType(resourceType.InstanceType));
 }
Exemple #17
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));

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

            if (searchParams.RevInclude?.Count > 0)
            {
                searchExpressions.AddRange(searchParams.RevInclude.Select(
                                               q => _expressionParser.ParseInclude(resourceTypesString, 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())
            {
                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 (Tuple <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(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(new[] { 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.RequestContext?.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));
                }
            }
        }
        private async Task Initialize()
        {
            if (!_schemaInformation.Current.HasValue)
            {
                _logger.LogError($"The current version of the database is not available. Unable in initialize {nameof(SqlServerFhirModel)}.");
                throw new ServiceUnavailableException();
            }

            var connectionStringBuilder = new SqlConnectionStringBuilder(_configuration.ConnectionString);

            _logger.LogInformation("Initializing {Server} {Database}", connectionStringBuilder.DataSource, connectionStringBuilder.InitialCatalog);

            using (var connection = new SqlConnection(_configuration.ConnectionString))
            {
                await connection.OpenAsync();

                using (SqlCommand sqlCommand = connection.CreateCommand())
                {
                    sqlCommand.CommandText = @"
                        SET XACT_ABORT ON
                        BEGIN TRANSACTION

                        INSERT INTO dbo.ResourceType (Name) 
                        SELECT value FROM string_split(@resourceTypes, ',')
                        EXCEPT SELECT Name FROM dbo.ResourceType WITH (TABLOCKX); 

                        -- result set 1
                        SELECT ResourceTypeId, Name FROM dbo.ResourceType;

                        INSERT INTO dbo.SearchParam (Uri)
                        SELECT * FROM  OPENJSON (@searchParams) 
                        WITH (Uri varchar(128) '$.Uri')
                        EXCEPT SELECT Uri FROM dbo.SearchParam;

                        -- result set 2
                        SELECT Uri, SearchParamId FROM dbo.SearchParam;

                        INSERT INTO dbo.ClaimType (Name) 
                        SELECT value FROM string_split(@claimTypes, ',')
                        EXCEPT SELECT Name FROM dbo.ClaimType; 

                        -- result set 3
                        SELECT ClaimTypeId, Name FROM dbo.ClaimType;

                        INSERT INTO dbo.CompartmentType (Name) 
                        SELECT value FROM string_split(@compartmentTypes, ',')
                        EXCEPT SELECT Name FROM dbo.CompartmentType; 

                        -- result set 4
                        SELECT CompartmentTypeId, Name FROM dbo.CompartmentType;
                        
                        COMMIT TRANSACTION
    
                        -- result set 5
                        SELECT Value, SystemId from dbo.System;

                        -- result set 6
                        SELECT Value, QuantityCodeId FROM dbo.QuantityCode";

                    string commaSeparatedResourceTypes    = string.Join(",", ModelInfoProvider.GetResourceTypeNames());
                    string searchParametersJson           = JsonConvert.SerializeObject(_searchParameterDefinitionManager.AllSearchParameters.Select(p => new { Name = p.Name, Uri = p.Url }));
                    string commaSeparatedClaimTypes       = string.Join(',', _securityConfiguration.PrincipalClaims);
                    string commaSeparatedCompartmentTypes = string.Join(',', ModelInfoProvider.GetCompartmentTypeNames());

                    sqlCommand.Parameters.AddWithValue("@resourceTypes", commaSeparatedResourceTypes);
                    sqlCommand.Parameters.AddWithValue("@searchParams", searchParametersJson);
                    sqlCommand.Parameters.AddWithValue("@claimTypes", commaSeparatedClaimTypes);
                    sqlCommand.Parameters.AddWithValue("@compartmentTypes", commaSeparatedCompartmentTypes);

                    using (SqlDataReader reader = await sqlCommand.ExecuteReaderAsync(CommandBehavior.SequentialAccess))
                    {
                        var resourceTypeToId         = new Dictionary <string, short>(StringComparer.Ordinal);
                        var resourceTypeIdToTypeName = new Dictionary <short, string>();
                        var searchParamUriToId       = new Dictionary <Uri, short>();
                        var systemToId          = new ConcurrentDictionary <string, int>(StringComparer.OrdinalIgnoreCase);
                        var quantityCodeToId    = new ConcurrentDictionary <string, int>(StringComparer.OrdinalIgnoreCase);
                        var claimNameToId       = new Dictionary <string, byte>(StringComparer.Ordinal);
                        var compartmentTypeToId = new Dictionary <string, byte>();

                        // result set 1
                        while (reader.Read())
                        {
                            (short id, string resourceTypeName) = reader.ReadRow(V1.ResourceType.ResourceTypeId, V1.ResourceType.Name);

                            resourceTypeToId.Add(resourceTypeName, id);
                            resourceTypeIdToTypeName.Add(id, resourceTypeName);
                        }

                        // result set 2
                        reader.NextResult();

                        while (reader.Read())
                        {
                            (string uri, short searchParamId) = reader.ReadRow(V1.SearchParam.Uri, V1.SearchParam.SearchParamId);
                            searchParamUriToId.Add(new Uri(uri), searchParamId);
                        }

                        // result set 3
                        reader.NextResult();

                        while (reader.Read())
                        {
                            (byte id, string claimTypeName) = reader.ReadRow(V1.ClaimType.ClaimTypeId, V1.ClaimType.Name);
                            claimNameToId.Add(claimTypeName, id);
                        }

                        // result set 4
                        reader.NextResult();

                        while (reader.Read())
                        {
                            (byte id, string compartmentName) = reader.ReadRow(V1.CompartmentType.CompartmentTypeId, V1.CompartmentType.Name);
                            compartmentTypeToId.Add(compartmentName, id);
                        }

                        // result set 5
                        reader.NextResult();

                        while (reader.Read())
                        {
                            var(value, systemId) = reader.ReadRow(V1.System.Value, V1.System.SystemId);
                            systemToId.TryAdd(value, systemId);
                        }

                        // result set 6
                        reader.NextResult();

                        while (reader.Read())
                        {
                            (string value, int quantityCodeId) = reader.ReadRow(V1.QuantityCode.Value, V1.QuantityCode.QuantityCodeId);
                            quantityCodeToId.TryAdd(value, quantityCodeId);
                        }

                        _resourceTypeToId         = resourceTypeToId;
                        _resourceTypeIdToTypeName = resourceTypeIdToTypeName;
                        _searchParamUriToId       = searchParamUriToId;
                        _systemToId          = systemToId;
                        _quantityCodeToId    = quantityCodeToId;
                        _claimNameToId       = claimNameToId;
                        _compartmentTypeToId = compartmentTypeToId;
                    }
                }
            }
        }
        /// <inheritdoc />
        public ReferenceSearchValue Parse(string s)
        {
            EnsureArg.IsNotNullOrWhiteSpace(s, nameof(s));

            Match match = ReferenceRegex.Match(s);

            if (match.Success)
            {
                string resourceTypeInString = match.Groups[ResourceTypeCapture].Value;

                ModelInfoProvider.EnsureValidResourceType(resourceTypeInString, nameof(s));

                string resourceId = match.Groups[ResourceIdCapture].Value;

                int resourceTypeStartIndex = match.Groups[ResourceTypeCapture].Index;

                if (resourceTypeStartIndex == 0)
                {
                    // This is relative URL.
                    return(new ReferenceSearchValue(
                               ReferenceKind.InternalOrExternal,
                               null,
                               resourceTypeInString,
                               resourceId));
                }

                Uri baseUri = null;

                try
                {
                    baseUri = new Uri(s.Substring(0, resourceTypeStartIndex), UriKind.RelativeOrAbsolute);

                    if (baseUri == _fhirRequestContextAccessor.FhirRequestContext.BaseUri)
                    {
                        // This is an absolute URL pointing to an internal resource.
                        return(new ReferenceSearchValue(
                                   ReferenceKind.Internal,
                                   null,
                                   resourceTypeInString,
                                   resourceId));
                    }
                    else if (baseUri.IsAbsoluteUri &&
                             SupportedSchemes.Contains(baseUri.Scheme, StringComparer.OrdinalIgnoreCase))
                    {
                        // This is an absolute URL pointing to an external resource.
                        return(new ReferenceSearchValue(
                                   ReferenceKind.External,
                                   baseUri,
                                   resourceTypeInString,
                                   resourceId));
                    }
                }
                catch (UriFormatException)
                {
                    // The reference is not a relative reference but is not a valid absolute reference either.
                }
            }

            return(new ReferenceSearchValue(
                       ReferenceKind.InternalOrExternal,
                       baseUri: null,
                       resourceType: null,
                       resourceId: s));
        }
Exemple #20
0
        public ReindexJobRecord(
            IReadOnlyDictionary <string, string> searchParametersHash,
            IReadOnlyCollection <string> targetResourceTypes,
            ushort maxiumumConcurrency            = 1,
            uint maxResourcesPerQuery             = 100,
            int queryDelayIntervalInMilliseconds  = 500,
            ushort?targetDataStoreUsagePercentage = null)
        {
            EnsureArg.IsNotNull(searchParametersHash, nameof(searchParametersHash));
            EnsureArg.IsNotNull(targetResourceTypes, nameof(targetResourceTypes));

            // Default values
            SchemaVersion = 1;
            Id            = Guid.NewGuid().ToString();
            Status        = OperationStatus.Queued;

            QueuedTime   = Clock.UtcNow;
            LastModified = Clock.UtcNow;

            ResourceTypeSearchParameterHashMap = searchParametersHash;

            // check for MaximumConcurrency boundary
            if (maxiumumConcurrency < MinMaximumConcurrency || maxiumumConcurrency > MaxMaximumConcurrency)
            {
                throw new BadRequestException(string.Format(Fhir.Core.Resources.InvalidReIndexParameterValue, nameof(MaximumConcurrency), MinMaximumConcurrency.ToString(), MaxMaximumConcurrency.ToString()));
            }
            else
            {
                MaximumConcurrency = maxiumumConcurrency;
            }

            // check for MaximumNumberOfResourcesPerQuery boundary
            if (maxResourcesPerQuery < MinMaximumNumberOfResourcesPerQuery || maxResourcesPerQuery > MaxMaximumNumberOfResourcesPerQuery)
            {
                throw new BadRequestException(string.Format(Fhir.Core.Resources.InvalidReIndexParameterValue, nameof(MaximumNumberOfResourcesPerQuery), MinMaximumNumberOfResourcesPerQuery.ToString(), MaxMaximumNumberOfResourcesPerQuery.ToString()));
            }
            else
            {
                MaximumNumberOfResourcesPerQuery = maxResourcesPerQuery;
            }

            // check for QueryDelayIntervalInMilliseconds boundary
            if (queryDelayIntervalInMilliseconds < MinQueryDelayIntervalInMilliseconds || queryDelayIntervalInMilliseconds > MaxQueryDelayIntervalInMilliseconds)
            {
                throw new BadRequestException(string.Format(Fhir.Core.Resources.InvalidReIndexParameterValue, nameof(QueryDelayIntervalInMilliseconds), MinQueryDelayIntervalInMilliseconds.ToString(), MaxQueryDelayIntervalInMilliseconds.ToString()));
            }
            else
            {
                QueryDelayIntervalInMilliseconds = queryDelayIntervalInMilliseconds;
            }

            // check for TargetDataStoreUsagePercentage boundary
            if (targetDataStoreUsagePercentage < MinTargetDataStoreUsagePercentage || targetDataStoreUsagePercentage > MaxTargetDataStoreUsagePercentage)
            {
                throw new BadRequestException(string.Format(Fhir.Core.Resources.InvalidReIndexParameterValue, nameof(TargetDataStoreUsagePercentage), MinTargetDataStoreUsagePercentage.ToString(), MaxTargetDataStoreUsagePercentage.ToString()));
            }
            else
            {
                TargetDataStoreUsagePercentage = targetDataStoreUsagePercentage;
            }

            // check for TargetResourceTypes boundary
            foreach (var type in targetResourceTypes)
            {
                ModelInfoProvider.EnsureValidResourceType(type, nameof(type));
            }

            TargetResourceTypes = targetResourceTypes;
        }
        public void Start()
        {
            _schemaInitializer.Start();

            if (_searchParameterDefinitionManager is IStartable startable)
            {
                startable.Start();
            }

            var connectionStringBuilder = new SqlConnectionStringBuilder(_configuration.ConnectionString);

            _logger.LogInformation("Initializing {Server} {Database}", connectionStringBuilder.DataSource, connectionStringBuilder.InitialCatalog);

            using (var connection = new SqlConnection(_configuration.ConnectionString))
            {
                connection.Open();

                // Synchronous calls are used because this code is executed on startup and doesn't need to be async.
                // Additionally, XUnit task scheduler constraints prevent async calls from being easily tested.
                using (SqlCommand sqlCommand = connection.CreateCommand())
                {
                    sqlCommand.CommandText = @"
                        SET XACT_ABORT ON
                        BEGIN TRANSACTION

                        INSERT INTO dbo.ResourceType (Name) 
                        SELECT value FROM string_split(@resourceTypes, ',')
                        EXCEPT SELECT Name FROM dbo.ResourceType WITH (TABLOCKX); 

                        -- result set 1
                        SELECT ResourceTypeId, Name FROM dbo.ResourceType;

                        INSERT INTO dbo.SearchParam (Uri)
                        SELECT * FROM  OPENJSON (@searchParams) 
                        WITH (Uri varchar(128) '$.Uri')
                        EXCEPT SELECT Uri FROM dbo.SearchParam;

                        -- result set 2
                        SELECT Uri, SearchParamId FROM dbo.SearchParam;

                        INSERT INTO dbo.ClaimType (Name) 
                        SELECT value FROM string_split(@claimTypes, ',')
                        EXCEPT SELECT Name FROM dbo.ClaimType; 

                        -- result set 3
                        SELECT ClaimTypeId, Name FROM dbo.ClaimType;

                        INSERT INTO dbo.CompartmentType (Name) 
                        SELECT value FROM string_split(@compartmentTypes, ',')
                        EXCEPT SELECT Name FROM dbo.CompartmentType; 

                        -- result set 4
                        SELECT CompartmentTypeId, Name FROM dbo.CompartmentType;
                        
                        COMMIT TRANSACTION
    
                        -- result set 5
                        SELECT Value, SystemId from dbo.System;

                        -- result set 6
                        SELECT Value, QuantityCodeId FROM dbo.QuantityCode";

                    string commaSeparatedResourceTypes    = string.Join(",", ModelInfoProvider.GetResourceTypeNames());
                    string searchParametersJson           = JsonConvert.SerializeObject(_searchParameterDefinitionManager.AllSearchParameters.Select(p => new { Name = p.Name, Uri = p.Url }));
                    string commaSeparatedClaimTypes       = string.Join(',', _securityConfiguration.PrincipalClaims);
                    string commaSeparatedCompartmentTypes = string.Join(',', ModelInfoProvider.GetCompartmentTypeNames());

                    sqlCommand.Parameters.AddWithValue("@resourceTypes", commaSeparatedResourceTypes);
                    sqlCommand.Parameters.AddWithValue("@searchParams", searchParametersJson);
                    sqlCommand.Parameters.AddWithValue("@claimTypes", commaSeparatedClaimTypes);
                    sqlCommand.Parameters.AddWithValue("@compartmentTypes", commaSeparatedCompartmentTypes);

                    using (SqlDataReader reader = sqlCommand.ExecuteReader(CommandBehavior.SequentialAccess))
                    {
                        var resourceTypeToId         = new Dictionary <string, short>(StringComparer.Ordinal);
                        var resourceTypeIdToTypeName = new Dictionary <short, string>();
                        var searchParamUriToId       = new Dictionary <Uri, short>();
                        var systemToId          = new ConcurrentDictionary <string, int>(StringComparer.OrdinalIgnoreCase);
                        var quantityCodeToId    = new ConcurrentDictionary <string, int>(StringComparer.OrdinalIgnoreCase);
                        var claimNameToId       = new Dictionary <string, byte>(StringComparer.Ordinal);
                        var compartmentTypeToId = new Dictionary <string, byte>();

                        // result set 1
                        while (reader.Read())
                        {
                            (short id, string resourceTypeName) = reader.ReadRow(VLatest.ResourceType.ResourceTypeId, VLatest.ResourceType.Name);

                            resourceTypeToId.Add(resourceTypeName, id);
                            resourceTypeIdToTypeName.Add(id, resourceTypeName);
                        }

                        // result set 2
                        reader.NextResult();

                        while (reader.Read())
                        {
                            (string uri, short searchParamId) = reader.ReadRow(VLatest.SearchParam.Uri, VLatest.SearchParam.SearchParamId);
                            searchParamUriToId.Add(new Uri(uri), searchParamId);
                        }

                        // result set 3
                        reader.NextResult();

                        while (reader.Read())
                        {
                            (byte id, string claimTypeName) = reader.ReadRow(VLatest.ClaimType.ClaimTypeId, VLatest.ClaimType.Name);
                            claimNameToId.Add(claimTypeName, id);
                        }

                        // result set 4
                        reader.NextResult();

                        while (reader.Read())
                        {
                            (byte id, string compartmentName) = reader.ReadRow(VLatest.CompartmentType.CompartmentTypeId, VLatest.CompartmentType.Name);
                            compartmentTypeToId.Add(compartmentName, id);
                        }

                        // result set 5
                        reader.NextResult();

                        while (reader.Read())
                        {
                            var(value, systemId) = reader.ReadRow(VLatest.System.Value, VLatest.System.SystemId);
                            systemToId.TryAdd(value, systemId);
                        }

                        // result set 6
                        reader.NextResult();

                        while (reader.Read())
                        {
                            (string value, int quantityCodeId) = reader.ReadRow(VLatest.QuantityCode.Value, VLatest.QuantityCode.QuantityCodeId);
                            quantityCodeToId.TryAdd(value, quantityCodeId);
                        }

                        _resourceTypeToId         = resourceTypeToId;
                        _resourceTypeIdToTypeName = resourceTypeIdToTypeName;
                        _searchParamUriToId       = searchParamUriToId;
                        _systemToId          = systemToId;
                        _quantityCodeToId    = quantityCodeToId;
                        _claimNameToId       = claimNameToId;
                        _compartmentTypeToId = compartmentTypeToId;

                        _started = true;
                    }
                }
            }
        }
 public ResourceKey(string id, string versionId = null)
     : base(ModelInfoProvider.GetFhirTypeNameForType(typeof(T)), id, versionId)
 {
 }
Exemple #23
0
 /// <summary>
 /// This method provides temporary compatibility while STU3/R4 compatibility is added
 /// </summary>
 internal static void SetModelInfoProvider()
 {
     ModelInfoProvider.SetProvider(new Stu3ModelInfoProvider());
 }
        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.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)
            {
                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)>();
                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);

                        if (searchParameterInfo.IsSortSupported())
                        {
                            sortings.Add((searchParameterInfo, sorting.Item2.ToCoreSortOrder()));
                        }
                        else
                        {
                            throw new SearchParameterNotSupportedException(string.Format(Core.Resources.SearchSortParameterNotSupported, searchParameterInfo.Name));
                        }
                    }
                    catch (SearchParameterNotSupportedException)
                    {
                        (unsupportedSortings ??= new List <(string parameterName, string reason)>()).Add((sorting.Item1, string.Format(Core.Resources.SearchSortParameterNotSupported, sorting.Item1)));
                    }
                }

                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);

            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));
                }
            }
        }
Exemple #25
0
 /// <summary>
 /// This method provides temporary compatibility while STU3/R4 compatibility is added
 /// </summary>
 public static void SetModelInfoProvider()
 {
     ModelInfoProvider.SetProvider(new VersionSpecificModelInfoProvider());
 }
Exemple #26
0
        private Expression ParseChainedExpression(string[] resourceTypes, SearchParameterInfo searchParameter, string[] targetResourceTypes, ReadOnlySpan <char> remainingKey, string value, bool reversed)
        {
            // We have more paths after this so this is a chained expression.
            // Since this is chained expression, the expression must be a reference type.
            if (searchParameter.Type != ValueSets.SearchParamType.Reference)
            {
                // The search parameter is not a reference type, which is not allowed.
                throw new InvalidSearchOperationException(Core.Resources.ChainedParameterMustBeReferenceSearchParamType);
            }

            // Check to see if the client has specifically specified the target resource type to scope to.
            if (targetResourceTypes.Any())
            {
                // A target resource type is specified.
                foreach (var targetResourceType in targetResourceTypes)
                {
                    if (!ModelInfoProvider.IsKnownResource(targetResourceType))
                    {
                        throw new InvalidSearchOperationException(string.Format(Core.Resources.ResourceNotSupported, targetResourceType));
                    }
                }
            }

            var possibleTargetResourceTypes = targetResourceTypes.Any()
                ? targetResourceTypes.Intersect(searchParameter.TargetResourceTypes)
                : searchParameter.TargetResourceTypes;

            ChainedExpression chainedExpression = null;

            foreach (var possibleTargetResourceType in possibleTargetResourceTypes)
            {
                var wrappedTargetResourceType = new[] { possibleTargetResourceType };
                var multipleChainType         = reversed ? resourceTypes : wrappedTargetResourceType;

                ChainedExpression expression;
                try
                {
                    expression = Expression.Chained(
                        resourceTypes,
                        searchParameter,
                        wrappedTargetResourceType,
                        reversed,
                        ParseImpl(
                            multipleChainType,
                            remainingKey,
                            value));
                }
                catch (Exception ex) when(ex is ResourceNotSupportedException || ex is SearchParameterNotSupportedException)
                {
                    // The resource or search parameter is not supported for the resource.
                    // We will ignore these unsupported types.
                    continue;
                }

                if (chainedExpression == null)
                {
                    chainedExpression = expression;
                }
                else if (reversed)
                {
                    chainedExpression = Expression.Chained(
                        resourceTypes,
                        searchParameter,
                        chainedExpression.TargetResourceTypes.Append(possibleTargetResourceType).ToArray(),
                        reversed,
                        ParseImpl(
                            multipleChainType,
                            remainingKey,
                            value));
                }
                else
                {
                    // If the target resource type is ambiguous, we throw an error.
                    // At the moment, this is not supported

                    throw new InvalidSearchOperationException(
                              string.Format(
                                  CultureInfo.CurrentCulture,
                                  Core.Resources.ChainedParameterSpecifyType,
                                  searchParameter.Name,
                                  string.Join(Core.Resources.OrDelimiter, searchParameter.TargetResourceTypes.Select(c => $"{searchParameter.Code}:{c}"))));
                }
            }

            if (chainedExpression == null)
            {
                // There was no reference that supports the search parameter.
                throw new InvalidSearchOperationException(Core.Resources.ChainedParameterNotSupported);
            }

            return(chainedExpression);
        }
Exemple #27
0
        /// <inheritdoc />
        public void Load(IServiceCollection services)
        {
            EnsureArg.IsNotNull(services, nameof(services));

            var jsonParser     = new FhirJsonParser();
            var jsonSerializer = new FhirJsonSerializer();

            var xmlParser     = new FhirXmlParser();
            var xmlSerializer = new FhirXmlSerializer();

            services.AddSingleton(jsonParser);
            services.AddSingleton(jsonSerializer);
            services.AddSingleton(xmlParser);
            services.AddSingleton(xmlSerializer);

            ResourceElement SetMetadata(Resource resource, string versionId, DateTimeOffset lastModified)
            {
                resource.VersionId        = versionId;
                resource.Meta.LastUpdated = lastModified;
                return(resource.ToResourceElement());
            }

            services.AddSingleton <IReadOnlyDictionary <FhirResourceFormat, Func <string, string, DateTimeOffset, ResourceElement> > >(_ =>
            {
                return(new Dictionary <FhirResourceFormat, Func <string, string, DateTimeOffset, ResourceElement> >
                {
                    {
                        FhirResourceFormat.Json, (str, version, lastModified) =>
                        {
                            var resource = jsonParser.Parse <Resource>(str);
                            return SetMetadata(resource, version, lastModified);
                        }
                    },
                    {
                        FhirResourceFormat.Xml, (str, version, lastModified) =>
                        {
                            var resource = xmlParser.Parse <Resource>(str);
                            return SetMetadata(resource, version, lastModified);
                        }
                    },
                });
            });

            services.AddSingleton <ResourceDeserializer>();

            services.Add <FormatterConfiguration>()
            .Singleton()
            .AsSelf()
            .AsService <IPostConfigureOptions <MvcOptions> >()
            .AsService <IProvideCapability>();

            services.AddSingleton <IContentTypeService, ContentTypeService>();
            services.AddSingleton <OperationOutcomeExceptionFilterAttribute>();
            services.AddSingleton <ValidateContentTypeFilterAttribute>();
            services.AddSingleton <ValidateExportRequestFilterAttribute>();

            // HTML
            // If UI is supported, then add the formatter so that the
            // document can be output in HTML view.
            if (_featureConfiguration.SupportsUI)
            {
                services.Add <HtmlOutputFormatter>()
                .Singleton()
                .AsSelf()
                .AsService <TextOutputFormatter>();
            }

            services.Add <FhirJsonInputFormatter>()
            .Singleton()
            .AsSelf()
            .AsService <TextInputFormatter>();

            services.Add <FhirJsonOutputFormatter>()
            .Singleton()
            .AsSelf()
            .AsService <TextOutputFormatter>();

            if (_featureConfiguration.SupportsXml)
            {
                services.Add <FhirXmlInputFormatter>()
                .Singleton()
                .AsSelf()
                .AsService <TextInputFormatter>();

                services.Add <FhirXmlOutputFormatter>()
                .Singleton()
                .AsSelf()
                .AsService <TextOutputFormatter>();
            }

            services.Add <FhirRequestContextAccessor>()
            .Singleton()
            .AsSelf()
            .AsService <IFhirRequestContextAccessor>();

            services.AddSingleton <CorrelationIdProvider>(_ => () => Guid.NewGuid().ToString());

            // Add conformance provider for implementation metadata.
            services.AddSingleton <IConfiguredConformanceProvider, DefaultConformanceProvider>();

            services.Add <ConformanceProvider>()
            .Singleton()
            .AsSelf()
            .AsService <IConformanceProvider>();

            services.Add <SystemConformanceProvider>()
            .Singleton()
            .AsSelf()
            .AsService <ISystemConformanceProvider>();

            services.Add <SecurityProvider>()
            .Singleton()
            .AsSelf()
            .AsService <IProvideCapability>();

            services.TypesInSameAssembly(KnownAssemblies.Core, KnownAssemblies.CoreStu3)
            .AssignableTo <IProvideCapability>()
            .Transient()
            .AsService <IProvideCapability>();

            services.AddSingleton <INarrativeHtmlSanitizer, NarrativeHtmlSanitizer>();

            var stu3ModelFactory = new Stu3ModelInfoProvider();

            services.Add(_ => stu3ModelFactory).Singleton().AsSelf().AsImplementedInterfaces();
            ModelInfoProvider.SetProvider(stu3ModelFactory);

            // Register a factory to resolve a scope that returns all components that provide capabilities
            services.AddFactory <IScoped <IEnumerable <IProvideCapability> > >();

            services.AddLazy();
            services.AddScoped();
        }