Skip to content

Commit

Permalink
FM2-411: Added support for instance level $everything operation on Pa…
Browse files Browse the repository at this point in the history
…tients in R4 (#360)
  • Loading branch information
Medhavi-16 authored Jul 22, 2021
1 parent 7c48ebd commit 4be5f0e
Show file tree
Hide file tree
Showing 9 changed files with 208 additions and 3 deletions.
2 changes: 2 additions & 0 deletions api/src/main/java/org/openmrs/module/fhir2/FhirConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -307,4 +307,6 @@ private FhirConstants() {
public static final String LASTN_OBSERVATION_SEARCH_HANDLER = "lastn.observation.search.handler";

public static final String LASTN_ENCOUNTERS_SEARCH_HANDLER = "lastn.encounters.search.handler";

public static final String EVERYTHING_SEARCH_HANDLER = "everything.search.handler";
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.TokenParam;
import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.Patient;
import org.openmrs.PatientIdentifierType;
Expand All @@ -41,4 +42,6 @@ IBundleProvider searchForPatients(StringAndListParam name, StringAndListParam gi
TokenAndListParam deceased, StringAndListParam city, StringAndListParam state, StringAndListParam postalCode,
StringAndListParam country, TokenAndListParam id, DateRangeParam lastUpdated, SortSpec sort,
HashSet<Include> revIncludes);

IBundleProvider getPatientEverything(TokenParam identifier);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.TokenParam;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
Expand Down Expand Up @@ -106,4 +107,26 @@ public IBundleProvider searchForPatients(StringAndListParam name, StringAndListP

return searchQuery.getQueryResults(theParams, dao, translator, searchQueryInclude);
}

@Override
@Transactional(readOnly = true)
public IBundleProvider getPatientEverything(TokenParam patientId) {
SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.EVERYTHING_SEARCH_HANDLER, "")
.addParameter(FhirConstants.COMMON_SEARCH_HANDLER, FhirConstants.ID_PROPERTY,
new TokenAndListParam().addAnd(patientId));

HashSet<Include> revIncludes = new HashSet<>();

revIncludes.add(new Include(FhirConstants.OBSERVATION + ":" + FhirConstants.INCLUDE_PATIENT_PARAM));
revIncludes.add(new Include(FhirConstants.ALLERGY_INTOLERANCE + ":" + FhirConstants.INCLUDE_PATIENT_PARAM));
revIncludes.add(new Include(FhirConstants.DIAGNOSTIC_REPORT + ":" + FhirConstants.INCLUDE_PATIENT_PARAM));
revIncludes.add(new Include(FhirConstants.ENCOUNTER + ":" + FhirConstants.INCLUDE_PATIENT_PARAM));
revIncludes.add(new Include(FhirConstants.MEDICATION_REQUEST + ":" + FhirConstants.INCLUDE_PATIENT_PARAM));
revIncludes.add(new Include(FhirConstants.SERVICE_REQUEST + ":" + FhirConstants.INCLUDE_PATIENT_PARAM));
revIncludes.add(new Include(FhirConstants.PROCEDURE_REQUEST + ":" + FhirConstants.INCLUDE_PATIENT_PARAM));

theParams.addParameter(FhirConstants.REVERSE_INCLUDE_SEARCH_HANDLER, revIncludes);

return searchQuery.getQueryResults(theParams, dao, translator, searchQueryInclude);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
package org.openmrs.module.fhir2.api.search;

import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.openmrs.Auditable;
import org.openmrs.OpenmrsObject;
import org.openmrs.module.fhir2.FhirConstants;
import org.openmrs.module.fhir2.api.FhirGlobalPropertyService;
import org.openmrs.module.fhir2.api.dao.FhirDao;
import org.openmrs.module.fhir2.api.search.param.SearchParameterMap;
Expand All @@ -37,10 +39,20 @@ public class SearchQuery<T extends OpenmrsObject & Auditable, U extends IBaseRes
*
* @param theParams search params.
* @param dao generic dao
* @param translator generic translator
* @param translator generic translator In case of $everything operation, package the results in
* SimpleBundleProvider to include count of _include and _revinclude resources in the
* total resources count and prevent paging
* @return IBundleProvider
*/
public IBundleProvider getQueryResults(SearchParameterMap theParams, O dao, V translator, W searchQueryInclude) {
if (!theParams.getParameters(FhirConstants.EVERYTHING_SEARCH_HANDLER).isEmpty()) {
SimpleBundleProvider result = new SimpleBundleProvider(
new SearchQueryBundleProvider<>(theParams, dao, translator, globalPropertyService, searchQueryInclude)
.getAllResources());

result.setPreferredPageSize(result.size());
return result;
}
return new SearchQueryBundleProvider<>(theParams, dao, translator, globalPropertyService, searchQueryInclude);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
import java.util.List;

import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.Delete;
import ca.uhn.fhir.rest.annotation.History;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.IncludeParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OptionalParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.ResourceParam;
Expand All @@ -34,6 +36,7 @@
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
Expand Down Expand Up @@ -143,4 +146,22 @@ public IBundleProvider searchPatients(@OptionalParam(name = Patient.SP_NAME) Str
return patientService.searchForPatients(name, given, family, identifier, gender, birthDate, deathDate, deceased,
city, state, postalCode, country, id, lastUpdated, sort, revIncludes);
}

/**
* The $everything operation fetches all the information related the specified patient
*
* @param patientId The id of the patient
* @return a bundle of resources which reference to or are referenced from the patient
*/
@Operation(name = "everything", idempotent = true, type = Patient.class, bundleType = BundleTypeEnum.SEARCHSET)
public IBundleProvider getPatientEverything(@IdParam IdType patientId) {

if (patientId == null || patientId.getIdPart() == null || patientId.getIdPart().isEmpty()) {
return null;
}

TokenParam patientReference = new TokenParam().setValue(patientId.getIdPart());

return patientService.getPatientEverything(patientReference);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,43 @@ public void searchForPatients_shouldNotAddRelatedResourcesToResultListForEmptyRe
assertThat(resultList.size(), greaterThanOrEqualTo(1));
}

@Test
public void getPatientEverything_shouldReturnAllInformationAboutSpecifiedPatient() {
TokenParam patientId = new TokenParam().setValue(PATIENT_UUID);

SearchParameterMap theParams = new SearchParameterMap()
.addParameter(FhirConstants.EVERYTHING_SEARCH_HANDLER, "")
.addParameter(FhirConstants.COMMON_SEARCH_HANDLER, FhirConstants.ID_PROPERTY, patientId);

HashSet<Include> revIncludes = new HashSet<>();

revIncludes.add(new Include(FhirConstants.OBSERVATION + ":" + FhirConstants.INCLUDE_PATIENT_PARAM));
revIncludes.add(new Include(FhirConstants.ALLERGY_INTOLERANCE + ":" + FhirConstants.INCLUDE_PATIENT_PARAM));
revIncludes.add(new Include(FhirConstants.DIAGNOSTIC_REPORT + ":" + FhirConstants.INCLUDE_PATIENT_PARAM));
revIncludes.add(new Include(FhirConstants.ENCOUNTER + ":" + FhirConstants.INCLUDE_PATIENT_PARAM));
revIncludes.add(new Include(FhirConstants.MEDICATION_REQUEST + ":" + FhirConstants.INCLUDE_PATIENT_PARAM));
revIncludes.add(new Include(FhirConstants.SERVICE_REQUEST + ":" + FhirConstants.INCLUDE_PATIENT_PARAM));
revIncludes.add(new Include(FhirConstants.PROCEDURE_REQUEST + ":" + FhirConstants.INCLUDE_PATIENT_PARAM));

theParams.addParameter(FhirConstants.REVERSE_INCLUDE_SEARCH_HANDLER, revIncludes);

when(dao.getSearchResultUuids(any())).thenReturn(Collections.singletonList(PATIENT_UUID));
when(dao.getSearchResults(any(), any())).thenReturn(Collections.singletonList(patient));
when(searchQuery.getQueryResults(any(), any(), any(), any())).thenReturn(
new SearchQueryBundleProvider<>(theParams, dao, patientTranslator, globalPropertyService, searchQueryInclude));

when(patientTranslator.toFhirResource(patient)).thenReturn(fhirPatient);

IBundleProvider results = patientService.getPatientEverything(patientId);

List<IBaseResource> resultList = get(results);

assertThat(results, notNullValue());
assertThat(resultList, not(empty()));
assertThat(resultList.size(), greaterThanOrEqualTo(1));

}

private List<IBaseResource> get(IBundleProvider results) {
return results.getResources(0, 10);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ private List<Patient> get(IBundleProvider results) {
.map(it -> (Patient) it).collect(Collectors.toList());
}

private List<IBaseResource> getAllResources(IBundleProvider results) {
return results.getAllResources();
}

@Test
public void searchForPatients_shouldSearchForPatientsByName() {
SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.NAME_SEARCH_HANDLER, "name",
Expand Down Expand Up @@ -1154,4 +1158,34 @@ public void shouldReturnCollectionOfPatientsSortedByCountry() {
}
}

@Test
public void searchForPatient_shouldReturnPatientEverything() {
TokenAndListParam patientId = new TokenAndListParam().addAnd(new TokenParam().setValue(PATIENT_OTHER2_UUID));

SearchParameterMap theParams = new SearchParameterMap()
.addParameter(FhirConstants.EVERYTHING_SEARCH_HANDLER, "")
.addParameter(FhirConstants.COMMON_SEARCH_HANDLER, FhirConstants.ID_PROPERTY, patientId);

HashSet<Include> revIncludes = new HashSet<>();

revIncludes.add(new Include(FhirConstants.OBSERVATION + ":" + FhirConstants.INCLUDE_PATIENT_PARAM));
revIncludes.add(new Include(FhirConstants.ALLERGY_INTOLERANCE + ":" + FhirConstants.INCLUDE_PATIENT_PARAM));
revIncludes.add(new Include(FhirConstants.DIAGNOSTIC_REPORT + ":" + FhirConstants.INCLUDE_PATIENT_PARAM));
revIncludes.add(new Include(FhirConstants.ENCOUNTER + ":" + FhirConstants.INCLUDE_PATIENT_PARAM));
revIncludes.add(new Include(FhirConstants.MEDICATION_REQUEST + ":" + FhirConstants.INCLUDE_PATIENT_PARAM));
revIncludes.add(new Include(FhirConstants.SERVICE_REQUEST + ":" + FhirConstants.INCLUDE_PATIENT_PARAM));
revIncludes.add(new Include(FhirConstants.PROCEDURE_REQUEST + ":" + FhirConstants.INCLUDE_PATIENT_PARAM));

theParams.addParameter(FhirConstants.REVERSE_INCLUDE_SEARCH_HANDLER, revIncludes);

IBundleProvider results = search(theParams);

assertThat(results, notNullValue());
assertThat(results.size(), equalTo(15));

List<IBaseResource> resultList = getAllResources(results);

assertThat(resultList.size(), equalTo(15));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.when;
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
Expand Down Expand Up @@ -58,7 +59,6 @@
import org.openmrs.module.fhir2.FhirConstants;
import org.openmrs.module.fhir2.api.FhirPatientService;
import org.openmrs.module.fhir2.providers.BaseFhirProvenanceResourceTest;
import org.openmrs.module.fhir2.providers.r3.MockIBundleProvider;

@RunWith(MockitoJUnitRunner.class)
public class PatientFhirResourceProviderTest extends BaseFhirProvenanceResourceTest<Patient> {
Expand Down Expand Up @@ -528,7 +528,47 @@ public void searchForPatients_shouldNotAddResourcesForEmptyRevInclude() {
assertThat(((Patient) resultList.iterator().next()).getId(), equalTo(PATIENT_UUID));
}

@Test
public void searchForPatients_shouldReturnPatientEverything() {
when(patientService.getPatientEverything(any()))
.thenReturn(new MockIBundleProvider<>(Collections.singletonList(patient), 10, 1));

IBundleProvider results = resourceProvider.getPatientEverything(new IdType(PATIENT_UUID));

List<IBaseResource> resultList = getAllResources(results);

assertThat(resultList, notNullValue());
assertThat(resultList.size(), equalTo(1));
assertThat(resultList.get(0).fhirType(), equalTo(FhirConstants.PATIENT));
assertThat(((Patient) resultList.iterator().next()).getId(), equalTo(PATIENT_UUID));
}

@Test
public void searchForPatients_shouldReturnNullForPatientEverythingWhenIdParamIsMissing() {
IBundleProvider results = resourceProvider.getPatientEverything(null);

assertThat(results, nullValue());
}

@Test
public void searchForPatients_shouldReturnNullForPatientEverythingWhenIdPartIsMissingInIdParam() {
IBundleProvider results = resourceProvider.getPatientEverything(new IdType());

assertThat(results, nullValue());
}

@Test
public void searchForPatients_shouldReturnNullPatientEverythingWhenIdPartIsEmptyInIdParam() {
IBundleProvider results = resourceProvider.getPatientEverything(new IdType(""));

assertThat(results, nullValue());
}

private List<IBaseResource> getResources(IBundleProvider result) {
return result.getResources(0, 10);
}

private List<IBaseResource> getAllResources(IBundleProvider result) {
return result.getAllResources();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.TokenParam;
import lombok.AccessLevel;
import lombok.Getter;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.hamcrest.MatcherAssert;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
Expand All @@ -63,7 +65,6 @@
import org.openmrs.module.fhir2.FhirConstants;
import org.openmrs.module.fhir2.api.FhirPatientService;
import org.openmrs.module.fhir2.api.util.FhirUtils;
import org.openmrs.module.fhir2.providers.r3.MockIBundleProvider;
import org.springframework.mock.web.MockHttpServletResponse;

@RunWith(MockitoJUnitRunner.class)
Expand Down Expand Up @@ -105,6 +106,9 @@ public class PatientFhirResourceProviderWebTest extends BaseFhirR4ResourceProvid
@Captor
private ArgumentCaptor<HashSet<Include>> includeArgumentCaptor;

@Captor
private ArgumentCaptor<TokenParam> tokenCaptor;

@Before
public void setup() throws ServletException {
resourceProvider = new PatientFhirResourceProvider();
Expand Down Expand Up @@ -772,4 +776,33 @@ public void deletePatient_shouldDeletePatient() throws Exception {
assertThat(response.getContentType(), equalTo(FhirMediaTypes.JSON.toString()));
}

@Test
public void getPatientEverything_shouldHandlePatientId() throws Exception {
verifyEverythingOperation("/Patient/" + PATIENT_UUID + "/$everything?");

verify(patientService).getPatientEverything(tokenCaptor.capture());

assertThat(tokenCaptor.getValue(), notNullValue());
assertThat(tokenCaptor.getValue().getValue(), equalTo(PATIENT_UUID));
}

private void verifyEverythingOperation(String uri) throws Exception {
Patient patient = new Patient();
patient.setId(PATIENT_UUID);

when(patientService.getPatientEverything(any()))
.thenReturn(new MockIBundleProvider<>(Collections.singletonList(patient), 10, 1));

MockHttpServletResponse response = get(uri).accept(FhirMediaTypes.JSON).go();

MatcherAssert.assertThat(response, isOk());
MatcherAssert.assertThat(response.getContentType(), equalTo(FhirMediaTypes.JSON.toString()));

Bundle results = readBundleResponse(response);
assertThat(results.getEntry(), notNullValue());
assertThat(results.getEntry(), not(empty()));
assertThat(results.getEntry().get(0).getResource(), notNullValue());
assertThat(results.getEntry().get(0).getResource().getIdElement().getIdPart(), equalTo(PATIENT_UUID));
}

}

0 comments on commit 4be5f0e

Please sign in to comment.