func generatePagingLinks(r *http.Request, query search.Query, total uint32) []models.BundleLinkComponent { links := make([]models.BundleLinkComponent, 0, 5) values := query.NormalizedQueryValues(false) options := query.Options() count := uint32(options.Count) offset := uint32(options.Offset) // First create the base URL for paging baseURL := responseURL(r, query.Resource) // Self link links = append(links, newLink("self", baseURL, values, count, offset)) // First link links = append(links, newLink("first", baseURL, values, count, uint32(0))) // Previous link if offset > uint32(0) { newOffset := offset - count // Handle case where paging is uneven (e.g., count=10&offset=5) if count > offset { newOffset = uint32(0) } links = append(links, newLink("previous", baseURL, values, offset-newOffset, newOffset)) } // Next Link if total > (offset + count) { links = append(links, newLink("next", baseURL, values, count, offset+count)) } // Last Link remainder := (total - offset) % count if total < offset { remainder = uint32(0) } newOffset := total - remainder if remainder == uint32(0) && total > count { newOffset = total - count } links = append(links, newLink("last", baseURL, values, count, newOffset)) return links }
func (dal *mongoDataAccessLayer) Search(baseURL url.URL, searchQuery search.Query) (*models.Bundle, error) { searcher := search.NewMongoSearcher(dal.Database) var result interface{} var err error usesIncludes := len(searchQuery.Options().Include) > 0 usesRevIncludes := len(searchQuery.Options().RevInclude) > 0 // Only use (slower) pipeline if it is needed if usesIncludes || usesRevIncludes { result = models.NewSlicePlusForResourceName(searchQuery.Resource, 0, 0) err = searcher.CreatePipeline(searchQuery).All(result) } else { result = models.NewSliceForResourceName(searchQuery.Resource, 0, 0) err = searcher.CreateQuery(searchQuery).All(result) } if err != nil { return nil, convertMongoErr(err) } includesMap := make(map[string]interface{}) var entryList []models.BundleEntryComponent resultVal := reflect.ValueOf(result).Elem() for i := 0; i < resultVal.Len(); i++ { var entry models.BundleEntryComponent entry.Resource = resultVal.Index(i).Addr().Interface() entry.Search = &models.BundleEntrySearchComponent{Mode: "match"} entryList = append(entryList, entry) if usesIncludes || usesRevIncludes { rpi, ok := entry.Resource.(ResourcePlusRelatedResources) if ok { for k, v := range rpi.GetIncludedAndRevIncludedResources() { includesMap[k] = v } } } } for _, v := range includesMap { var entry models.BundleEntryComponent entry.Resource = v entry.Search = &models.BundleEntrySearchComponent{Mode: "include"} entryList = append(entryList, entry) } var bundle models.Bundle bundle.Id = bson.NewObjectId().Hex() bundle.Type = "searchset" bundle.Entry = entryList options := searchQuery.Options() // Need to get the true total (not just how many were returned in this response) var total uint32 if resultVal.Len() == options.Count || resultVal.Len() == 0 { // Need to get total count from the server, since there may be more or the offset was too high intTotal, err := searcher.CreateQueryWithoutOptions(searchQuery).Count() if err != nil { return nil, convertMongoErr(err) } total = uint32(intTotal) } else { // We can figure out the total by adding the offset and # results returned total = uint32(options.Offset + resultVal.Len()) } bundle.Total = &total // Add links for paging bundle.Link = generatePagingLinks(baseURL, searchQuery, total) return &bundle, nil }
func (rc *ResourceController) IndexHandler(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { defer func() { if r := recover(); r != nil { rw.Header().Set("Content-Type", "application/json; charset=utf-8") switch x := r.(type) { case search.Error: rw.WriteHeader(x.HTTPStatus) json.NewEncoder(rw).Encode(x.OperationOutcome) return default: outcome := &models.OperationOutcome{ Issue: []models.OperationOutcomeIssueComponent{ models.OperationOutcomeIssueComponent{ Severity: "fatal", Code: "exception", }, }, } rw.WriteHeader(http.StatusInternalServerError) json.NewEncoder(rw).Encode(outcome) } } }() result := models.NewSliceForResourceName(rc.Name, 0, 0) // Create and execute the Mongo query based on the http query params searcher := search.NewMongoSearcher(Database) searchQuery := search.Query{Resource: rc.Name, Query: r.URL.RawQuery} mgoQuery := searcher.CreateQuery(searchQuery) // Horrible, horrible hack (for now) to ensure patients are sorted by name. This is needed by // the frontend, else paging won't work correctly. This should be removed when the general // sorting feature is implemented. if rc.Name == "Patient" { // To add insult to injury, mongo will not let us sort by family *and* given name: // Executor error: BadValue cannot sort with keys that are parallel arrays mgoQuery = mgoQuery.Sort("name.0.family.0" /*", name.0.given.0"*/, "_id") } err := mgoQuery.All(result) if err != nil { http.Error(rw, err.Error(), http.StatusInternalServerError) } var entryList []models.BundleEntryComponent resultVal := reflect.ValueOf(result).Elem() for i := 0; i < resultVal.Len(); i++ { var entry models.BundleEntryComponent entry.Resource = resultVal.Index(i).Addr().Interface() entryList = append(entryList, entry) } var bundle models.Bundle bundle.Id = bson.NewObjectId().Hex() bundle.Type = "searchset" bundle.Entry = entryList options := searchQuery.Options() // Need to get the true total (not just how many were returned in this response) var total uint32 if resultVal.Len() == options.Count || resultVal.Len() == 0 { // Need to get total count from the server, since there may be more or the offset was too high intTotal, err := searcher.CreateQueryWithoutOptions(searchQuery).Count() if err != nil { http.Error(rw, err.Error(), http.StatusInternalServerError) } total = uint32(intTotal) } else { // We can figure out the total by adding the offset and # results returned total = uint32(options.Offset + resultVal.Len()) } bundle.Total = &total // Add links for paging bundle.Link = generatePagingLinks(r, searchQuery, total) context.Set(r, rc.Name, reflect.ValueOf(result).Elem().Interface()) context.Set(r, "Resource", rc.Name) context.Set(r, "Action", "search") rw.Header().Set("Content-Type", "application/json; charset=utf-8") rw.Header().Set("Access-Control-Allow-Origin", "*") json.NewEncoder(rw).Encode(&bundle) }