diff --git a/api/src/main/java/org/openmrs/module/clientregistry/ClientRegistryConfig.java b/api/src/main/java/org/openmrs/module/clientregistry/ClientRegistryConfig.java index 884fab2..af0b0e6 100644 --- a/api/src/main/java/org/openmrs/module/clientregistry/ClientRegistryConfig.java +++ b/api/src/main/java/org/openmrs/module/clientregistry/ClientRegistryConfig.java @@ -1,10 +1,9 @@ /** - * 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. + * 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.clientregistry; @@ -28,16 +27,16 @@ public class ClientRegistryConfig { @Qualifier("adminService") AdministrationService administrationService; - @Value("${CLIENTREGISTRY_SERVERURL}") + @Value("${CLIENTREGISTRY_SERVERURL:}") private String serverUrl; - @Value("${CLIENTREGISTRY_USERNAME}") + @Value("${CLIENTREGISTRY_USERNAME:}") private String username; - @Value("${CLIENTREGISTRY_PASSWORD}") + @Value("${CLIENTREGISTRY_PASSWORD:}") private String password; - @Value("${CLIENTREGISTRY_IDENTIFIERROOT}") + @Value("${CLIENTREGISTRY_IDENTIFIERROOT:}") private String identifierRoot; public boolean clientRegistryConnectionEnabled() { diff --git a/api/src/main/java/org/openmrs/module/clientregistry/ClientRegistryConstants.java b/api/src/main/java/org/openmrs/module/clientregistry/ClientRegistryConstants.java index 293842e..ff698e7 100644 --- a/api/src/main/java/org/openmrs/module/clientregistry/ClientRegistryConstants.java +++ b/api/src/main/java/org/openmrs/module/clientregistry/ClientRegistryConstants.java @@ -21,4 +21,13 @@ public class ClientRegistryConstants { public static final String GP_CLIENT_REGISTRY_TRANSACTION_METHOD = "clientregistry.transactionMethod"; public static final String UPDATE_MESSAGE_DESTINATION = "topic://UPDATED:org.openmrs.Patient"; + + public static final String CR_FHIR_OPERATION = "$cr"; + + public static final String CR_FHIR_SEARCH_OPERATION = "$cr-search"; + + public static final String CR_FHIR_UPDATE_OPERATION = "$cr-update"; + + public static final String CR_FHIR_DELETE_OPERATION = "$cr-delete"; + } diff --git a/api/src/main/java/org/openmrs/module/clientregistry/api/CRPatientService.java b/api/src/main/java/org/openmrs/module/clientregistry/api/CRPatientService.java index f1cc9da..389210b 100644 --- a/api/src/main/java/org/openmrs/module/clientregistry/api/CRPatientService.java +++ b/api/src/main/java/org/openmrs/module/clientregistry/api/CRPatientService.java @@ -2,12 +2,54 @@ import org.hl7.fhir.r4.model.Patient; import org.openmrs.module.fhir2.api.search.param.PatientSearchParams; - +import ca.uhn.fhir.rest.api.server.IBundleProvider; import java.util.List; public interface CRPatientService { - List getCRPatients(String sourceIdentifier, String sourceIdentifierSystem, List extraTargetSystems); + /** + * Queries patient by ID + */ + Patient getPatientById(String id); + + /** + * Queries patients through a PIXm manager using native IDs. + * + * @param sourceIdentifier the source identifier of the patient + * @param sourceIdentifierSystem the system of the source identifier + * @param extraTargetSystems additional target systems + * @return a bundle containing patients that match the specified identifiers + */ + IBundleProvider getPatientsByPIX(String sourceIdentifier, String sourceIdentifierSystem, List extraTargetSystems); + + /** + * Searches for patients, including fuzzy search. + * + * @param patientSearchParams the parameters for searching patients + * @return a bundle containing patients that match the search criteria + */ + IBundleProvider searchPatients(PatientSearchParams patientSearchParams); + + /** + * Creates a patient record + * + * @param patient the patient to create + * @return the created patient + */ + Patient createPatient(Patient patient); + + /** + * Updates a patient record. + * + * @param patient the patient to update + * @return the updated patient + */ + Patient updatePatient(Patient patient); - List searchCRForPatients(PatientSearchParams patientSearchParams); + /** + * Purges a patient record from the registry. + * + * @param patient the patient to purge + */ + void purgePatient(Patient patient); } diff --git a/api/src/main/java/org/openmrs/module/clientregistry/api/impl/FhirCRPatientServiceImpl.java b/api/src/main/java/org/openmrs/module/clientregistry/api/impl/FhirCRPatientServiceImpl.java index 10ec4b6..eba4e83 100644 --- a/api/src/main/java/org/openmrs/module/clientregistry/api/impl/FhirCRPatientServiceImpl.java +++ b/api/src/main/java/org/openmrs/module/clientregistry/api/impl/FhirCRPatientServiceImpl.java @@ -1,24 +1,26 @@ package org.openmrs.module.clientregistry.api.impl; +import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.gclient.ICriterion; import ca.uhn.fhir.rest.gclient.IOperationUntypedWithInputAndPartialOutput; import ca.uhn.fhir.rest.gclient.IQuery; import ca.uhn.fhir.rest.gclient.StringClientParam; -import ca.uhn.fhir.rest.param.StringOrListParam; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.TokenParam; -import ca.uhn.fhir.rest.param.UriOrListParam; +import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.r4.model.*; -import org.openmrs.module.clientregistry.ClientRegistryConfig; import org.openmrs.module.clientregistry.api.CRPatientService; +import org.openmrs.module.clientregistry.api.search.CRSearchBundleProvider; +import org.openmrs.module.clientregistry.api.search.PatientSearchCriteriaBuilder; import org.openmrs.module.clientregistry.providers.FhirCRConstants; import org.openmrs.module.fhir2.FhirConstants; +import org.openmrs.module.fhir2.api.FhirGlobalPropertyService; import org.openmrs.module.fhir2.api.search.param.PatientSearchParams; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.beans.factory.annotation.Qualifier; - import java.util.Collections; import java.util.List; import java.util.Objects; @@ -31,58 +33,86 @@ public class FhirCRPatientServiceImpl implements CRPatientService { @Qualifier("clientRegistryFhirClient") private IGenericClient fhirClient; - /** - * Get patient identifiers from an external client registry's $ihe-pix implementation. Use the - * returned identifiers to then request a matching Patient bundle from the client registry. - */ + @Autowired + private PatientSearchCriteriaBuilder criteriaBuilder; + + @Autowired + private FhirGlobalPropertyService globalPropertyService; + + @Override + public Patient getPatientById(String id) { + if (StringUtils.isBlank(id)) { + return null; + } + return fhirClient.read().resource(Patient.class).withId(id).execute(); + } + @Override - public List getCRPatients(String sourceIdentifier, String sourceIdentifierSystem, List targetSystems) { + public IBundleProvider getPatientsByPIX(String sourceIdentifier, String sourceIdentifierSystem, + List targetSystems) { // construct request to external FHIR $ihe-pix endpoint - IOperationUntypedWithInputAndPartialOutput identifiersRequest = fhirClient - .operation() - .onType(FhirConstants.PATIENT) - .named(FhirCRConstants.IHE_PIX_OPERATION) - .withSearchParameter(Parameters.class, FhirCRConstants.SOURCE_IDENTIFIER, new TokenParam(sourceIdentifierSystem, sourceIdentifier)); - + IOperationUntypedWithInputAndPartialOutput identifiersRequest = fhirClient.operation() + .onType(FhirConstants.PATIENT).named(FhirCRConstants.IHE_PIX_OPERATION).withSearchParameter(Parameters.class, + FhirCRConstants.SOURCE_IDENTIFIER, new TokenParam(sourceIdentifierSystem, sourceIdentifier)); + if (!targetSystems.isEmpty()) { - identifiersRequest.andSearchParameter(FhirCRConstants.TARGET_SYSTEM, new StringParam(String.join(",", targetSystems))); + identifiersRequest.andSearchParameter(FhirCRConstants.TARGET_SYSTEM, + new StringParam(String.join(",", targetSystems))); } - + Parameters crMatchingParams = identifiersRequest.useHttpGet().execute(); - List crIdentifiers = crMatchingParams.getParameter().stream() - .filter(param -> Objects.equals(param.getName(), "targetId")) - .map(param -> param.getValue().toString()) - .collect(Collectors.toList()); - + List crIdentifiers = crMatchingParams.getParameter().stream().filter(param -> Objects.equals(param.getName(), "targetId")) + .map(param -> ((Reference) param.getValue()).getReference()).collect(Collectors.toList()); + if (crIdentifiers.isEmpty()) { - return Collections.emptyList(); + return new CRSearchBundleProvider(Collections.emptyList(), globalPropertyService); } - - // construct and send request to external client registry - Bundle patientBundle = fhirClient - .search() - .forResource(Patient.class) - .where(new StringClientParam(Patient.SP_RES_ID).matches().values(crIdentifiers)) - .returnBundle(Bundle.class) - .execute(); - return parseCRPatientSearchResults(patientBundle); + Bundle patientBundle = fhirClient.search().forResource(Patient.class) + .where(new StringClientParam(Patient.SP_RES_ID).matches().values(crIdentifiers)).returnBundle(Bundle.class) + .execute(); + + return new CRSearchBundleProvider(parseCRPatientSearchResults(patientBundle), globalPropertyService); + + } + + @Override + public IBundleProvider searchPatients(PatientSearchParams patientSearchParams) { + List> criterions = criteriaBuilder.buildCriteria(patientSearchParams); + IQuery query = fhirClient.search().forResource(Patient.class); + + for (int i = 0; i < criterions.size(); i++) { + ICriterion criterion = criterions.get(i); + if (i == 0) { + query.where(criterion); + } else { + query.and(criterion); + } + } + Bundle patientBundle = query.returnBundle(Bundle.class).execute(); + return new CRSearchBundleProvider(parseCRPatientSearchResults(patientBundle), globalPropertyService); + } + + @Override + public Patient createPatient(Patient patient) { + return (Patient) fhirClient.create().resource(patient).execute().getResource(); } @Override - public List searchCRForPatients(PatientSearchParams patientSearchParams) { - return null; + public Patient updatePatient(Patient patient) { + return (Patient) fhirClient.update().resource(patient).execute().getResource(); + } + + @Override + public void purgePatient(Patient patient) { + fhirClient.delete().resource(patient).execute(); } /** * Filter and parse out fhir patients from Client Registry Patient Search results */ private List parseCRPatientSearchResults(Bundle patientBundle) { - return patientBundle - .getEntry() - .stream() - .filter(entry -> entry.hasType(FhirConstants.PATIENT)) - .map(entry -> (Patient) entry.getResource()) - .collect(Collectors.toList()); - } + return patientBundle.getEntry().stream().filter(entry -> entry.getResource().hasType(FhirConstants.PATIENT)) + .map(entry -> (Patient) entry.getResource()).collect(Collectors.toList()); + } } diff --git a/api/src/main/java/org/openmrs/module/clientregistry/api/search/CRSearchBundleProvider.java b/api/src/main/java/org/openmrs/module/clientregistry/api/search/CRSearchBundleProvider.java new file mode 100644 index 0000000..279f4d3 --- /dev/null +++ b/api/src/main/java/org/openmrs/module/clientregistry/api/search/CRSearchBundleProvider.java @@ -0,0 +1,27 @@ +package org.openmrs.module.clientregistry.api.search; + +import java.io.Serializable; +import java.util.List; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.openmrs.module.fhir2.FhirConstants; +import org.openmrs.module.fhir2.api.FhirGlobalPropertyService; +import ca.uhn.fhir.rest.server.SimpleBundleProvider; + +public class CRSearchBundleProvider extends SimpleBundleProvider implements Serializable { + + private final FhirGlobalPropertyService globalPropertyService; + + public CRSearchBundleProvider(List patientList, FhirGlobalPropertyService globalPropertyService) { + super(patientList); + this.globalPropertyService = globalPropertyService; + } + + @Override + public Integer preferredPageSize() { + if (size() == null) { + setSize(globalPropertyService.getGlobalProperty(FhirConstants.OPENMRS_FHIR_DEFAULT_PAGE_SIZE, 10)); + } + return size(); + } + +} diff --git a/api/src/main/java/org/openmrs/module/clientregistry/api/search/PatientSearchCriteriaBuilder.java b/api/src/main/java/org/openmrs/module/clientregistry/api/search/PatientSearchCriteriaBuilder.java new file mode 100644 index 0000000..5040911 --- /dev/null +++ b/api/src/main/java/org/openmrs/module/clientregistry/api/search/PatientSearchCriteriaBuilder.java @@ -0,0 +1,197 @@ +package org.openmrs.module.clientregistry.api.search; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.commons.lang3.StringUtils; +import org.hl7.fhir.r4.model.Patient; +import org.openmrs.module.fhir2.FhirConstants; +import org.openmrs.module.fhir2.api.search.param.PatientSearchParams; +import org.openmrs.module.fhir2.api.search.param.PropParam; +import org.springframework.stereotype.Component; +import ca.uhn.fhir.rest.gclient.ICriterion; +import ca.uhn.fhir.rest.gclient.StringClientParam; +import ca.uhn.fhir.rest.gclient.TokenClientParam; +import ca.uhn.fhir.rest.param.DateParam; +import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.param.StringAndListParam; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.TokenAndListParam; +import ca.uhn.fhir.rest.param.TokenParam; + +@Component +public class PatientSearchCriteriaBuilder { + + private static final Map customPropertyToFhirMap; + + static { + customPropertyToFhirMap = new HashMap<>(); + customPropertyToFhirMap.put("name.property", "name"); + customPropertyToFhirMap.put("given.property", "given"); + customPropertyToFhirMap.put("family.property", "family"); + customPropertyToFhirMap.put("city.property", "address-city"); + customPropertyToFhirMap.put("state.property", "address-state"); + customPropertyToFhirMap.put("postalCode.property", "address-postalcode"); + customPropertyToFhirMap.put("country.property", "address-country"); + customPropertyToFhirMap.put("_id.property", "_id"); + customPropertyToFhirMap.put("_lastUpdated.property", "_lastUpdated"); + } + + public List> buildCriteria(PatientSearchParams patientSearchParams) { + return patientSearchParams.toSearchParameterMap().getParameters().stream().map(entry -> { + List> params = entry.getValue(); + switch (entry.getKey()) { + case FhirConstants.NAME_SEARCH_HANDLER: + case FhirConstants.ADDRESS_SEARCH_HANDLER: + return processParamsEntry(params, this::handleStringParam); + case FhirConstants.IDENTIFIER_SEARCH_HANDLER: + return processParamsEntry(params, this::handleIdentifierParam); + case FhirConstants.GENDER_SEARCH_HANDLER: + return processParamsEntry(params, param -> handleTokenParam(param, null)); + case FhirConstants.DATE_RANGE_SEARCH_HANDLER: + return processParamsEntry(params, this::handleDateParam); + case FhirConstants.BOOLEAN_SEARCH_HANDLER: + return processParamsEntry(params, param -> handleTokenParam(param, "deceased")); + case FhirConstants.COMMON_SEARCH_HANDLER: + return processParamsEntry(params, this::handleCommonProps); + default: + return Arrays.asList(Optional.> empty()); + } + }).flatMap(Collection::stream).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList()); + } + + private List>> processParamsEntry(List> params, + Function, List>>> handler) { + return params.stream().map(handler).flatMap(Collection::stream).collect(Collectors.toList()); + } + + /** + * A generic handler for processing string parameters. + * + * @param param the PropParam containing the string parameter to handle + * @return a list of criterions representing the criteria created from the string parameter + */ + private List>> handleStringParam(PropParam param) { + if (param == null || param.getParam() == null) { + return Arrays.asList(Optional.empty()); + } + String paramName = getFhirParamName(param); + + return extractStringParams((StringAndListParam) param.getParam()).stream() + .map(stringParam -> createCriterionFromStringParam(paramName, stringParam)).collect(Collectors.toList()); + } + + /** + * A generic handler for processing token-based parameters. + * + * @param param the PropParam containing the token parameter to handle + * @param paramName optional; if provided, will be used as the param name + * @return a list of criterions representing the criteria created from the token parameter + */ + private List>> handleTokenParam(PropParam param, String paramName) { + if (param == null || param.getParam() == null) { + return Arrays.asList(Optional.empty()); + } + List tokenParams = extractTokenParams((TokenAndListParam) param.getParam()); + return tokenParams.stream() + .map(token -> createCriterionFromTokenParam( + StringUtils.isNotBlank(paramName) ? paramName : getFhirParamName(param), token)) + .collect(Collectors.toList()); + } + + private List>> handleIdentifierParam(PropParam param) { + if (param == null || param.getParam() == null) { + return Arrays.asList(Optional.empty()); + } + Map> identifierTokens = extractTokenParamsBySystem((TokenAndListParam) param.getParam()); + List>> criterions = identifierTokens.entrySet().stream() + .map(e -> createIdentifierCriterion(e.getKey(), e.getValue())).collect(Collectors.toList()); + + return criterions; + } + + private List>> handleDateParam(PropParam param) { + DateRangeParam dateRangeParam = (DateRangeParam) param.getParam(); + DateParam lower = dateRangeParam.getLowerBound(); + DateParam upper = dateRangeParam.getUpperBound(); + + return Stream.of(hasDistinctRanges(lower, upper) ? new DateParam[] { lower, upper } : new DateParam[] { lower }) + .filter(Objects::nonNull).map(dateParam -> { + String prefix = dateParam.getPrefix() != null ? dateParam.getPrefix().getValue() : "eq"; + String dateString = dateParam.getValueAsString(); + return createCriterionFromTokenParam(getFhirParamName(param), new TokenParam(null, prefix + dateString)); + }).collect(Collectors.toList()); + } + + private List>> handleCommonProps(PropParam param) { + switch (param.getPropertyName()) { + case FhirConstants.LAST_UPDATED_PROPERTY: + return handleDateParam(param); + case FhirConstants.ID_PROPERTY: + return handleTokenParam(param, null); + } + return Arrays.asList(Optional.empty()); + } + + private Optional> createCriterionFromTokenParam(String paramName, TokenParam token) { + String paramNameWithModifier = token.getModifier() != null ? paramName + token.getModifier().getValue() : paramName; + return Optional.of(new TokenClientParam(paramNameWithModifier).exactly().code(token.getValue())); + } + + private Optional> createCriterionFromStringParam(String paramName, StringParam param) { + String value = param.getValue(); + StringClientParam clientParam = new StringClientParam(paramName); + if (param.isExact()) { + return Optional.of(clientParam.matchesExactly().value(value)); + } + return Optional.of(clientParam.matches().value(value)); + } + + private Optional> createIdentifierCriterion(String system, List identifierTokens) { + if (identifierTokens == null || identifierTokens.isEmpty()) { + return Optional.empty(); + } + List identifiers = identifierTokens.stream().map(TokenParam::getValue).collect(Collectors.toList()); + + ICriterion criterion; + if (StringUtils.isNotBlank(system)) { + criterion = Patient.IDENTIFIER.exactly().systemAndValues(system, identifiers); + } else { + criterion = Patient.IDENTIFIER.exactly().codes(identifiers); + } + return Optional.ofNullable(criterion); + } + + private boolean hasDistinctRanges(DateParam lower, DateParam upper) { + String lowerModifier = lower != null && lower.getPrefix() != null ? lower.getPrefix().getValue() : ""; + String upperModifier = upper != null && upper.getPrefix() != null ? upper.getPrefix().getValue() : ""; + return !lowerModifier.equals(upperModifier); + } + + private List extractStringParams(StringAndListParam listParam) { + return listParam.getValuesAsQueryTokens().stream().flatMap(token -> token.getValuesAsQueryTokens().stream()) + .collect(Collectors.toList()); + } + + private Map> extractTokenParamsBySystem(TokenAndListParam listParam) { + return extractTokenParams(listParam).stream() + .collect(Collectors.groupingBy(token -> StringUtils.trimToEmpty(token.getSystem()))); + } + + private List extractTokenParams(TokenAndListParam listParam) { + return listParam.getValuesAsQueryTokens().stream().flatMap(token -> token.getValuesAsQueryTokens().stream()) + .collect(Collectors.toList()); + } + + private static String getFhirParamName(PropParam param) { + String fhirParamName = customPropertyToFhirMap.get(param.getPropertyName()); + return fhirParamName != null ? fhirParamName : param.getPropertyName(); + } +} diff --git a/api/src/main/java/org/openmrs/module/clientregistry/providers/FhirCRConstants.java b/api/src/main/java/org/openmrs/module/clientregistry/providers/FhirCRConstants.java index 66c7286..3ec4eea 100644 --- a/api/src/main/java/org/openmrs/module/clientregistry/providers/FhirCRConstants.java +++ b/api/src/main/java/org/openmrs/module/clientregistry/providers/FhirCRConstants.java @@ -4,7 +4,6 @@ import ca.uhn.fhir.rest.gclient.StringClientParam; import ca.uhn.fhir.rest.gclient.TokenClientParam; import ca.uhn.fhir.rest.gclient.UriClientParam; -import ca.uhn.fhir.rest.param.StringOrListParam; public class FhirCRConstants { diff --git a/api/src/main/java/org/openmrs/module/clientregistry/providers/r4/FhirCRPatientResourceProvider.java b/api/src/main/java/org/openmrs/module/clientregistry/providers/r4/FhirCRPatientResourceProvider.java index 206e9d2..bc1e089 100644 --- a/api/src/main/java/org/openmrs/module/clientregistry/providers/r4/FhirCRPatientResourceProvider.java +++ b/api/src/main/java/org/openmrs/module/clientregistry/providers/r4/FhirCRPatientResourceProvider.java @@ -1,31 +1,42 @@ package org.openmrs.module.clientregistry.providers.r4; -import ca.uhn.fhir.model.valueset.BundleTypeEnum; -import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.TokenAndListParam; +import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.param.StringAndListParam; import ca.uhn.fhir.rest.param.StringOrListParam; +import ca.uhn.fhir.rest.param.StringParam; 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.NotImplementedOperationException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import lombok.Setter; import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.OperationOutcome; import org.hl7.fhir.r4.model.Patient; +import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; - +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.annotation.Sort; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.SortSpec; +import ca.uhn.fhir.rest.api.server.IBundleProvider; import org.openmrs.api.context.Context; import org.openmrs.module.clientregistry.ClientRegistryConfig; -import org.openmrs.module.clientregistry.api.ClientRegistryManager; +import org.openmrs.module.clientregistry.ClientRegistryConstants; +import org.openmrs.module.clientregistry.api.CRPatientService; import org.openmrs.module.clientregistry.providers.FhirCRConstants; import org.openmrs.module.fhir2.api.annotations.R4Provider; +import org.openmrs.module.fhir2.api.search.param.PatientSearchParams; +import org.openmrs.module.fhir2.providers.util.FhirProviderUtils; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; - import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; - +import javax.annotation.Nonnull; import static lombok.AccessLevel.PACKAGE; @Component("crPatientFhirR4ResourceProvider") @@ -33,11 +44,45 @@ @Setter(PACKAGE) public class FhirCRPatientResourceProvider implements IResourceProvider { + @Autowired + private CRPatientService crService; + @Override public Class getResourceType() { return Patient.class; } + @Operation(name = ClientRegistryConstants.CR_FHIR_OPERATION, idempotent = true) + public Patient getPatientById(@IdParam @Nonnull IdType id) { + Patient patient = crService.getPatientById(id.getIdPart()); + if (patient == null) { + throw new ResourceNotFoundException("Could not find patient with Id " + id.getIdPart()); + } + return patient; + } + + @Operation(name = ClientRegistryConstants.CR_FHIR_OPERATION) + public MethodOutcome createPatient(@ResourceParam Patient patient) { + Patient createdPatient = crService.createPatient(patient); + return FhirProviderUtils.buildCreate(createdPatient); + } + + @Operation(name = ClientRegistryConstants.CR_FHIR_UPDATE_OPERATION) + public MethodOutcome updatePatient(@IdParam @Nonnull IdType id, @ResourceParam @Nonnull Patient patient) { + if (id == null || id.getIdPart() == null) { + throw new InvalidRequestException("id must be specified to update"); + } + patient.setId(id.getIdPart()); + Patient updatedPatient = crService.updatePatient(patient); + return FhirProviderUtils.buildUpdate(updatedPatient); + } + + @Operation(name = ClientRegistryConstants.CR_FHIR_DELETE_OPERATION) + public OperationOutcome deletePatient(@ResourceParam Patient patient) { + crService.purgePatient(patient); + return FhirProviderUtils.buildDeleteR4(); + } + /** * FHIR endpoint to get Patient references from external client registry Example request: GET * [fhirbase @@ -47,51 +92,53 @@ public Class getResourceType() { * we will use it to override the module defined source system. * @param targetSystemsParam (optional) Patient assigning authorities (ie systems) from which * the returned identifiers shall be selected - * @return List of matching FHIR patients returned by the client registry + * @return a bundle matching FHIR patients returned by the client registry */ - @Operation(name = FhirCRConstants.IHE_PIX_OPERATION, idempotent=true, type = Patient.class, bundleType = BundleTypeEnum.SEARCHSET) - public List getCRPatientById( - @OperationParam(name = FhirCRConstants.SOURCE_IDENTIFIER) TokenParam sourceIdentifierParam, - @OperationParam(name = FhirCRConstants.TARGET_SYSTEM) StringOrListParam targetSystemsParam - ) { - ClientRegistryManager clientRegistryManager = Context.getRegisteredComponent("clientRegistryManager", - ClientRegistryManager.class); - - ClientRegistryConfig config = Context.getRegisteredComponent("clientRegistryFhirClient", - ClientRegistryConfig.class); + @Operation(name = FhirCRConstants.IHE_PIX_OPERATION, idempotent = true) + public IBundleProvider getCRPatientByPix( + @OperationParam(name = FhirCRConstants.SOURCE_IDENTIFIER) TokenParam sourceIdentifierParam, + @OperationParam(name = FhirCRConstants.TARGET_SYSTEM) StringOrListParam targetSystemsParam) { + + ClientRegistryConfig config = Context.getRegisteredComponent("clientRegistryFhirClient", ClientRegistryConfig.class); if (sourceIdentifierParam == null || sourceIdentifierParam.getValue() == null) { throw new InvalidRequestException("sourceIdentifier must be specified"); } - - List targetSystems = targetSystemsParam == null - ? Collections.emptyList() - : targetSystemsParam.getValuesAsQueryTokens().stream().filter(Objects::nonNull).map(StringParam::getValue).collect(Collectors.toList()); - + + List targetSystems = targetSystemsParam == null ? Collections.emptyList() + : targetSystemsParam.getValuesAsQueryTokens().stream().filter(Objects::nonNull).map(StringParam::getValue) + .collect(Collectors.toList()); + // If no sourceSystem provided, use config defined default - boolean userDefinedSourceSystem = sourceIdentifierParam.getSystem() != null && !sourceIdentifierParam.getSystem().isEmpty(); - String sourceIdentifierSystem = userDefinedSourceSystem - ? sourceIdentifierParam.getSystem() - : config.getClientRegistryDefaultPatientIdentifierSystem(); - + boolean userDefinedSourceSystem = sourceIdentifierParam.getSystem() != null + && !sourceIdentifierParam.getSystem().isEmpty(); + String sourceIdentifierSystem = userDefinedSourceSystem ? sourceIdentifierParam.getSystem() + : config.getClientRegistryDefaultPatientIdentifierSystem(); + if (sourceIdentifierSystem == null || sourceIdentifierSystem.isEmpty()) { - throw new InvalidRequestException("ClientRegistry module does not have a default source system assigned " + - "via the defaultPatientIdentifierSystem property. Source system must be provided as a token in " + - "the sourceIdentifier request param"); - } - - List patients = clientRegistryManager.getPatientService().getCRPatients( - sourceIdentifierParam.getValue(), sourceIdentifierSystem, targetSystems - ); - - if (patients.isEmpty()) { - throw new ResourceNotFoundException("No Client Registry patients found."); + throw new InvalidRequestException("ClientRegistry module does not have a default source system assigned " + + "via the defaultPatientIdentifierSystem property. Source system must be provided as a token in " + + "the sourceIdentifier request param"); } - - return patients; + + return crService.getPatientsByPIX(sourceIdentifierSystem, sourceIdentifierSystem, targetSystems); } - @Operation(name = "$search", idempotent = true) - public List searchClientRegistryPatients() { - throw new NotImplementedOperationException("search client registry is not yet implemented"); + @Operation(name = ClientRegistryConstants.CR_FHIR_SEARCH_OPERATION, idempotent = true) + public IBundleProvider searchPatients(@OperationParam(name = Patient.SP_NAME) StringAndListParam name, + @OperationParam(name = Patient.SP_GIVEN) StringAndListParam given, + @OperationParam(name = Patient.SP_FAMILY) StringAndListParam family, + @OperationParam(name = Patient.SP_IDENTIFIER) TokenAndListParam identifier, + @OperationParam(name = Patient.SP_GENDER) TokenAndListParam gender, + @OperationParam(name = Patient.SP_BIRTHDATE) DateRangeParam birthDate, + @OperationParam(name = Patient.SP_DEATH_DATE) DateRangeParam deathDate, + @OperationParam(name = Patient.SP_DECEASED) TokenAndListParam deceased, + @OperationParam(name = Patient.SP_ADDRESS_CITY) StringAndListParam city, + @OperationParam(name = Patient.SP_ADDRESS_STATE) StringAndListParam state, + @OperationParam(name = Patient.SP_ADDRESS_POSTALCODE) StringAndListParam postalCode, + @OperationParam(name = Patient.SP_ADDRESS_COUNTRY) StringAndListParam country, + @OperationParam(name = Patient.SP_RES_ID) TokenAndListParam id, + @OperationParam(name = "_lastUpdated") DateRangeParam lastUpdated, @Sort SortSpec sort) { + return crService.searchPatients(new PatientSearchParams(name, given, family, identifier, gender, birthDate, + deathDate, deceased, city, state, postalCode, country, id, lastUpdated, sort, null)); } } diff --git a/api/src/test/java/org/openmrs/module/clientregistry/api/search/PatientSearchCriteriaBuilderTest.java b/api/src/test/java/org/openmrs/module/clientregistry/api/search/PatientSearchCriteriaBuilderTest.java new file mode 100644 index 0000000..f806da1 --- /dev/null +++ b/api/src/test/java/org/openmrs/module/clientregistry/api/search/PatientSearchCriteriaBuilderTest.java @@ -0,0 +1,215 @@ +package org.openmrs.module.clientregistry.api.search; + +import java.lang.reflect.Field; +import java.util.List; + +import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.param.TokenAndListParam; +import ca.uhn.fhir.rest.param.StringAndListParam; +import ca.uhn.fhir.rest.param.TokenOrListParam; +import ca.uhn.fhir.rest.param.StringOrListParam; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.TokenParam; +import org.junit.Test; +import org.openmrs.module.fhir2.api.search.param.PatientSearchParams; + +import ca.uhn.fhir.rest.gclient.ICriterion; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class PatientSearchCriteriaBuilderTest { + + private final PatientSearchCriteriaBuilder builder = new PatientSearchCriteriaBuilder(); + + @Test + public void buildCriteria_shouldGenerateCriterionForName() throws NoSuchFieldException, IllegalAccessException { + PatientSearchParams params = new PatientSearchParams(); + params.setName(createStringParam("Smith")); + + List> criterions = builder.buildCriteria(params); + + assertNotNull(criterions); + assertEquals(1, criterions.size()); + + ICriterion criterion = criterions.get(0); + + Field nameField = criterion.getClass().getDeclaredField("myName"); + nameField.setAccessible(true); + String name = (String) nameField.get(criterion); + assertEquals("name", name); + + Field valueField = criterion.getClass().getDeclaredField("myValue"); + valueField.setAccessible(true); + String value = (String) valueField.get(criterion); + assertEquals("Smith", value); + } + + @Test + public void buildCriteria_shouldGenerateCriterionForGivenName() throws NoSuchFieldException, IllegalAccessException { + PatientSearchParams params = new PatientSearchParams(); + params.setGiven(createStringParam("John")); + List> criterions = builder.buildCriteria(params); + + assertNotNull(criterions); + assertEquals(1, criterions.size()); + + ICriterion criterion = criterions.get(0); + + Field nameField = criterion.getClass().getDeclaredField("myName"); + nameField.setAccessible(true); + String name = (String) nameField.get(criterion); + assertEquals("given", name); + + Field valueField = criterion.getClass().getDeclaredField("myValue"); + valueField.setAccessible(true); + String value = (String) valueField.get(criterion); + assertEquals("John", value); + } + + @Test + public void buildCriteria_shouldGenerateCriterionForFamilyName() throws NoSuchFieldException, IllegalAccessException { + PatientSearchParams params = new PatientSearchParams(); + params.setFamily(createStringParam("Walter")); + List> criterions = builder.buildCriteria(params); + + assertNotNull(criterions); + assertEquals(1, criterions.size()); + + ICriterion criterion = criterions.get(0); + + Field nameField = criterion.getClass().getDeclaredField("myName"); + nameField.setAccessible(true); + String name = (String) nameField.get(criterion); + assertEquals("family", name); + + Field valueField = criterion.getClass().getDeclaredField("myValue"); + valueField.setAccessible(true); + String value = (String) valueField.get(criterion); + assertEquals("Walter", value); + } + + @Test + public void buildCriteria_shouldGenerateCriterionForGender() throws NoSuchFieldException, IllegalAccessException { + PatientSearchParams params = new PatientSearchParams(); + params.setGender(createTokenParam("male")); + List> criterions = builder.buildCriteria(params); + + assertNotNull(criterions); + assertEquals(1, criterions.size()); + + ICriterion criterion = criterions.get(0); + + Field nameField = criterion.getClass().getDeclaredField("myName"); + nameField.setAccessible(true); + String name = (String) nameField.get(criterion); + assertEquals("gender", name); + + Field valueField = criterion.getClass().getDeclaredField("myValue"); + valueField.setAccessible(true); + String value = (String) valueField.get(criterion); + assertEquals("male", value); + } + + @Test + public void buildCriteria_shouldGenerateCriterionForIdentifier() throws NoSuchFieldException, IllegalAccessException { + PatientSearchParams params = new PatientSearchParams(); + params.setIdentifier(createTokenParam("3r34g346-tk")); + List> criterions = builder.buildCriteria(params); + + assertNotNull(criterions); + assertEquals(1, criterions.size()); + + ICriterion criterion = criterions.get(0); + + Field nameField = criterion.getClass().getDeclaredField("myName"); + nameField.setAccessible(true); + String name = (String) nameField.get(criterion); + assertEquals("identifier", name); + + Field valueField = criterion.getClass().getDeclaredField("myValue"); + valueField.setAccessible(true); + String value = (String) valueField.get(criterion); + assertEquals("3r34g346-tk", value); + } + + @Test + public void buildCriteria_shouldGenerateCriterionForPatientWhenPatientBirthDateIsProvided() throws NoSuchFieldException, + IllegalAccessException { + PatientSearchParams params = new PatientSearchParams(); + params.setBirthDate(createDateRangeParam("1996-12-12")); + List> criterions = builder.buildCriteria(params); + + assertNotNull(criterions); + assertEquals(2, criterions.size()); + + ICriterion criterion = criterions.get(0); + + Field nameField = criterion.getClass().getDeclaredField("myName"); + nameField.setAccessible(true); + String name = (String) nameField.get(criterion); + assertEquals("birthdate", name); + + Field valueField = criterion.getClass().getDeclaredField("myValue"); + valueField.setAccessible(true); + String value = (String) valueField.get(criterion); + assertTrue(value.endsWith("1996-12-12")); + } + + @Test + public void buildCriteria_shouldGenerateCriterionForBoolean() throws NoSuchFieldException, IllegalAccessException { + PatientSearchParams params = new PatientSearchParams(); + params.setDeceased(createTokenParam("false")); + List> criterions = builder.buildCriteria(params); + + assertNotNull(criterions); + assertEquals(1, criterions.size()); + + ICriterion criterion = criterions.get(0); + + Field nameField = criterion.getClass().getDeclaredField("myName"); + nameField.setAccessible(true); + String name = (String) nameField.get(criterion); + assertEquals("deceased", name); + + Field valueField = criterion.getClass().getDeclaredField("myValue"); + valueField.setAccessible(true); + String value = (String) valueField.get(criterion); + assertEquals("false", value); + } + + @Test + public void buildCriteria_shouldGenerateCriterionForAddressWhenCityIsProvided() throws NoSuchFieldException, + IllegalAccessException { + PatientSearchParams params = new PatientSearchParams(); + params.setCity(createStringParam("Washington")); + List> criterions = builder.buildCriteria(params); + + assertNotNull(criterions); + assertEquals(1, criterions.size()); + + ICriterion criterion = criterions.get(0); + + Field nameField = criterion.getClass().getDeclaredField("myName"); + nameField.setAccessible(true); + String name = (String) nameField.get(criterion); + assertEquals("address-city", name); + + Field valueField = criterion.getClass().getDeclaredField("myValue"); + valueField.setAccessible(true); + String value = (String) valueField.get(criterion); + assertEquals("Washington", value); + } + + private StringAndListParam createStringParam(String paramValue) { + return new StringAndListParam().addAnd(new StringOrListParam().add(new StringParam(paramValue))); + } + + private TokenAndListParam createTokenParam(String paramValue) { + return new TokenAndListParam().addAnd(new TokenOrListParam().add(new TokenParam(paramValue))); + } + + private DateRangeParam createDateRangeParam(String paramValue) { + return new DateRangeParam().setLowerBound(paramValue).setUpperBound(paramValue); + } +} diff --git a/api/src/test/java/org/openmrs/module/clientregistry/providers/r4/FhirCRPatientResourceProviderTest.java b/api/src/test/java/org/openmrs/module/clientregistry/providers/r4/FhirCRPatientResourceProviderTest.java new file mode 100644 index 0000000..69e37b4 --- /dev/null +++ b/api/src/test/java/org/openmrs/module/clientregistry/providers/r4/FhirCRPatientResourceProviderTest.java @@ -0,0 +1,352 @@ +package org.openmrs.module.clientregistry.providers.r4; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.mockito.Mockito.when; + +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.Enumerations; +import org.hl7.fhir.r4.model.HumanName; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.OperationOutcome; +import org.hl7.fhir.r4.model.Patient; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.openmrs.module.clientregistry.api.CRPatientService; +import org.openmrs.module.fhir2.FhirConstants; +import org.openmrs.module.fhir2.api.search.param.PatientSearchParams; + +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.param.StringAndListParam; +import ca.uhn.fhir.rest.param.StringOrListParam; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.TokenAndListParam; +import ca.uhn.fhir.rest.param.TokenOrListParam; +import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.rest.server.SimpleBundleProvider; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; + +@RunWith(MockitoJUnitRunner.class) +public class FhirCRPatientResourceProviderTest { + + private static final String PATIENT_UUID = "01v312b1-cfv6-43ab-ae87-24070c801d1b"; + + private static final String WRONG_PATIENT_UUID = "417312c1-cx56-b3ab-aeb7-41070b401d1p"; + + private static final String NAME = "John"; + + private static final String GIVEN_NAME = "Smith"; + + private static final String FAMILY_NAME = "Doe"; + + private static final String GENDER = "male"; + + private static final String IDENTIFIER = "F10000"; + + private static final String BIRTH_DATE = "1967-02-04"; + + private static final String CITY = "Los Angeles"; + + private static final String STATE = "California"; + + private static final String COUNTRY = "USA"; + + private static final String POSTAL_CODE = "90210"; + + private static final String LAST_UPDATED_DATE = "2020-12-15"; + + @Mock + private CRPatientService crService; + + private FhirCRPatientResourceProvider resourceProvider; + + private Patient patient; + + @Before + public void setup() { + resourceProvider = new FhirCRPatientResourceProvider(); + resourceProvider.setCrService(crService); + + // init patient + HumanName name = new HumanName(); + name.addGiven(GIVEN_NAME); + name.setFamily(FAMILY_NAME); + name.addGiven(NAME); + patient = new Patient(); + patient.setId(PATIENT_UUID); + patient.addName(name); + patient.setActive(true); + patient.setBirthDate(parseDate(BIRTH_DATE)); + patient.setDeceased(new BooleanType(true)); + patient.setGender(Enumerations.AdministrativeGender.MALE); + } + + @Test + public void getPatientById_shouldGetPatientById() { + IdType id = new IdType(); + id.setValue(PATIENT_UUID); + when(crService.getPatientById(PATIENT_UUID)).thenReturn(patient); + + Patient result = resourceProvider.getPatientById(id); + assertThat(result.isResource(), is(true)); + assertThat(result.getId(), equalTo(PATIENT_UUID)); + } + + @Test(expected = ResourceNotFoundException.class) + public void getPatientById_shouldThrowAnExceptionIfPatientWasNotFound() { + IdType idType = new IdType(); + idType.setValue(WRONG_PATIENT_UUID); + assertThat(resourceProvider.getPatientById(idType), nullValue()); + } + + @Test + public void createPatient_shouldCreateNewPatient() { + when(crService.createPatient(patient)).thenReturn(patient); + + MethodOutcome result = resourceProvider.createPatient(patient); + + assertThat(result, notNullValue()); + assertThat(result.getResource(), equalTo(patient)); + } + + @Test + public void updatePatient_shouldUpdateRequestedPatient() { + when(crService.updatePatient(patient)).thenReturn(patient); + + MethodOutcome result = resourceProvider.updatePatient(new IdType().setValue(PATIENT_UUID), patient); + + assertThat(result, notNullValue()); + assertThat(result.getResource(), equalTo(patient)); + } + + @Test + public void deletePatient_shouldDeleteRequestedPatient() { + OperationOutcome result = resourceProvider.deletePatient(patient); + + assertThat(result, notNullValue()); + assertThat(result.getIssueFirstRep().getSeverity(), equalTo(OperationOutcome.IssueSeverity.INFORMATION)); + assertThat(result.getIssueFirstRep().getDetails().getCodingFirstRep().getCode(), equalTo("MSG_DELETED")); + assertThat(result.getIssueFirstRep().getDetails().getCodingFirstRep().getDisplay(), + equalTo("This resource has been deleted")); + } + + @Test + public void searchPatients_shouldReturnMatchingBundleOfPatientsByName() { + StringAndListParam nameParam = createStringParam(NAME); + when( + crService.searchPatients(new PatientSearchParams(nameParam, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null))).thenReturn(getBundleProvider(patient)); + + IBundleProvider results = resourceProvider.searchPatients(nameParam, null, null, null, null, null, null, null, null, + null, null, null, null, null, null); + + verifySinglePatientResult(results); + } + + @Test + public void searchPatients_shouldReturnMatchingBundleOfPatientsByGivenName() { + StringAndListParam givenNameParam = createStringParam(NAME); + when( + crService.searchPatients(new PatientSearchParams(null, givenNameParam, null, null, null, null, null, null, null, + null, null, null, null, null, null, null))).thenReturn(getBundleProvider(patient)); + + IBundleProvider results = resourceProvider.searchPatients(null, givenNameParam, null, null, null, null, null, null, + null, null, null, null, null, null, null); + + verifySinglePatientResult(results); + } + + @Test + public void searchPatients_shouldReturnMatchingBundleOfPatientsByFamilyName() { + StringAndListParam familyNameParam = createStringParam(FAMILY_NAME); + when( + crService.searchPatients(new PatientSearchParams(null, null, familyNameParam, null, null, null, null, null, + null, null, null, null, null, null, null, null))).thenReturn(getBundleProvider(patient)); + + IBundleProvider results = resourceProvider.searchPatients(null, null, familyNameParam, null, null, null, null, null, + null, null, null, null, null, null, null); + + verifySinglePatientResult(results); + } + + @Test + public void searchPatients_shouldReturnMatchingBundleOfPatientsByIdentifier() { + TokenAndListParam identifierParam = new TokenAndListParam().addAnd(new TokenOrListParam().add(IDENTIFIER)); + when( + crService.searchPatients(new PatientSearchParams(null, null, null, identifierParam, null, null, null, null, + null, null, null, null, null, null, null, null))).thenReturn(getBundleProvider(patient)); + + IBundleProvider results = resourceProvider.searchPatients(null, null, null, identifierParam, null, null, null, null, + null, null, null, null, null, null, null); + + verifySinglePatientResult(results); + } + + @Test + public void searchPatients_shouldReturnMatchingBundleOfPatientsByGender() { + TokenAndListParam genderParam = new TokenAndListParam().addAnd(new TokenOrListParam().add(GENDER)); + when( + crService.searchPatients(new PatientSearchParams(null, null, null, null, genderParam, null, null, null, null, + null, null, null, null, null, null, null))).thenReturn(getBundleProvider(patient)); + + IBundleProvider results = resourceProvider.searchPatients(null, null, null, null, genderParam, null, null, null, + null, null, null, null, null, null, null); + + verifySinglePatientResult(results); + } + + @Test + public void searchPatients_shouldReturnMatchingBundleOfPatientsByBirthDate() { + DateRangeParam birthDateParam = new DateRangeParam().setLowerBound(BIRTH_DATE).setUpperBound(BIRTH_DATE); + when( + crService.searchPatients(new PatientSearchParams(null, null, null, null, null, birthDateParam, null, null, null, + null, null, null, null, null, null, null))).thenReturn(getBundleProvider(patient)); + + IBundleProvider results = resourceProvider.searchPatients(null, null, null, null, null, birthDateParam, null, null, + null, null, null, null, null, null, null); + + verifySinglePatientResult(results); + } + + @Test + public void searchPatients_shouldReturnMatchingBundleOfPatientsByDeceased() { + TokenAndListParam deceasedParam = new TokenAndListParam().addAnd(new TokenOrListParam().add("true")); + when( + crService.searchPatients(new PatientSearchParams(null, null, null, null, null, null, null, deceasedParam, null, + null, null, null, null, null, null, null))).thenReturn(getBundleProvider(patient)); + + IBundleProvider results = resourceProvider.searchPatients(null, null, null, null, null, null, null, deceasedParam, + null, null, null, null, null, null, null); + + verifySinglePatientResult(results); + } + + @Test + public void searchPatients_shouldReturnMatchingBundleOfPatientsByCity() { + StringAndListParam cityParam = new StringAndListParam().addAnd(new StringOrListParam().add(new StringParam(CITY))); + when( + crService.searchPatients(new PatientSearchParams(null, null, null, null, null, null, null, null, cityParam, + null, null, null, null, null, null, null))).thenReturn(getBundleProvider(patient)); + + IBundleProvider results = resourceProvider.searchPatients(null, null, null, null, null, null, null, null, cityParam, + null, null, null, null, null, null); + + verifySinglePatientResult(results); + } + + @Test + public void searchPatients_shouldReturnMatchingBundleOfPatientsByState() { + StringAndListParam stateParam = new StringAndListParam().addAnd(new StringOrListParam().add(new StringParam(STATE))); + when( + crService.searchPatients(new PatientSearchParams(null, null, null, null, null, null, null, null, null, + stateParam, null, null, null, null, null, null))).thenReturn(getBundleProvider(patient)); + + IBundleProvider results = resourceProvider.searchPatients(null, null, null, null, null, null, null, null, null, + stateParam, null, null, null, null, null); + + verifySinglePatientResult(results); + } + + @Test + public void searchPatients_shouldReturnMatchingBundleOfPatientsByPostalCode() { + StringAndListParam postalCodeParam = new StringAndListParam().addAnd(new StringOrListParam().add(new StringParam( + POSTAL_CODE))); + when( + crService.searchPatients(new PatientSearchParams(null, null, null, null, null, null, null, null, null, null, + postalCodeParam, null, null, null, null, null))).thenReturn(getBundleProvider(patient)); + + IBundleProvider results = resourceProvider.searchPatients(null, null, null, null, null, null, null, null, null, + null, postalCodeParam, null, null, null, null); + + verifySinglePatientResult(results); + } + + @Test + public void searchPatients_shouldReturnMatchingBundleOfPatientsByCountry() { + StringAndListParam countryParam = new StringAndListParam().addAnd(new StringOrListParam().add(new StringParam( + COUNTRY))); + when( + crService.searchPatients(new PatientSearchParams(null, null, null, null, null, null, null, null, null, null, + null, countryParam, null, null, null, null))).thenReturn(getBundleProvider(patient)); + + IBundleProvider results = resourceProvider.searchPatients(null, null, null, null, null, null, null, null, null, + null, null, countryParam, null, null, null); + + verifySinglePatientResult(results); + } + + @Test + public void searchPatients_shouldReturnMatchingBundleOfPatientsByUUID() { + TokenAndListParam uuid = new TokenAndListParam().addAnd(new TokenParam(PATIENT_UUID)); + + when( + crService.searchPatients(new PatientSearchParams(null, null, null, null, null, null, null, null, null, null, + null, null, uuid, null, null, null))).thenReturn(getBundleProvider(patient)); + + IBundleProvider results = resourceProvider.searchPatients(null, null, null, null, null, null, null, null, null, + null, null, null, uuid, null, null); + + verifySinglePatientResult(results); + } + + @Test + public void searchPatients_shouldReturnMatchingBundleOfPatientsByLastUpdated() { + DateRangeParam lastUpdated = new DateRangeParam().setLowerBound(LAST_UPDATED_DATE).setUpperBound(LAST_UPDATED_DATE); + + when( + crService.searchPatients(new PatientSearchParams(null, null, null, null, null, null, null, null, null, null, + null, null, null, lastUpdated, null, null))).thenReturn(getBundleProvider(patient)); + + IBundleProvider results = resourceProvider.searchPatients(null, null, null, null, null, null, null, null, null, + null, null, null, null, lastUpdated, null); + + verifySinglePatientResult(results); + } + + private void verifySinglePatientResult(IBundleProvider results) { + List resources = results.getResources(0, 10); + + assertThat(resources, notNullValue()); + assertThat(resources, hasSize(equalTo(1))); + assertThat(resources.get(0).fhirType(), is(FhirConstants.PATIENT)); + assertThat(resources.get(0).getIdElement().getIdPart(), is(PATIENT_UUID)); + } + + private StringAndListParam createStringParam(String paramValue) { + return new StringAndListParam().addAnd(new StringOrListParam().add(new StringParam(paramValue))); + } + + private IBundleProvider getBundleProvider(Patient patient) { + return getBundleProvider(Collections.singletonList(patient)); + } + + private IBundleProvider getBundleProvider(List patientList) { + return new SimpleBundleProvider(patientList); + } + + private Date parseDate(String dateString) { + Date parsed = null; + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); + try { + parsed = formatter.parse(dateString); + } + catch (Exception ignored) {} + return parsed; + } + +} diff --git a/pom.xml b/pom.xml index 057dede..0d3c188 100644 --- a/pom.xml +++ b/pom.xml @@ -227,7 +227,7 @@ - 1.8.0 + 2.2.0 1.11.6 2.4.0