From 893ced50db8c8af5dc9f478ea1f6233a2a18b9e9 Mon Sep 17 00:00:00 2001 From: Michael Seaton Date: Fri, 28 Jun 2024 08:46:22 -0400 Subject: [PATCH] EA-191 - Support retrieving EMR API configurations via REST (#232) --- .../module/emrapi/EmrApiProperties.java | 15 +- api/src/test/resources/baseTestDataset.xml | 6 + omod/pom.xml | 8 + .../rest/converter/SimpleBeanConverter.java | 89 ++++++++ .../controller/EmrApiConfigController.java | 27 --- .../EmrApiConfigurationController.java | 31 +++ .../EmrApiConfigurationControllerTest.java | 190 ++++++++++++++++++ .../resources/TestingApplicationContext.xml | 29 +++ .../src/test/resources/test-hibernate.cfg.xml | 18 ++ 9 files changed, 385 insertions(+), 28 deletions(-) create mode 100644 omod/src/main/java/org/openmrs/module/emrapi/rest/converter/SimpleBeanConverter.java delete mode 100644 omod/src/main/java/org/openmrs/module/emrapi/web/controller/EmrApiConfigController.java create mode 100644 omod/src/main/java/org/openmrs/module/emrapi/web/controller/EmrApiConfigurationController.java create mode 100644 omod/src/test/java/org/openmrs/module/emrapi/web/controller/EmrApiConfigurationControllerTest.java create mode 100644 omod/src/test/resources/TestingApplicationContext.xml create mode 100644 omod/src/test/resources/test-hibernate.cfg.xml diff --git a/api/src/main/java/org/openmrs/module/emrapi/EmrApiProperties.java b/api/src/main/java/org/openmrs/module/emrapi/EmrApiProperties.java index 823fb4955..ff22ab76a 100644 --- a/api/src/main/java/org/openmrs/module/emrapi/EmrApiProperties.java +++ b/api/src/main/java/org/openmrs/module/emrapi/EmrApiProperties.java @@ -29,8 +29,12 @@ import org.openmrs.Role; import org.openmrs.VisitType; import org.openmrs.module.emrapi.diagnosis.DiagnosisMetadata; +import org.openmrs.module.emrapi.disposition.Disposition; +import org.openmrs.module.emrapi.disposition.DispositionDescriptor; +import org.openmrs.module.emrapi.disposition.DispositionService; import org.openmrs.module.metadatamapping.util.ModuleProperties; import org.openmrs.util.OpenmrsUtil; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @@ -47,6 +51,9 @@ @Component("emrApiProperties") public class EmrApiProperties extends ModuleProperties { + @Autowired + protected DispositionService dispositionService; + @Override public String getMetadataSourceName() { return EmrApiConstants.EMR_METADATA_SOURCE_NAME; @@ -191,7 +198,6 @@ public DiagnosisMetadata getDiagnosisMetadata() { return new DiagnosisMetadata(conceptService, getEmrApiConceptSource()); } - public List getConceptSourcesForDiagnosisSearch() { //The results can very well be cached to reduce calls to database. //however the compatibility requirement to core 1.9.9 do not allow this currently @@ -330,4 +336,11 @@ public Boolean getVisitAssignmentHandlerAdjustEncounterTimeOfDayIfNecessary() { return "TRUE".equalsIgnoreCase(getGlobalProperty(GP_VISIT_ASSIGNMENT_HANDLER_ADJUST_ENCOUNTER_TIME_OF_DAY_IF_NECESSARY, false)); } + public List getDispositions() { + return dispositionService.getDispositions(); + } + + public DispositionDescriptor getDispositionDescriptor() { + return dispositionService.getDispositionDescriptor(); + } } diff --git a/api/src/test/resources/baseTestDataset.xml b/api/src/test/resources/baseTestDataset.xml index 6f8aeb8ad..520368a89 100644 --- a/api/src/test/resources/baseTestDataset.xml +++ b/api/src/test/resources/baseTestDataset.xml @@ -26,6 +26,12 @@ retired = "false" code = "emr.unknownProvider" name = "Unknown Provider" /> + + compile + + ${project.parent.groupId} + ${project.parent.artifactId}-api + ${project.parent.version} + test + test-jar + + ${project.parent.groupId} ${project.parent.artifactId}-api-reporting diff --git a/omod/src/main/java/org/openmrs/module/emrapi/rest/converter/SimpleBeanConverter.java b/omod/src/main/java/org/openmrs/module/emrapi/rest/converter/SimpleBeanConverter.java new file mode 100644 index 000000000..59dc3bbde --- /dev/null +++ b/omod/src/main/java/org/openmrs/module/emrapi/rest/converter/SimpleBeanConverter.java @@ -0,0 +1,89 @@ +package org.openmrs.module.emrapi.rest.converter; + +import org.apache.commons.beanutils.PropertyUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.openmrs.annotation.Handler; +import org.openmrs.module.emrapi.EmrApiProperties; +import org.openmrs.module.emrapi.diagnosis.DiagnosisMetadata; +import org.openmrs.module.emrapi.disposition.Disposition; +import org.openmrs.module.emrapi.disposition.DispositionDescriptor; +import org.openmrs.module.emrapi.disposition.DispositionObs; +import org.openmrs.module.webservices.rest.SimpleObject; +import org.openmrs.module.webservices.rest.web.ConversionUtil; +import org.openmrs.module.webservices.rest.web.representation.CustomRepresentation; +import org.openmrs.module.webservices.rest.web.representation.Representation; +import org.openmrs.module.webservices.rest.web.resource.api.Converter; +import org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription; +import org.openmrs.module.webservices.rest.web.response.ConversionException; +import org.openmrs.module.webservices.rest.web.response.ResourceDoesNotSupportOperationException; + +import java.beans.PropertyDescriptor; +import java.util.Map; + +@Handler(supports = { + EmrApiProperties.class, + DiagnosisMetadata.class, + Disposition.class, + DispositionObs.class, + DispositionDescriptor.class +}, order = 0) +public class SimpleBeanConverter implements Converter { + + private final Log log = LogFactory.getLog(getClass()); + + /** + * @return a resource description that represents a custom representation, or one that represents all bean properties in the class + */ + public DelegatingResourceDescription getResourceDescription(T o, Representation representation) { + if (representation instanceof CustomRepresentation) { + return ConversionUtil.getCustomRepresentationDescription((CustomRepresentation) representation); + } + DelegatingResourceDescription ret = new DelegatingResourceDescription(); + for (PropertyDescriptor pd : PropertyUtils.getPropertyDescriptors(o.getClass())) { + if (pd.getReadMethod() != null && pd.getReadMethod().getDeclaringClass() == o.getClass()) { + String propName = pd.getName(); + ret.addProperty(propName, representation); + } + } + return ret; + } + + @Override + public SimpleObject asRepresentation(T o, Representation rep) throws ConversionException { + SimpleObject ret = new SimpleObject(); + Map props = getResourceDescription(o, rep).getProperties(); + for (String propName : props.keySet()) { + Object value = null; + // Log exception rather than fail if an exception is thrown while trying to retrieve a property + try { + value = PropertyUtils.getProperty(o, propName); + } + catch (Exception e) { + log.debug("Could not get property value " + propName + " from " + o.getClass(), e); + } + ret.put(propName, ConversionUtil.convertToRepresentation(value, props.get(propName).getRep())); + } + return ret; + } + + @Override + public T newInstance(String s) { + throw new ResourceDoesNotSupportOperationException(); + } + + @Override + public T getByUniqueId(String s) { + throw new ResourceDoesNotSupportOperationException(); + } + + @Override + public Object getProperty(T o, String s) throws ConversionException { + throw new ResourceDoesNotSupportOperationException(); + } + + @Override + public void setProperty(Object o, String s, Object o1) throws ConversionException { + throw new ResourceDoesNotSupportOperationException(); + } +} diff --git a/omod/src/main/java/org/openmrs/module/emrapi/web/controller/EmrApiConfigController.java b/omod/src/main/java/org/openmrs/module/emrapi/web/controller/EmrApiConfigController.java deleted file mode 100644 index 4a355668a..000000000 --- a/omod/src/main/java/org/openmrs/module/emrapi/web/controller/EmrApiConfigController.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.openmrs.module.emrapi.web.controller; - -import org.openmrs.module.emrapi.EmrApiProperties; -import org.openmrs.module.webservices.rest.SimpleObject; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; - -@Controller -@RequestMapping(value = "/rest/emrapi/configuration") -public class EmrApiConfigController { - - @Autowired - private EmrApiProperties emrApiProperties; - @RequestMapping(method = RequestMethod.GET) - @ResponseBody - public SimpleObject getEmrApiConfig() { - SimpleObject response = new SimpleObject(); - response.put("admissionEncounterType", emrApiProperties.getAdmissionEncounterType().getUuid()); - response.put("transferWithinHospitalEncounterType", emrApiProperties.getTransferWithinHospitalEncounterType().getUuid()); - response.put("exitFromInpatientEncounterTpye", emrApiProperties.getExitFromInpatientEncounterType().getUuid()); - return response; - } - -} diff --git a/omod/src/main/java/org/openmrs/module/emrapi/web/controller/EmrApiConfigurationController.java b/omod/src/main/java/org/openmrs/module/emrapi/web/controller/EmrApiConfigurationController.java new file mode 100644 index 000000000..df8de93df --- /dev/null +++ b/omod/src/main/java/org/openmrs/module/emrapi/web/controller/EmrApiConfigurationController.java @@ -0,0 +1,31 @@ +package org.openmrs.module.emrapi.web.controller; + +import org.openmrs.module.emrapi.EmrApiProperties; +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.RestUtil; +import org.openmrs.module.webservices.rest.web.representation.Representation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@Controller +@RequestMapping(value = "/rest/emrapi/configuration") +public class EmrApiConfigurationController { + + @Autowired + private EmrApiProperties emrApiProperties; + + @RequestMapping(method = RequestMethod.GET) + @ResponseBody + public SimpleObject getEmrApiConfiguration(HttpServletRequest request, HttpServletResponse response) { + RequestContext context = RestUtil.getRequestContext(request, response, Representation.REF); + return (SimpleObject) ConversionUtil.convertToRepresentation(emrApiProperties, context.getRepresentation()); + } +} diff --git a/omod/src/test/java/org/openmrs/module/emrapi/web/controller/EmrApiConfigurationControllerTest.java b/omod/src/test/java/org/openmrs/module/emrapi/web/controller/EmrApiConfigurationControllerTest.java new file mode 100644 index 000000000..1b24b1089 --- /dev/null +++ b/omod/src/test/java/org/openmrs/module/emrapi/web/controller/EmrApiConfigurationControllerTest.java @@ -0,0 +1,190 @@ +package org.openmrs.module.emrapi.web.controller; + +import org.codehaus.jackson.map.ObjectMapper; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.openmrs.api.ConceptService; +import org.openmrs.module.emrapi.EmrApiProperties; +import org.openmrs.module.emrapi.disposition.DispositionService; +import org.openmrs.module.emrapi.test.ContextSensitiveMetadataTestUtils; +import org.openmrs.module.metadatamapping.api.MetadataMappingService; +import org.openmrs.module.webservices.rest.SimpleObject; +import org.openmrs.module.webservices.rest.web.representation.Representation; +import org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription; +import org.openmrs.module.webservices.rest.web.v1_0.resource.openmrs2_0.LocationResource2_0; +import org.openmrs.web.test.BaseModuleWebContextSensitiveTest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +public class EmrApiConfigurationControllerTest extends BaseModuleWebContextSensitiveTest { + + MockHttpServletRequest request; + MockHttpServletResponse response; + + @Autowired + EmrApiProperties emrApiProperties; + + @Autowired + EmrApiConfigurationController emrApiConfigurationController; + + @Autowired + MetadataMappingService metadataMappingService; + + @Autowired + ConceptService conceptService; + + @Autowired + DispositionService dispositionService; + + @Before + public void setUp() { + executeDataSet("baseTestDataset.xml"); + request = new MockHttpServletRequest(); + response = new MockHttpServletResponse(); + } + + @Test + public void shouldGetAsJson() throws Exception { + request.addParameter("v", "full"); + SimpleObject config = emrApiConfigurationController.getEmrApiConfiguration(request, response); + String jsonString = new ObjectMapper().writeValueAsString(config); + Assert.assertTrue(jsonString.contains("unknownLocation")); + } + + @Test + public void shouldGetDefaultRepresentation() { + SimpleObject config = emrApiConfigurationController.getEmrApiConfiguration(request, response); + assertEquals(46, config.keySet().size()); + assertEquals("org.openmrs.module.emrapi", config.get("metadataSourceName")); + assertEquals("50", config.get("lastViewedPatientSizeLimit").toString()); + Map unknownLocation = mapNode(config, "unknownLocation"); + assertEquals(3, unknownLocation.size()); + assertThat(unknownLocation.keySet(), containsInAnyOrder("uuid", "display", "links")); + assertEquals("Unknown Location", unknownLocation.get("display")); + } + + @Test + public void shouldGetFullRepresentation() { + request.addParameter("v", "full"); + SimpleObject config = emrApiConfigurationController.getEmrApiConfiguration(request, response); + Map unknownLocation = mapNode(config, "unknownLocation"); + DelegatingResourceDescription drd = new LocationResource2_0().getRepresentationDescription(Representation.FULL); + Set expectedProps = drd.getProperties().keySet(); + for (String prop : expectedProps) { + Assert.assertTrue("Expected property: " + prop, unknownLocation.containsKey(prop)); + } + for (int i=1; i<=15; i++) { + assertTrue(unknownLocation.containsKey("address"+i)); + } + assertEquals("Unknown Location", unknownLocation.get("display")); + } + + @Test + public void shouldGetCustomRepresentation() { + request.addParameter("v", "custom:(unknownLocation:(display),admissionEncounterType:full)"); + SimpleObject config = emrApiConfigurationController.getEmrApiConfiguration(request, response); + assertEquals(2, config.size()); + assertThat(config.keySet(), containsInAnyOrder("unknownLocation", "admissionEncounterType")); + assertEquals(1, mapNode(config, "unknownLocation").size()); + assertEquals("Unknown Location", mapNode(config, "unknownLocation").get("display")); + assertEquals("06087111-222-11e3-9c1a-0800200c9a66", mapNode(config, "admissionEncounterType").get("uuid")); + assertEquals("Admission", mapNode(config, "admissionEncounterType").get("name")); + } + + @Test + public void shouldGetDispositions() { + request.addParameter("v", "custom:(dispositions)"); + SimpleObject config = emrApiConfigurationController.getEmrApiConfiguration(request, response); + List> dispositions = listNode(config, "dispositions"); + assertThat(dispositions.size(), equalTo(4)); + for (Map d : dispositions) { + if (d.get("uuid").equals("d2d89630-b698-11e2-9e96-0800200c9a66")) { + assertThat(d.get("name"), equalTo("disposition.death")); + assertThat(d.get("conceptCode"), equalTo("org.openmrs.module.emrapi:Death")); + assertThat(listNode(d, "additionalObs").size(), equalTo(1)); + assertThat(listNode(d, "additionalObs").get(0).get("conceptCode"), equalTo("org.openmrs.module.emrapi:Date of death")); + } + else if (d.get("uuid").equals("66de7f60-b73a-11e2-9e96-0800200c9a66")) { + assertThat(d.get("name"), equalTo("disposition.admit")); + assertThat(d.get("conceptCode"), equalTo("org.openmrs.module.emrapi:Admit to hospital")); + assertThat(listNode(d, "additionalObs").size(), equalTo(0)); + } + else if (d.get("uuid").equals("687d966bb-9c91-4886-b8b0-e63361f495f0")) { + assertThat(d.get("name"), equalTo("disposition.observation")); + assertThat(d.get("conceptCode"), equalTo("org.openmrs.module.emrapi:ED Observation")); + assertThat(listNode(d, "additionalObs").size(), equalTo(0)); + } + else if (d.get("uuid").equals("12129630-b698-11e2-9e96-0800200c9a66")) { + assertThat(d.get("name"), equalTo("disposition.discharge")); + assertThat(d.get("conceptCode"), equalTo("org.openmrs.module.emrapi:Discharged")); + assertThat(listNode(d, "additionalObs").size(), equalTo(0)); + } + else { + Assert.fail("Unexpected disposition uuid: " + d.get("uuid")); + } + } + } + + @Test + public void shouldGetDispositionDescriptor() { + ContextSensitiveMetadataTestUtils.setupDispositionDescriptor(conceptService, dispositionService); + request.addParameter("v", "custom:(dispositionDescriptor:full)"); + SimpleObject config = emrApiConfigurationController.getEmrApiConfiguration(request, response); + Map descriptor = mapNode(config, "dispositionDescriptor"); + assertThat(descriptor.keySet().size(), equalTo(5)); + assertThat(mapNode(descriptor, "dispositionSetConcept", "name").get("name"), equalTo("Disposition Construct")); + assertThat(mapNode(descriptor, "dispositionConcept", "name").get("name"), equalTo("Disposition")); + assertThat(mapNode(descriptor, "admissionLocationConcept", "name").get("name"), equalTo("Admission Location")); + assertThat(mapNode(descriptor, "internalTransferLocationConcept", "name").get("name"), equalTo("Internal Transfer Location")); + assertThat(mapNode(descriptor, "dateOfDeathConcept", "name").get("name"), equalTo("Date of Death")); + } + + @Test + public void shouldGetDiagnosisMetadata() { + ContextSensitiveMetadataTestUtils.setupDiagnosisMetadata(conceptService, emrApiProperties); + request.addParameter("v", "custom:(diagnosisMetadata:full)"); + SimpleObject config = emrApiConfigurationController.getEmrApiConfiguration(request, response); + Map dm = mapNode(config, "diagnosisMetadata"); + assertThat(dm.keySet().size(), equalTo(5)); + assertThat(mapNode(dm, "diagnosisSetConcept", "name").get("name"), equalTo("Visit diagnoses")); + assertThat(mapNode(dm, "codedDiagnosisConcept", "name").get("name"), equalTo("Coded diagnosis")); + assertThat(mapNode(dm, "nonCodedDiagnosisConcept", "name").get("name"), equalTo("Non-coded diagnosis")); + assertThat(mapNode(dm, "diagnosisOrderConcept", "name").get("name"), equalTo("Diagnosis order")); + assertThat(mapNode(dm, "diagnosisCertaintyConcept", "name").get("name"), equalTo("Diagnosis certainty")); + } + + @SuppressWarnings("unchecked") + private Map mapNode(Map o, String... keys) { + Map ret = o; + for (String key : keys) { + ret = (Map) ret.get(key); + } + return ret; + } + + @SuppressWarnings("unchecked") + private List> listNode(Map o, String key) { + return (List>)o.get(key); + } + + private void printAsJson(Object o) { + try { + System.out.println(new ObjectMapper().writeValueAsString(o)); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/omod/src/test/resources/TestingApplicationContext.xml b/omod/src/test/resources/TestingApplicationContext.xml new file mode 100644 index 000000000..8aa187e55 --- /dev/null +++ b/omod/src/test/resources/TestingApplicationContext.xml @@ -0,0 +1,29 @@ + + + + + + + + classpath:hibernate.cfg.xml + classpath:test-hibernate.cfg.xml + + + + + + + + org.openmrs + + + + + + diff --git a/omod/src/test/resources/test-hibernate.cfg.xml b/omod/src/test/resources/test-hibernate.cfg.xml new file mode 100644 index 000000000..7ad80b994 --- /dev/null +++ b/omod/src/test/resources/test-hibernate.cfg.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + +