// Copyright 2015 The Project Buendia Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy
// of the License at: http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distrib-
// uted under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
// OR CONDITIONS OF ANY KIND, either express or implied. See the License for
// specific language governing permissions and limitations under the License.
package org.openmrs.projectbuendia.webservices.rest;
import org.openmrs.Concept;
import org.openmrs.Encounter;
import org.openmrs.Obs;
import org.openmrs.OpenmrsObject;
import org.openmrs.Patient;
import org.openmrs.api.PatientService;
import org.openmrs.api.context.Context;
import org.openmrs.module.webservices.rest.SimpleObject;
import org.openmrs.module.webservices.rest.web.RequestContext;
import org.openmrs.module.webservices.rest.web.RestConstants;
import org.openmrs.module.webservices.rest.web.annotation.Resource;
import org.openmrs.module.webservices.rest.web.resource.api.Creatable;
import org.openmrs.module.webservices.rest.web.response.ResponseException;
import org.openmrs.projectbuendia.Utils;
import org.projectbuendia.openmrs.webservices.rest.RestController;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* A collection where each item corresponds to one patient and contains
* the encounter and observation data for that patient as of a particular
* point in time (referred to as the "snapshot time").
* @see AbstractReadOnlyResource
*/
@Resource(name = RestController.REST_VERSION_1_AND_NAMESPACE + "/encounters",
supportedClass = Encounter.class, supportedOpenmrsVersions = "1.10.*,1.11.*")
public class EncounterResource implements Creatable {
private final PatientService patientService;
private RequestLogger logger = RequestLogger.LOGGER;
public EncounterResource() {
patientService = Context.getPatientService();
}
/**
* Create a new encounter for a patient. The expected JSON format is:
* {
* "uuid": "patient-uuid-xxxx",
* "timestamp": seconds_since_epoch,
* "observations": [
* {
* "question_uuid": "xxxx-...",
* # then ONE of the following three answer_* fields:
* "answer_date": "2013-01-30"
* "answer_number": 40
* "answer_uuid": "xxxx-...."
* # and OPTIONALLY this field:
* "order_uuid": "xxxx-..."
* },
* ]
* }
*/
@Override
public Object create(SimpleObject obj, RequestContext context) throws ResponseException {
try {
logger.request(context, this, "create", obj);
Object result = createInner(obj, context);
logger.reply(context, this, "create", result);
return result;
} catch (Exception e) {
logger.error(context, this, "create", e);
throw e;
}
}
public Object createInner(SimpleObject post, RequestContext context) throws ResponseException {
// Warning! In order to re-use the observation creation code from PatientResource,
// the JSON data format for this create method is different from the JSON data format
// for get. This is terrible REST design. However, we are close to shipping, and
// I don't want to introduce the server and client side changes needed if I changed
// the wire format. So instead, there is this comment.
// TODO: refactor the wire format for getEncounters so it matches the create format.
if (!post.containsKey("uuid")) {
throw new InvalidObjectDataException("Missing \"uuid\" key for patient");
}
Patient patient = patientService.getPatientByUuid(post.get("uuid").toString());
if (patient == null) {
throw new InvalidObjectDataException("Patient not found: " + post.get("uuid"));
}
Date encounterTime;
Object timestamp = post.get("timestamp");
try {
if (timestamp != null) {
encounterTime = new Date(Long.parseLong(timestamp.toString())*1000L);
} else {
// Allow clients to omit the timestamp to use the current server time.
encounterTime = new Date();
}
} catch (NumberFormatException ex) {
throw new InvalidObjectDataException(
"Expected seconds since epoch for \"timestamp\" value: " + ex.getMessage());
}
Encounter encounter = ObservationsHandler.addEncounter(
(List) post.get("observations"), (List) post.get("order_uuids"),
patient, encounterTime, "new observation", "ADULTRETURN",
// TODO: Consider using patient's location instead of the root location.
LocationResource.ROOT_UUID, (String) post.get("enterer_uuid"));
SimpleObject simpleObject = new SimpleObject();
populateJsonProperties(encounter, simpleObject);
return simpleObject;
}
/**
* Populates observation and order data for the given encounter, including:
* <ul>
* <li>"timestamp": the encounter datetime in RFC 3339 === ISO 8601 format
* <li>"uuid": the encounter's UUID
* <li>"observations": {@link SimpleObject} that maps concept UUIDs to values
* <li>"order_uuids": unique identifiers of orders executed as part of this encounter.
* </ul>
*/
public static void populateJsonProperties(Encounter encounter, SimpleObject encounterJson) {
encounterJson.put("patient_uuid", encounter.getPatient().getUuid());
encounterJson.put("timestamp", Utils.toIso8601(encounter.getEncounterDatetime()));
encounterJson.put("uuid", encounter.getUuid());
ArrayList<SimpleObject> observations = new ArrayList<>();
List<String> orderUuids = new ArrayList<>();
for (Obs obs : encounter.getObs()) {
Concept concept = obs.getConcept();
if (concept != null &&
concept.getUuid().equals(DbUtil.getOrderExecutedConcept().getUuid())) {
orderUuids.add(obs.getOrder().getUuid());
continue;
}
observations.add(ObservationsHandler.obsToJson(obs));
}
if (!observations.isEmpty()) {
encounterJson.put("observations", observations);
}
if (!orderUuids.isEmpty()) {
encounterJson.put("order_uuids", orderUuids);
}
}
@Override
public String getUri(Object instance) {
// We don't actually use this, but return a relatively sensible value anyway.
OpenmrsObject mrsObject = (OpenmrsObject) instance;
Resource res = getClass().getAnnotation(Resource.class);
return RestConstants.URI_PREFIX + res.name() + "/" + mrsObject.getUuid();
}
}