func (m *MongoSearchSuite) SetUpSuite(c *C) { m.EST = time.FixedZone("EST", -5*60*60) m.Local, _ = time.LoadLocation("Local") //turnOnDebugLog() // Set up the database m.DBServer = &dbtest.DBServer{} m.DBServer.SetPath(c.MkDir()) m.Session = m.DBServer.Session() db := m.Session.DB("fhir-test") m.MongoSearcher = &MongoSearcher{db} // Read in the data in FHIR format data, err := ioutil.ReadFile("../fixtures/search_test_data.json") util.CheckErr(err) maps := make([]interface{}, 19) err = json.Unmarshal(data, &maps) util.CheckErr(err) for _, resourceMap := range maps { r := models.MapToResource(resourceMap, true) collection := models.PluralizeLowerResourceName(reflect.TypeOf(r).Elem().Name()) util.CheckErr(db.C(collection).Insert(r)) } }
func (m *MongoSearcher) createQuery(query Query, withOptions bool) *mgo.Query { c := m.db.C(models.PluralizeLowerResourceName(query.Resource)) q := m.createQueryObject(query) mgoQuery := c.Find(q) if withOptions { o := query.Options() removeParallelArraySorts(o) if len(o.Sort) > 0 { fields := make([]string, len(o.Sort)) for i := range o.Sort { // Note: If there are multiple paths, we only look at the first one -- not ideal, but otherwise it gets tricky field := convertSearchPathToMongoField(o.Sort[i].Parameter.Paths[0].Path) if o.Sort[i].Descending { field = "-" + field } fields[i] = field } mgoQuery = mgoQuery.Sort(fields...) } if o.Offset > 0 { mgoQuery = mgoQuery.Skip(o.Offset) } mgoQuery = mgoQuery.Limit(o.Count) } return mgoQuery }
func (rc *ResourceController) UpdateHandler(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { var id bson.ObjectId idString := mux.Vars(r)["id"] if bson.IsObjectIdHex(idString) { id = bson.ObjectIdHex(idString) } else { http.Error(rw, "Invalid id", http.StatusBadRequest) } decoder := json.NewDecoder(r.Body) resource := models.NewStructForResourceName(rc.Name) err := decoder.Decode(resource) if err != nil { http.Error(rw, err.Error(), http.StatusInternalServerError) } c := Database.C(models.PluralizeLowerResourceName(rc.Name)) reflect.ValueOf(resource).Elem().FieldByName("Id").SetString(id.Hex()) err = c.Update(bson.M{"_id": id.Hex()}, resource) if err != nil { http.Error(rw, err.Error(), http.StatusInternalServerError) } context.Set(r, rc.Name, resource) context.Set(r, "Resource", rc.Name) context.Set(r, "Action", "update") rw.Header().Set("Content-Type", "application/json; charset=utf-8") rw.Header().Set("Access-Control-Allow-Origin", "*") json.NewEncoder(rw).Encode(resource) }
func (rc *ResourceController) CreateHandler(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { decoder := json.NewDecoder(r.Body) resource := models.NewStructForResourceName(rc.Name) err := decoder.Decode(resource) if err != nil { http.Error(rw, err.Error(), http.StatusInternalServerError) } c := Database.C(models.PluralizeLowerResourceName(rc.Name)) i := bson.NewObjectId() reflect.ValueOf(resource).Elem().FieldByName("Id").SetString(i.Hex()) err = c.Insert(resource) if err != nil { http.Error(rw, err.Error(), http.StatusInternalServerError) } context.Set(r, rc.Name, resource) context.Set(r, "Resource", rc.Name) context.Set(r, "Action", "create") rw.Header().Add("Location", responseURL(r, rc.Name, i.Hex()).String()) rw.Header().Set("Content-Type", "application/json; charset=utf-8") rw.Header().Set("Access-Control-Allow-Origin", "*") rw.WriteHeader(http.StatusCreated) json.NewEncoder(rw).Encode(resource) }
func (dal *mongoDataAccessLayer) Delete(id, resourceType string) error { bsonID, err := convertIDToBsonID(id) if err != nil { return convertMongoErr(err) } collection := dal.Database.C(models.PluralizeLowerResourceName(resourceType)) return convertMongoErr(collection.RemoveId(bsonID.Hex())) }
func (dal *mongoDataAccessLayer) ConditionalDelete(query search.Query) (count int, err error) { searcher := search.NewMongoSearcher(dal.Database) queryObject := searcher.CreateQueryObject(query) collection := dal.Database.C(models.PluralizeLowerResourceName(query.Resource)) info, err := collection.RemoveAll(queryObject) if info != nil { count = info.Removed } return count, convertMongoErr(err) }
func (dal *mongoDataAccessLayer) PostWithID(id string, resource interface{}) error { bsonID, err := convertIDToBsonID(id) if err != nil { return convertMongoErr(err) } reflect.ValueOf(resource).Elem().FieldByName("Id").SetString(bsonID.Hex()) resourceType := reflect.TypeOf(resource).Elem().Name() collection := dal.Database.C(models.PluralizeLowerResourceName(resourceType)) updateLastUpdatedDate(resource) return convertMongoErr(collection.Insert(resource)) }
func (m *MongoSearcher) createQuery(query Query, withOptions bool) *mgo.Query { c := m.db.C(models.PluralizeLowerResourceName(query.Resource)) q := m.createQueryObject(query) mgoQuery := c.Find(q) if withOptions { o := query.Options() if o.Offset > 0 { mgoQuery = mgoQuery.Skip(o.Offset) } mgoQuery = mgoQuery.Limit(o.Count) } return mgoQuery }
func (dal *mongoDataAccessLayer) Get(id, resourceType string) (result interface{}, err error) { bsonID, err := convertIDToBsonID(id) if err != nil { return nil, convertMongoErr(err) } collection := dal.Database.C(models.PluralizeLowerResourceName(resourceType)) result = models.NewStructForResourceName(resourceType) if err = collection.FindId(bsonID.Hex()).One(result); err != nil { return nil, convertMongoErr(err) } return }
func (dal *mongoDataAccessLayer) Put(id string, resource interface{}) (createdNew bool, err error) { bsonID, err := convertIDToBsonID(id) if err != nil { return false, convertMongoErr(err) } resourceType := reflect.TypeOf(resource).Elem().Name() collection := dal.Database.C(models.PluralizeLowerResourceName(resourceType)) reflect.ValueOf(resource).Elem().FieldByName("Id").SetString(bsonID.Hex()) updateLastUpdatedDate(resource) info, err := collection.UpsertId(bsonID.Hex(), resource) if err == nil { createdNew = (info.Updated == 0) } return createdNew, convertMongoErr(err) }
func (rc *ResourceController) LoadResource(r *http.Request) (interface{}, error) { var id bson.ObjectId idString := mux.Vars(r)["id"] if bson.IsObjectIdHex(idString) { id = bson.ObjectIdHex(idString) } else { return nil, errors.New("Invalid id") } c := Database.C(models.PluralizeLowerResourceName(rc.Name)) result := models.NewStructForResourceName(rc.Name) err := c.Find(bson.M{"_id": id.Hex()}).One(result) if err != nil { return nil, err } context.Set(r, rc.Name, result) context.Set(r, "Resource", rc.Name) return result, nil }
func (rc *ResourceController) DeleteHandler(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { var id bson.ObjectId idString := mux.Vars(r)["id"] if bson.IsObjectIdHex(idString) { id = bson.ObjectIdHex(idString) } else { http.Error(rw, "Invalid id", http.StatusBadRequest) } c := Database.C(models.PluralizeLowerResourceName(rc.Name)) err := c.Remove(bson.M{"_id": id.Hex()}) if err != nil { http.Error(rw, err.Error(), http.StatusInternalServerError) return } context.Set(r, rc.Name, id.Hex()) context.Set(r, "Resource", rc.Name) context.Set(r, "Action", "delete") }
func (s *BatchControllerSuite) TestUploadPatientBundle(c *C) { data, err := os.Open("../fixtures/john_peters_bundle.json") defer data.Close() util.CheckErr(err) decoder := json.NewDecoder(data) requestBundle := &models.Bundle{} err = decoder.Decode(requestBundle) util.CheckErr(err) data.Seek(0, 0) // Reset the file pointer res, err := http.Post(s.Server.URL+"/", "application/json", data) util.CheckErr(err) c.Assert(res.StatusCode, Equals, 200) decoder = json.NewDecoder(res.Body) responseBundle := &models.Bundle{} err = decoder.Decode(responseBundle) util.CheckErr(err) c.Assert(responseBundle.Type, Equals, "transaction-response") c.Assert(*responseBundle.Total, Equals, uint32(19)) c.Assert(responseBundle.Entry, HasLen, 19) for i := range responseBundle.Entry { resEntry, reqEntry := responseBundle.Entry[i], requestBundle.Entry[i] // response resource type should match request resource type c.Assert(reflect.TypeOf(resEntry.Resource), Equals, reflect.TypeOf(reqEntry.Resource)) // full URLs and IDs should be difference in the response c.Assert(resEntry.FullUrl, Not(Equals), reqEntry.FullUrl) c.Assert(s.getResourceID(resEntry), Not(Equals), s.getResourceID(reqEntry)) // full URL in response should contain the new ID c.Assert(strings.HasSuffix(resEntry.FullUrl, s.getResourceID(resEntry)), Equals, true) // response should not contain the request c.Assert(resEntry.Request, IsNil) // response should have 201 status and location c.Assert(resEntry.Response.Status, Equals, "201") c.Assert(resEntry.Response.Location, Equals, resEntry.FullUrl) // make sure it was stored to the DB rName := reflect.TypeOf(resEntry.Resource).Elem().Name() coll := Database.C(models.PluralizeLowerResourceName(rName)) num, err := coll.Find(bson.M{"_id": s.getResourceID(resEntry)}).Count() util.CheckErr(err) c.Assert(num, Equals, 1) } // Check patient references patientId := responseBundle.Entry[0].Resource.(*models.Patient).Id c.Assert(bson.IsObjectIdHex(patientId), Equals, true) s.checkReference(c, responseBundle.Entry[1].Resource.(*models.Encounter).Patient, patientId, "Patient") s.checkReference(c, responseBundle.Entry[2].Resource.(*models.Encounter).Patient, patientId, "Patient") s.checkReference(c, responseBundle.Entry[3].Resource.(*models.Encounter).Patient, patientId, "Patient") s.checkReference(c, responseBundle.Entry[4].Resource.(*models.Encounter).Patient, patientId, "Patient") s.checkReference(c, responseBundle.Entry[5].Resource.(*models.Condition).Patient, patientId, "Patient") s.checkReference(c, responseBundle.Entry[6].Resource.(*models.Condition).Patient, patientId, "Patient") s.checkReference(c, responseBundle.Entry[7].Resource.(*models.Condition).Patient, patientId, "Patient") s.checkReference(c, responseBundle.Entry[8].Resource.(*models.Condition).Patient, patientId, "Patient") s.checkReference(c, responseBundle.Entry[9].Resource.(*models.Condition).Patient, patientId, "Patient") s.checkReference(c, responseBundle.Entry[10].Resource.(*models.Observation).Subject, patientId, "Patient") s.checkReference(c, responseBundle.Entry[11].Resource.(*models.Procedure).Subject, patientId, "Patient") s.checkReference(c, responseBundle.Entry[12].Resource.(*models.DiagnosticReport).Subject, patientId, "Patient") s.checkReference(c, responseBundle.Entry[13].Resource.(*models.Observation).Subject, patientId, "Patient") s.checkReference(c, responseBundle.Entry[14].Resource.(*models.Observation).Subject, patientId, "Patient") s.checkReference(c, responseBundle.Entry[15].Resource.(*models.Observation).Subject, patientId, "Patient") s.checkReference(c, responseBundle.Entry[16].Resource.(*models.Procedure).Subject, patientId, "Patient") s.checkReference(c, responseBundle.Entry[17].Resource.(*models.MedicationStatement).Patient, patientId, "Patient") s.checkReference(c, responseBundle.Entry[18].Resource.(*models.Immunization).Patient, patientId, "Patient") // Check encounter references encounterId := responseBundle.Entry[1].Resource.(*models.Encounter).Id c.Assert(bson.IsObjectIdHex(encounterId), Equals, true) s.checkReference(c, responseBundle.Entry[10].Resource.(*models.Observation).Encounter, encounterId, "Encounter") s.checkReference(c, responseBundle.Entry[11].Resource.(*models.Procedure).Encounter, encounterId, "Encounter") // Check dx report references dxReportId := responseBundle.Entry[12].Resource.(*models.DiagnosticReport).Id c.Assert(bson.IsObjectIdHex(dxReportId), Equals, true) s.checkReference(c, &responseBundle.Entry[11].Resource.(*models.Procedure).Report[0], dxReportId, "DiagnosticReport") // Check observation references obs0Id := responseBundle.Entry[13].Resource.(*models.Observation).Id c.Assert(bson.IsObjectIdHex(obs0Id), Equals, true) s.checkReference(c, &responseBundle.Entry[12].Resource.(*models.DiagnosticReport).Result[0], obs0Id, "Observation") obs1Id := responseBundle.Entry[14].Resource.(*models.Observation).Id c.Assert(bson.IsObjectIdHex(obs1Id), Equals, true) s.checkReference(c, &responseBundle.Entry[12].Resource.(*models.DiagnosticReport).Result[1], obs1Id, "Observation") obs2Id := responseBundle.Entry[15].Resource.(*models.Observation).Id c.Assert(bson.IsObjectIdHex(obs2Id), Equals, true) s.checkReference(c, &responseBundle.Entry[12].Resource.(*models.DiagnosticReport).Result[2], obs2Id, "Observation") }
// CreatePipeline takes a FHIR-based Query and returns a pointer to the // corresponding mgo.Pipe. The returned mgo.Pipe will obey any options // passed in through the query string (such as _count and _offset) and will // also use default options when none are passed in (e.g., count = 100). // The caller is responsible for executing the returned pipe (allowing // additional flexibility in how results are returned). // // CreatePipeline must be used when the _include and _revinclude options // are used (since CreateQuery can't support joins). func (m *MongoSearcher) CreatePipeline(query Query) *mgo.Pipe { c := m.db.C(models.PluralizeLowerResourceName(query.Resource)) p := []bson.M{{"$match": m.createQueryObject(query)}} o := query.Options() // support for _sort removeParallelArraySorts(o) if len(o.Sort) > 0 { var sortBSOND bson.D for _, sort := range o.Sort { // Note: If there are multiple paths, we only look at the first one -- not ideal, but otherwise it gets tricky field := convertSearchPathToMongoField(sort.Parameter.Paths[0].Path) order := 1 if sort.Descending { order = -1 } sortBSOND = append(sortBSOND, bson.DocElem{Name: field, Value: order}) } p = append(p, bson.M{"$sort": sortBSOND}) } // support for _offset if o.Offset > 0 { p = append(p, bson.M{"$skip": o.Offset}) } // support for _count p = append(p, bson.M{"$limit": o.Count}) // support for _include if len(o.Include) > 0 { for _, incl := range o.Include { for _, inclPath := range incl.Parameter.Paths { if inclPath.Type != "Reference" { continue } // Mongo paths shouldn't have the array indicators, so remove them localField := strings.Replace(inclPath.Path, "[]", "", -1) + ".referenceid" for i, inclTarget := range incl.Parameter.Targets { if inclTarget == "Any" { continue } from := models.PluralizeLowerResourceName(inclTarget) as := fmt.Sprintf("_included%sResourcesReferencedBy%s", inclTarget, strings.Title(incl.Parameter.Name)) // If there are multiple paths, we need to store each path separately if len(incl.Parameter.Paths) > 1 { as += fmt.Sprintf("Path%d", i+1) } p = append(p, bson.M{"$lookup": bson.M{ "from": from, "localField": localField, "foreignField": "_id", "as": as, }}) } } } } // support for _revinclude if len(o.RevInclude) > 0 { for _, incl := range o.RevInclude { // we only want parameters that have the search resource as their target targetsSearchResource := false for _, inclTarget := range incl.Parameter.Targets { if inclTarget == query.Resource || inclTarget == "Any" { targetsSearchResource = true break } } if !targetsSearchResource { continue } // it comes from the other resource collection from := models.PluralizeLowerResourceName(incl.Parameter.Resource) // iterate through the paths, adding a join to the pipeline for each one for i, inclPath := range incl.Parameter.Paths { if inclPath.Type != "Reference" { continue } // Mongo paths shouldn't have the array indicators, so remove them foreignField := strings.Replace(inclPath.Path, "[]", "", -1) + ".referenceid" as := fmt.Sprintf("_revIncluded%sResourcesReferencing%s", incl.Parameter.Resource, strings.Title(incl.Parameter.Name)) // If there are multiple paths, we need to store each path separately if len(incl.Parameter.Paths) > 1 { as += fmt.Sprintf("Path%d", i+1) } p = append(p, bson.M{"$lookup": bson.M{ "from": from, "localField": "_id", "foreignField": foreignField, "as": as, }}) } } } return c.Pipe(p) }