/** * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. * * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS * graphic logo is a trademark of OpenMRS Inc. */ package org.openmrs.module.webservices.rest.web.v1_0.resource.openmrs1_8; import org.apache.commons.lang.StringUtils; import org.openmrs.Concept; import org.openmrs.ConceptNumeric; import org.openmrs.Drug; import org.openmrs.Encounter; import org.openmrs.Obs; import org.openmrs.Patient; import org.openmrs.api.APIException; import org.openmrs.api.ObsService; import org.openmrs.api.context.Context; import org.openmrs.module.webservices.rest.SimpleObject; import org.openmrs.module.webservices.rest.web.ConversionUtil; import org.openmrs.module.webservices.rest.web.RequestContext; import org.openmrs.module.webservices.rest.web.RestConstants; import org.openmrs.module.webservices.rest.web.annotation.PropertyGetter; import org.openmrs.module.webservices.rest.web.annotation.PropertySetter; import org.openmrs.module.webservices.rest.web.annotation.Resource; import org.openmrs.module.webservices.rest.web.api.RestService; import org.openmrs.module.webservices.rest.web.representation.DefaultRepresentation; import org.openmrs.module.webservices.rest.web.representation.FullRepresentation; import org.openmrs.module.webservices.rest.web.representation.Representation; import org.openmrs.module.webservices.rest.web.resource.api.PageableResult; import org.openmrs.module.webservices.rest.web.resource.api.Uploadable; import org.openmrs.module.webservices.rest.web.resource.impl.DataDelegatingCrudResource; import org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription; import org.openmrs.module.webservices.rest.web.resource.impl.EmptySearchResult; import org.openmrs.module.webservices.rest.web.resource.impl.NeedsPaging; import org.openmrs.module.webservices.rest.web.response.ConversionException; import org.openmrs.module.webservices.rest.web.response.IllegalRequestException; import org.openmrs.module.webservices.rest.web.response.ObjectNotFoundException; import org.openmrs.module.webservices.rest.web.response.ResponseException; import org.openmrs.obs.ComplexData; import org.springframework.web.multipart.MultipartFile; import javax.xml.bind.DatatypeConverter; import java.io.ByteArrayInputStream; import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Set; /** * {@link Resource} for Obs, supporting standard CRUD operations */ @Resource(name = RestConstants.VERSION_1 + "/obs", order = 2, supportedClass = Obs.class, supportedOpenmrsVersions = { "1.8.*" }) public class ObsResource1_8 extends DataDelegatingCrudResource<Obs> implements Uploadable { /** * @see org.openmrs.module.webservices.rest.web.resource.impl.BaseDelegatingResource#delete(java.lang.Object, * java.lang.String, org.openmrs.module.webservices.rest.web.RequestContext) */ @Override protected void delete(Obs delegate, String reason, RequestContext context) throws ResponseException { if (delegate.isVoided()) { // DELETE is idempotent, so we return success here return; } Context.getObsService().voidObs(delegate, reason); } /** * @see org.openmrs.module.webservices.rest.web.resource.impl.BaseDelegatingResource#getByUniqueId(java.lang.String) */ @Override public Obs getByUniqueId(String uniqueId) { return Context.getObsService().getObsByUuid(uniqueId); } /** * @see org.openmrs.module.webservices.rest.web.resource.impl.BaseDelegatingResource#getRepresentationDescription(org.openmrs.module.webservices.rest.web.representation.Representation) */ @Override public DelegatingResourceDescription getRepresentationDescription(Representation rep) { if (rep instanceof DefaultRepresentation) { // TODO how to handle valueCodedName? DelegatingResourceDescription description = new DelegatingResourceDescription(); description.addProperty("uuid"); description.addProperty("display"); description.addProperty("concept", Representation.REF); description.addProperty("person", Representation.REF); description.addProperty("obsDatetime"); description.addProperty("accessionNumber"); description.addProperty("obsGroup", Representation.REF); description.addProperty("valueCodedName", Representation.REF); description.addProperty("groupMembers"); description.addProperty("comment"); description.addProperty("location", Representation.REF); description.addProperty("order", Representation.REF); description.addProperty("encounter", Representation.REF); description.addProperty("voided"); description.addProperty("value"); description.addProperty("valueModifier"); description.addSelfLink(); description.addLink("full", ".?v=" + RestConstants.REPRESENTATION_FULL); return description; } else if (rep instanceof FullRepresentation) { // TODO how to handle valueCodedName? DelegatingResourceDescription description = new DelegatingResourceDescription(); description.addProperty("uuid"); description.addProperty("display"); description.addProperty("concept"); description.addProperty("person", Representation.REF); description.addProperty("obsDatetime"); description.addProperty("accessionNumber"); description.addProperty("obsGroup"); description.addProperty("valueCodedName"); description.addProperty("groupMembers", Representation.FULL); description.addProperty("comment"); description.addProperty("location"); description.addProperty("order"); description.addProperty("encounter"); description.addProperty("voided"); description.addProperty("auditInfo"); description.addProperty("value"); description.addProperty("valueModifier"); description.addSelfLink(); return description; } return null; } /** * @see org.openmrs.module.webservices.rest.web.resource.impl.BaseDelegatingResource#getCreatableProperties() */ @Override public DelegatingResourceDescription getCreatableProperties() { DelegatingResourceDescription description = new DelegatingResourceDescription(); description.addRequiredProperty("person"); description.addRequiredProperty("obsDatetime"); description.addRequiredProperty("concept"); description.addProperty("location"); description.addProperty("order"); description.addProperty("encounter"); description.addProperty("accessionNumber"); description.addProperty("groupMembers"); description.addProperty("valueCodedName"); description.addProperty("comment"); description.addProperty("value"); description.addProperty("valueModifier"); return description; } /** * @see org.openmrs.module.webservices.rest.web.resource.impl.BaseDelegatingResource#newDelegate() */ @Override public Obs newDelegate() { return new Obs(); } /** * @see org.openmrs.module.webservices.rest.web.resource.impl.BaseDelegatingResource#purge(java.lang.Object, * org.openmrs.module.webservices.rest.web.RequestContext) */ @Override public void purge(Obs delegate, RequestContext context) throws ResponseException { Context.getObsService().purgeObs(delegate); } /** * @see org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceHandler#save(java.lang.Object) */ @Override public Obs save(Obs delegate) { Obs savedObs = Context.getObsService().saveObs(delegate, "REST web service"); return Context.getObsService().getObs(savedObs.getId()); } /** * Display string for Obs * * @param obs * @return String ConceptName = value */ @PropertyGetter("display") public String getDisplayString(Obs obs) { if (obs.getConcept() == null) return ""; return obs.getConcept().getName() + ": " + obs.getValueAsString(Context.getLocale()); } /** * Retrives the Obs Value as string * * @param obs * @return */ @PropertyGetter("value") public static Object getValue(Obs obs) throws ConversionException { if (obs.isComplex()) { //Note that complex obs value is handled by ObsComplexValueController1_8 SimpleObject so = new SimpleObject(); so.put("display", "raw file"); SimpleObject links = new SimpleObject(); links.put("rel", "self"); links.put("uri", new ObsResource1_8().getUri(obs) + "/value"); so.put("links", links); return so; } if (obs.isObsGrouping()) return null; if (obs.getValueDatetime() != null) { return ConversionUtil.convert(obs.getValueDatetime(), Date.class); } if (obs.getValueDrug() != null) { return obs.getValueDrug(); } if (obs.getValueCoded() != null) { return obs.getValueCoded(); } if (obs.getValueComplex() != null) { return obs.getValueComplex(); } if (obs.getValueText() != null) { if ("org.openmrs.Location".equals(obs.getComment())) { // string first to make it null-safe try { return Context.getLocationService().getLocation(new Integer(obs.getValueText())); } catch (NumberFormatException e) { return Context.getLocationService().getLocationByUuid(obs.getValueText()); } } else { return obs.getValueText(); } } if (obs.getValueNumeric() != null) { return obs.getValueNumeric(); } return null; } /** * Sets the members of an obs group * * @param obsGroup the obs group whose members to set * @param members the members to set */ @PropertySetter("groupMembers") public static void setGroupMembers(Obs obsGroup, Set<Obs> members) { for (Obs member : members) { member.setObsGroup(obsGroup); } obsGroup.setGroupMembers(members); } /** * Checks if there are more than one obs in GroupMembers and converts into a DEFAULT * representation * * @param obs * @return Object * @throws ConversionException */ @PropertyGetter("groupMembers") public static Object getGroupMembers(Obs obs) throws ConversionException { if (obs.getGroupMembers() != null && obs.getGroupMembers().size() > 0) { return obs.getGroupMembers(); } return null; } /** * Annotated setter for Concept * * @param obs * @param value */ @PropertySetter("concept") public static void setConcept(Obs obs, Object value) { obs.setConcept(Context.getConceptService().getConceptByUuid((String) value)); } /** * Annotated setter for ConceptValue * * @param obs * @param value * @throws ParseException * @throws ConversionException * @should return uuid for foncept true * @should return uuid for concept false * @should throw exception on unexpected value * @should return uuid for primitive true * @should return uuid for primitive false */ @PropertySetter("value") public static void setValue(Obs obs, Object value) throws ParseException, ConversionException, IOException { if (value != null) { if (obs.isComplex()) { byte[] bytes = DatatypeConverter.parseBase64Binary(value.toString()); ComplexData complexData = new ComplexData(obs.getUuid() + ".raw", new ByteArrayInputStream(bytes)); obs.setComplexData(complexData); } else if (obs.getConcept().getDatatype().isCoded()) { // setValueAsString is not implemented for coded obs (in core) Concept valueCoded = (Concept) ConversionUtil.convert(value, Concept.class); if (valueCoded == null) { //try checking if this this is value drug Drug valueDrug = (Drug) ConversionUtil.convert(value, Drug.class); if (valueDrug != null) { obs.setValueCoded(valueDrug.getConcept()); obs.setValueDrug(valueDrug); } else { throw new ObjectNotFoundException(); } } else { obs.setValueCoded(valueCoded); } } else { if (obs.getConcept().isNumeric()) { //get the actual persistent object rather than the hibernate proxy ConceptNumeric concept = Context.getConceptService().getConceptNumeric(obs.getConcept().getId()); String units = concept.getUnits(); if (StringUtils.isNotBlank(units)) { String originalValue = value.toString().trim(); if (originalValue.endsWith(units)) value = originalValue.substring(0, originalValue.indexOf(units)).trim(); else { //check that that this value has no invalid units try { Double.parseDouble(originalValue); } catch (NumberFormatException e) { throw new APIException(originalValue + " has invalid units", e); } } } } else if (obs.getConcept().getDatatype().isBoolean()) { if (value instanceof Concept) { value = ((Concept) value).getUuid(); } if (value.equals(Context.getConceptService().getTrueConcept().getUuid())) { value = true; } else if (value.equals(Context.getConceptService().getFalseConcept().getUuid())) { value = false; } else if (!value.getClass().isAssignableFrom(Boolean.class)) { List<String> trueValues = Arrays.asList("true", "1", "on", "yes"); List<String> falseValues = Arrays.asList("false", "0", "off", "no"); String val = value.toString().trim().toLowerCase(); if (trueValues.contains(val)) { value = Boolean.TRUE; } else if (falseValues.contains(val)) { value = Boolean.FALSE; } if (!(Boolean.TRUE.equals(value) || Boolean.FALSE.equals(value))) { throw new ConversionException("Unexpected value: " + value + " set as the value of boolean. " + trueValues + falseValues + ", ConceptService.getTrueConcept or " + ", ConceptService.getFalseConcept expected"); } } } obs.setValueAsString(value.toString()); } } else throw new APIException("The value for an observation cannot be null"); } /** * Gets obs by patient or encounter (paged according to context if necessary) only if a patient * or encounter parameter exists respectively in the request set on the {@link RequestContext} * otherwise searches for obs that match the specified query * * @param context * @see org.openmrs.module.webservices.rest.web.resource.impl.DelegatingCrudResource#doSearch(org.openmrs.module.webservices.rest.web.RequestContext) */ @Override protected PageableResult doSearch(RequestContext context) { String patientUuid = context.getRequest().getParameter("patient"); if (patientUuid != null) { Patient patient = ((PatientResource1_8) Context.getService(RestService.class).getResourceBySupportedClass( Patient.class)).getByUniqueId(patientUuid); if (patient == null) return new EmptySearchResult(); List<Obs> obs = Context.getObsService().getObservationsByPerson(patient); return new NeedsPaging<Obs>(obs, context); } String encounterUuid = context.getRequest().getParameter("encounter"); if (encounterUuid != null) { Encounter enc = ((EncounterResource1_8) Context.getService(RestService.class).getResourceBySupportedClass( Encounter.class)).getByUniqueId(encounterUuid); if (enc == null) return new EmptySearchResult(); List<Obs> obs = new ArrayList<Obs>(enc.getAllObs()); return new NeedsPaging<Obs>(obs, context); } return new NeedsPaging<Obs>(Context.getObsService().getObservations(context.getParameter("q")), context); } @Override public Object upload(MultipartFile file, RequestContext context) throws ResponseException, IOException { String json = context.getParameter("json"); if (json == null) { throw new IllegalRequestException("Obs metadata must be included in a request parameter named 'json'."); } SimpleObject object = SimpleObject.parseJson(json); Obs obs = convert(object); if (!obs.isComplex()) { throw new IllegalRequestException("Complex concept must be set in order to create a complex obs with data."); } ObsService obsService = Context.getObsService(); ComplexData complexData = new ComplexData(file.getOriginalFilename(), new ByteArrayInputStream(file.getBytes())); obs.setComplexData(complexData); obs = obsService.saveObs(obs, null); SimpleObject ret = (SimpleObject) ConversionUtil.convertToRepresentation(obs, Representation.DEFAULT); return ret; } }