From 4f3e8ecd078bfde7870dada85f1b2521e9e85048 Mon Sep 17 00:00:00 2001 From: Mark Goodrich Date: Mon, 12 Aug 2024 12:14:03 -0400 Subject: [PATCH] RESTWS-953:Support setting values of type "Location" (#617) --- .../resource/openmrs1_8/ObsResource1_8.java | 136 +++++++++++------- .../openmrs1_9/ObsResource1_9Test.java | 41 +++++- .../module/webservices/rest/web/RestUtil.java | 8 ++ 3 files changed, 134 insertions(+), 51 deletions(-) diff --git a/omod-1.8/src/main/java/org/openmrs/module/webservices/rest/web/v1_0/resource/openmrs1_8/ObsResource1_8.java b/omod-1.8/src/main/java/org/openmrs/module/webservices/rest/web/v1_0/resource/openmrs1_8/ObsResource1_8.java index 00a5900e1..45ca8a132 100644 --- a/omod-1.8/src/main/java/org/openmrs/module/webservices/rest/web/v1_0/resource/openmrs1_8/ObsResource1_8.java +++ b/omod-1.8/src/main/java/org/openmrs/module/webservices/rest/web/v1_0/resource/openmrs1_8/ObsResource1_8.java @@ -26,6 +26,7 @@ import org.openmrs.ConceptNumeric; import org.openmrs.Drug; import org.openmrs.Encounter; +import org.openmrs.Location; import org.openmrs.Obs; import org.openmrs.Patient; import org.openmrs.api.APIException; @@ -35,6 +36,7 @@ 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.RestUtil; 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; @@ -315,6 +317,7 @@ public Object getValue(Obs obs) throws ConversionException { return Context.getLocationService().getLocation(new Integer(obs.getValueText())); } catch (NumberFormatException e) { + // TDOO; we really shouldn't be supporting two ways of storing a location obs, should only use the location id return Context.getLocationService().getLocationByUuid(obs.getValueText()); } } else { @@ -394,23 +397,28 @@ public static void setConcept(Obs obs, Object value) { @PropertySetter("value") public static void setValue(Obs obs, Object value) throws ParseException, ConversionException, IOException { if (value != null) { + + // special case for complex obs 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) - - //We want clients to be able to fetch a coded value in one rest call - //and set the returned payload as the obs value + return; + } + + // special case for data type coded (setValueAsString is not implemented for coded obs (in core)) + if (obs.getConcept().getDatatype().isCoded()) { + // We want clients to be able to fetch a coded value in one rest call + // and set the returned payload as the obs value + // (ie support setting based on posting the entire REST rep or just the concept uuid) if (value instanceof Map) { Object uuid = ((Map) value).get(RestConstants.PROPERTY_UUID); if (uuid != null) { value = uuid.toString(); } } - + Concept valueCoded = (Concept) ConversionUtil.convert(value, Concept.class); if (valueCoded == null) { //try checking if this this is value drug @@ -421,58 +429,86 @@ public static void setValue(Obs obs, Object value) throws ParseException, Conver } else { throw new ObjectNotFoundException(obs.getConcept().getName().getName() + ":" + value.toString()); } - + } 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); - } + return; + } + + // special case for Location + String potentialLocationUuid = null; + + // if this is a representation of an object, get the uuid property as potential location uuid + if (value instanceof Map) { + Object uuid = ((Map) value).get(RestConstants.PROPERTY_UUID); + if (uuid != null) { + potentialLocationUuid = uuid.toString(); + } + } + else { + // otherwise, we will test if the value itself is a location uuid + potentialLocationUuid = value.toString(); + } + + // if there is a potential uuid, see if there is a matching location, and,if so, set the value text as the primary key + if (RestUtil.isUuid(potentialLocationUuid)) { + Location location = Context.getLocationService().getLocationByUuid(potentialLocationUuid); + if (location != null) { + obs.setValueText(location.getLocationId().toString()); + obs.setComment("org.openmrs.Location"); + return; + } + } + + // handle all other types using obs.setValueAsString after special conversions for numeric and boolean + 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 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(); + } + } 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 trueValues = Arrays.asList("true", "1", "on", "yes"); + List 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 (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 trueValues = Arrays.asList("true", "1", "on", "yes"); - List 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"); - } + + 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()); } + obs.setValueAsString(value.toString()); + + } else { throw new APIException("The value for an observation cannot be null"); } diff --git a/omod-1.9/src/test/java/org/openmrs/module/webservices/rest/web/v1_0/resource/openmrs1_9/ObsResource1_9Test.java b/omod-1.9/src/test/java/org/openmrs/module/webservices/rest/web/v1_0/resource/openmrs1_9/ObsResource1_9Test.java index ec560b4ce..91b15d7f6 100644 --- a/omod-1.9/src/test/java/org/openmrs/module/webservices/rest/web/v1_0/resource/openmrs1_9/ObsResource1_9Test.java +++ b/omod-1.9/src/test/java/org/openmrs/module/webservices/rest/web/v1_0/resource/openmrs1_9/ObsResource1_9Test.java @@ -234,7 +234,46 @@ public void setValue_shouldReturnDrug() throws Exception { ObsResource1_8.setValue(obs, drugUuid); assertEquals(drug, new ObsResource1_8().getValue(obs)); } - + + @Test + public void setValue_shouldSupportPassingInFullConceptRep() throws Exception { + Obs obs = new Obs(); + obs.setConcept(Context.getConceptService().getConceptByUuid("89ca642a-dab6-4f20-b712-e12ca4fc6d36")); + Concept valueCoded = Context.getConceptService().getConceptByUuid("32d3611a-6699-4d52-823f-b4b788bac3e3"); + ObsResource1_8.setValue(obs, ConversionUtil.convertToRepresentation(valueCoded, Representation.FULL)); + assertEquals(valueCoded, new ObsResource1_8().getValue(obs)); + } + + @Test + public void setValue_shouldReturnLocation() throws Exception { + Obs obs = new Obs(); + Concept concept = Context.getConceptService().getConceptByUuid("96408258-000b-424e-af1a-403919332938"); + obs.setConcept(concept); + String locationUuid = "8d6c993e-c2cc-11de-8d13-0010c6dffd0f"; + Location location = Context.getLocationService().getLocationByUuid(locationUuid); + ObsResource1_8.setValue(obs, locationUuid); + assertEquals(location, new ObsResource1_8().getValue(obs)); + } + + @Test + public void setValue_shouldSupportPassingInFullLocationRep() throws Exception { + Obs obs = new Obs(); + obs.setConcept(Context.getConceptService().getConceptByUuid("96408258-000b-424e-af1a-403919332938")); + String locationUuid = "8d6c993e-c2cc-11de-8d13-0010c6dffd0f"; + Location location = Context.getLocationService().getLocationByUuid(locationUuid); + ObsResource1_8.setValue(obs, ConversionUtil.convertToRepresentation(location, Representation.FULL)); + assertEquals(location, new ObsResource1_8().getValue(obs)); + } + + @Test + public void setValue_shouldReturnValueText() throws Exception { + Obs obs = new Obs(); + Concept concept = Context.getConceptService().getConceptByUuid("96408258-000b-424e-af1a-403919332938"); + obs.setConcept(concept); + ObsResource1_8.setValue(obs, "cheesecake"); + assertEquals("cheesecake", new ObsResource1_8().getValue(obs)); + } + @Test public void setValue_shouldReturnUuidForConceptFalse() throws Exception { Obs obs = new Obs(); diff --git a/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/RestUtil.java b/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/RestUtil.java index 1b08aa3d4..5b43e48c4 100644 --- a/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/RestUtil.java +++ b/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/RestUtil.java @@ -26,6 +26,7 @@ import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; +import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -64,6 +65,9 @@ public class RestUtil implements GlobalPropertyListener { private static Log log = LogFactory.getLog(RestUtil.class); private static boolean contextEnabled = true; + + private static final Pattern UUID_REGEX = + Pattern.compile("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"); /** * Looks up the admin defined global property for the system limit @@ -936,4 +940,8 @@ public static Class getSupportedClass(Resource resource) { .supportedClass(); } } + + public static boolean isUuid(String str) { + return StringUtils.isNotBlank(str) && UUID_REGEX.matcher(str).matches(); + } }