/// <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);
        }
Example #3
0
        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);
        }
Example #4
0
        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);
            }
        }