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); }
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); } }
/// <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>(); }
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; }
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; }); } }
/// <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)); }
public static Type ToResourceModelType(this ResourceElement resourceType) { EnsureArg.IsNotNull(resourceType, nameof(resourceType)); return(ModelInfoProvider.GetTypeForFhirType(resourceType.InstanceType)); }
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)); }
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) { }
/// <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)); } } }
/// <summary> /// This method provides temporary compatibility while STU3/R4 compatibility is added /// </summary> public static void SetModelInfoProvider() { ModelInfoProvider.SetProvider(new VersionSpecificModelInfoProvider()); }
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); }
/// <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(); }