/// <summary> /// Asynchronously gets the resources that match the given params /// </summary> /// <param name="query">Query parameters (optional)</param> /// <param name="size">Number of results to return (optional)</param> /// <param name="from">Beginning index for results (optional)</param> /// <param name="includeFields">Fields to include (optional)</param> /// <returns>Resource query result</returns> public async Task <ResourceQueryResult> QueryResourcesAsync( ResourceQuery query, int size = 20, int from = 0, string[] includeFields = null ) { ResourceQueryResult queryResults = new ResourceQueryResult(); // Set up the SearchRequest to send to the API. Indices index = Indices.Index(new string[] { this._apiOptions.AliasName }); Types types = Types.Type(new string[] { "resource" }); SearchRequest request = new SearchRequest(index, types) { Size = size, From = from, Sort = new List <ISort> { new SortField { Field = "_score" }, new SortField { Field = "id" } }, //TODO: Source = new SourceFilter { Includes = includeFields } }; //Add in the query var searchQuery = this.GetFullQuery(query.Keyword, query.Filters); if (searchQuery != null) { request.Query = searchQuery; } ISearchResponse <Resource> response = null; try { // Fetch the resources that match the given query and parameters from the API. response = await _elasticClient.SearchAsync <Resource>(request); } catch (Exception ex) { //TODO: Update error logger to include query _logger.LogError("Could not fetch resources for query.", ex); throw new APIErrorException(500, "Could not fetch resources for query."); } // If the API's response isn't valid, throw an error and return 500 status code. if (!response.IsValid) { _logger.LogError("Bad request."); throw new APIErrorException(500, "Errors occurred."); } // If the API finds resources matching the params, build the ResourceQueryResult to return. if (response.Total > 0) { // Build the array of resources for the returned restult. List <Resource> resourceResults = new List <Resource>(); foreach (Resource res in response.Documents) { resourceResults.Add(res); } queryResults.Results = resourceResults.ToArray(); queryResults.TotalResults = Convert.ToInt32(response.Total); queryResults.From = from; } return(queryResults); }
public async Task <ResourceResults> GetAll( [FromQuery(Name = "q")] string keyword = null, [FromQuery(Name = "toolTypes")] string[] toolTypes = null, [FromQuery(Name = "toolSubtypes")] string[] subTypes = null, [FromQuery(Name = "researchAreas")] string[] researchAreas = null, [FromQuery(Name = "researchTypes")] string[] researchTypes = null, [FromQuery(Name = "docs")] string[] docs = null, [FromQuery(Name = "include")] string[] includeFields = null, [FromQuery(Name = "includeFacets")] string[] includeFacets = null, [FromQuery(Name = "size")] string strSize = null, [FromQuery(Name = "from")] string strFrom = null ) { // Validate query params here // 1. Throw error if subToolType exists, but no toolType if (IsNullOrEmpty(toolTypes) && !IsNullOrEmpty(subTypes)) { _logger.LogError("Cannot have subtype without tooltype.", subTypes); throw new ArgumentException("Cannot have subtype without tooltype."); } // 2. Throw error if multiple toolTypes exist if (toolTypes != null && toolTypes.Length > 1) { _logger.LogError("Cannot have multiple tooltypes.", toolTypes); throw new ArgumentException("Cannot have multiple tooltypes."); } // 3. Throw error if size is invalid int int size; if (string.IsNullOrWhiteSpace(strSize)) { size = 20; } else { int tmpInt = -1; if (int.TryParse(strSize.Trim(), out tmpInt)) { if (tmpInt < 0) { _logger.LogError($"Invalid size parameter: {strSize}."); throw new APIErrorException(400, $"Bad request: Invalid size parameter {strSize}."); } size = tmpInt; } else { _logger.LogError($"Invalid size parameter: {strSize}."); throw new APIErrorException(400, $"Bad request: Invalid size parameter {strSize}."); } } // 4. Throw error if from is invalid int int from; if (string.IsNullOrWhiteSpace(strFrom)) { from = 0; } else { int tmpInt = -1; if (int.TryParse(strFrom.Trim(), out tmpInt)) { if (tmpInt < 0) { _logger.LogError($"Invalid from parameter: {strFrom}."); throw new APIErrorException(400, $"Bad request: Invalid from parameter {strFrom}."); } from = tmpInt; } else { _logger.LogError($"Invalid from parameter: {strFrom}."); throw new APIErrorException(400, $"Bad request: Invalid from parameter {strFrom}."); } } // Build resource query object using params ResourceQuery resourceQuery = new ResourceQuery(); // Add keyword, if included in params if (!string.IsNullOrWhiteSpace(keyword)) { resourceQuery.Keyword = keyword; } // Add tool types, if included in params if (!IsNullOrEmpty(toolTypes)) { resourceQuery.Filters.Add("toolTypes", toolTypes); } // Add tool subtypes, if included in params if (!IsNullOrEmpty(subTypes)) { resourceQuery.Filters.Add("toolSubtypes", subTypes); } // Add research areas, if included in params if (!IsNullOrEmpty(researchAreas)) { resourceQuery.Filters.Add("researchAreas", researchAreas); } // Add research types, if included in params if (!IsNullOrEmpty(researchTypes)) { resourceQuery.Filters.Add("researchTypes", researchTypes); } // Add docs, if included in params if (!IsNullOrEmpty(docs)) { resourceQuery.Filters.Add("docs", docs); } // Set default values for params //facetsBeingRequest is used because if we modify the original params //then the OriginalString of the response will show the default //include facets. string[] facetsBeingRequested = new string[] { }; if (IsNullOrEmpty(includeFacets)) { facetsBeingRequested = GetDefaultIncludeFacets(); } else if (!ValidateFacetList(includeFacets)) { //TODO: Actually list the invalid facets _logger.LogError("Included facets in query are not valid."); throw new APIErrorException(400, "Included facets in query are not valid."); } else { facetsBeingRequested = includeFacets; } string[] fieldsBeingRequested = new string[] { }; if (IsNullOrEmpty(includeFields)) { fieldsBeingRequested = GetDefaultIncludeFields(); } else if (!ValidateFieldList(includeFields)) { //TODO: Actually list the invalid fields _logger.LogError("Included fields in query are not valid."); throw new APIErrorException(400, "Included fields in query are not valid."); } else { fieldsBeingRequested = includeFields; } //Create the results object ResourceResults results = new ResourceResults(); bool noMatchedResults = true; //Now call query results & get aggs at the same time. await Task.WhenAll( Task.Run(async() => { // Perform query for resources (using params if they're given) ResourceQueryResult queryResults = await _queryService.QueryResourcesAsync(resourceQuery, size, from, fieldsBeingRequested); // Convert query results into ResourceResults PageMetaData meta = new PageMetaData { TotalResults = queryResults.TotalResults, From = queryResults.From, OriginalQuery = _urlHelper.RouteUrl(new { size, from, q = keyword, toolTypes, toolSubtypes = subTypes, researchAreas, researchTypes, docs, //DO NOT USE fieldsBeingRequested or facetsBeingRequested HERE. These are the original lists provided by the user. include = includeFields, includeFacets }) }; results.Meta = meta; results.Results = queryResults.Results; //While we may have asked for size 0, this will still tell us if the full query has results. noMatchedResults = queryResults.TotalResults == 0; if (from > queryResults.TotalResults) { throw new APIErrorException(400, $"Offset (from) value of {queryResults.TotalResults} is greater than the number of results."); } }), Task.Run(async() => { // Perform query for facet aggregations (using includeFacets param if it's given, otherwise default facets to include from options) results.Facets = await GetFacets(facetsBeingRequested, resourceQuery); //Go back to sync now. }) ); //Multiselect facets will query all filters except thier own. So, //you can have no search results returned, but still get a facet. //For example, /resources?researchAreas=chicken will actually return //all the facet items for researchAreas. This code below removes //all facets when there are 0 results, unless the user has asked for //a query size of 0; if (noMatchedResults) { results.Facets = new Facet[] { }; } return(results); }
public async Task <HttpResponseMessage> Get(string resource) { string respval; if (Request.RequestUri.AbsolutePath.ToLower().EndsWith("metadata")) { respval = SerializeResponse(FhirHelper.GenerateCapabilityStatement(Request.RequestUri.AbsoluteUri)); } else { var nvc = HttpUtility.ParseQueryString(Request.RequestUri.Query); var id = nvc["_id"]; var nextpage = nvc["_nextpage"]; var count = nvc["_count"] ?? "100"; var querytotal = nvc["_querytotal"] ?? "-1"; IEnumerable <Resource> retVal; ResourceQueryResult searchrslt = null; int iqueryTotal; if (string.IsNullOrEmpty(id)) { var query = FhirParmMapper.Instance.GenerateQuery(storage, resource, nvc); searchrslt = await storage.QueryFhirResourceAsync(query, resource, int.Parse(count), nextpage, long.Parse(querytotal)). ConfigureAwait(false); retVal = searchrslt.Resources; iqueryTotal = (int)searchrslt.Total; } else { retVal = new List <Resource>(); var r = await storage.LoadFhirResourceAsync(id, resource).ConfigureAwait(false); if (r != null) { ((List <Resource>)retVal).Add(r); } iqueryTotal = retVal.Count(); } var baseurl = Request.RequestUri.Scheme + "://" + Request.RequestUri.Authority + "/" + resource; var results = new Bundle { Id = Guid.NewGuid().ToString(), Type = Bundle.BundleType.Searchset, Total = iqueryTotal, Link = new List <Bundle.LinkComponent>() }; var qscoll = Request.RequestUri.ParseQueryString(); qscoll.Remove("_count"); qscoll.Remove("_querytotal"); qscoll.Add("_querytotal", searchrslt.Total.ToString()); qscoll.Add("_count", count); results.Link.Add(new Bundle.LinkComponent { Url = baseurl + "?" + qscoll, Relation = "self" }); if (searchrslt.ContinuationToken != null) { qscoll.Remove("_nextpage"); qscoll.Add("_nextpage", searchrslt.ContinuationToken); results.Link.Add(new Bundle.LinkComponent { Url = baseurl + "?" + qscoll, Relation = "next" }); } results.Entry = new List <Bundle.EntryComponent>(); var match = new Bundle.SearchComponent { Mode = Bundle.SearchEntryMode.Match }; var include = new Bundle.SearchComponent { Mode = Bundle.SearchEntryMode.Include }; foreach (var p in retVal) { results.Entry.Add(new Bundle.EntryComponent { Resource = p, FullUrl = FhirHelper.GetFullUrl(Request, p), Search = match }); var includes = await FhirHelper.ProcessIncludesAsync(p, nvc, storage).ConfigureAwait(false); foreach (var r in includes) { results.Entry.Add(new Bundle.EntryComponent { Resource = r, FullUrl = FhirHelper.GetFullUrl(Request, r), Search = include }); } } respval = SerializeResponse(results); } var response = Request.CreateResponse(HttpStatusCode.OK); response.Headers.TryAddWithoutValidation("Accept", CurrentAcceptType); response.Content = new StringContent(respval, Encoding.UTF8); response.Content.Headers.TryAddWithoutValidation("Content-Type", IsAccceptTypeJson ? Fhircontenttypejson : Fhircontenttypexml); return(response); }
public async Task <HttpResponseMessage> Get(string resource) { try { string respval = null; if (Request.RequestUri.AbsolutePath.ToLower().EndsWith("metadata")) { respval = SerializeResponse(FhirHelper.GenerateCapabilityStatement(Request.RequestUri.AbsoluteUri)); } else { string validResource = FhirHelper.ValidateResourceType(resource); NameValueCollection nvc = HttpUtility.ParseQueryString(Request.RequestUri.Query); string _id = nvc["_id"]; string _nextpage = nvc["_nextpage"]; string _count = nvc["_count"]; if (_count == null) { _count = "100"; } string _querytotal = nvc["_querytotal"]; if (_querytotal == null) { _querytotal = "-1"; } IEnumerable <Resource> retVal = null; ResourceQueryResult searchrslt = null; int iqueryTotal = 0; if (string.IsNullOrEmpty(_id)) { string query = FhirParmMapper.Instance.GenerateQuery(storage, validResource, nvc); searchrslt = await storage.QueryFHIRResource(query, validResource, int.Parse(_count), _nextpage, long.Parse(_querytotal)); retVal = searchrslt.Resources; iqueryTotal = (int)searchrslt.Total; } else { retVal = new List <Resource>(); var r = await storage.LoadFHIRResource(_id, validResource); if (r != null) { ((List <Resource>)retVal).Add(r); } iqueryTotal = retVal.Count(); } var baseurl = Request.RequestUri.Scheme + "://" + Request.RequestUri.Authority + "/" + validResource; Bundle results = new Bundle(); results.Id = Guid.NewGuid().ToString(); results.Type = Bundle.BundleType.Searchset; results.Total = iqueryTotal; results.Link = new System.Collections.Generic.List <Bundle.LinkComponent>(); NameValueCollection qscoll = Request.RequestUri.ParseQueryString(); qscoll.Remove("_count"); qscoll.Remove("_querytotal"); qscoll.Add("_querytotal", searchrslt.Total.ToString()); qscoll.Add("_count", _count); results.Link.Add(new Bundle.LinkComponent() { Url = baseurl + "?" + qscoll.ToString(), Relation = "self" }); if (searchrslt.ContinuationToken != null) { qscoll.Remove("_nextpage"); qscoll.Add("_nextpage", searchrslt.ContinuationToken); results.Link.Add(new Bundle.LinkComponent() { Url = baseurl + "?" + qscoll.ToString(), Relation = "next" }); } results.Entry = new System.Collections.Generic.List <Bundle.EntryComponent>(); Bundle.SearchComponent match = new Bundle.SearchComponent(); match.Mode = Bundle.SearchEntryMode.Match; Bundle.SearchComponent include = new Bundle.SearchComponent(); include.Mode = Bundle.SearchEntryMode.Include; foreach (Resource p in retVal) { results.Entry.Add(new Bundle.EntryComponent() { Resource = p, FullUrl = FhirHelper.GetFullURL(Request, p), Search = match }); var includes = await FhirHelper.ProcessIncludes(p, nvc, storage); foreach (Resource r in includes) { results.Entry.Add(new Bundle.EntryComponent() { Resource = r, FullUrl = FhirHelper.GetFullURL(Request, r), Search = include }); } } if (retVal != null) { respval = SerializeResponse(results); } } var response = this.Request.CreateResponse(HttpStatusCode.OK); response.Headers.TryAddWithoutValidation("Accept", CurrentAcceptType); response.Content = new StringContent(respval, Encoding.UTF8); response.Content.Headers.TryAddWithoutValidation("Content-Type", IsAccceptTypeJSON ? FHIRCONTENTTYPEJSON : FHIRCONTENTTYPEXML); return(response); } catch (Exception e) { OperationOutcome oo = new OperationOutcome(); oo.Issue = new System.Collections.Generic.List <OperationOutcome.IssueComponent>(); OperationOutcome.IssueComponent ic = new OperationOutcome.IssueComponent(); ic.Severity = OperationOutcome.IssueSeverity.Error; ic.Code = OperationOutcome.IssueType.Exception; ic.Diagnostics = e.Message; oo.Issue.Add(ic); var response = this.Request.CreateResponse(HttpStatusCode.BadRequest); response.Headers.TryAddWithoutValidation("Accept", CurrentAcceptType); response.Content = new StringContent(SerializeResponse(oo), Encoding.UTF8); response.Content.Headers.TryAddWithoutValidation("Content-Type", IsAccceptTypeJSON ? FHIRCONTENTTYPEJSON : FHIRCONTENTTYPEXML); response.Content.Headers.LastModified = DateTimeOffset.Now; return(response); } }