Example #1
0
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
}
Example #3
0
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)
}
Example #4
0
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))
}
Example #8
0
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)
}
Example #11
0
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
}
Example #12
0
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")
}
Example #13
0
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)
}