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