From f9adbcf1e9787506ba3f7fd6cf26d7b11436e172 Mon Sep 17 00:00:00 2001 From: Ian Date: Tue, 8 Oct 2024 17:36:20 -0400 Subject: [PATCH] Initial pass at some of the sorting work --- .../FhirMedicationDispenseDaoImpl_2_6.java | 3 +- .../openmrs/module/fhir2/FhirConstants.java | 4 +- .../module/fhir2/api/dao/impl/BaseDao.java | 97 ++++++++++++------- .../fhir2/api/dao/impl/BaseFhirDao.java | 52 +++++----- .../fhir2/api/dao/impl/BasePersonDao.java | 25 ++--- .../impl/FhirAllergyIntoleranceDaoImpl.java | 5 +- .../api/dao/impl/FhirConditionDaoImpl.java | 7 +- .../dao/impl/FhirDiagnosticReportDaoImpl.java | 5 +- .../api/dao/impl/FhirEncounterDaoImpl.java | 5 +- .../api/dao/impl/FhirLocationDaoImpl.java | 13 +-- .../api/dao/impl/FhirObservationDaoImpl.java | 5 +- .../api/dao/impl/FhirPatientDaoImpl.java | 29 +++--- .../dao/impl/FhirRelatedPersonDaoImpl.java | 68 ++++++------- .../fhir2/api/dao/impl/FhirVisitDaoImpl.java | 5 +- .../dao/impl/OpenmrsFhirCriteriaContext.java | 4 +- .../api/search/PersonSearchQueryTest.java | 74 ++++++++++++-- .../impl/ValueSetTranslatorImplTest.java | 4 +- 17 files changed, 250 insertions(+), 155 deletions(-) diff --git a/api-2.6/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirMedicationDispenseDaoImpl_2_6.java b/api-2.6/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirMedicationDispenseDaoImpl_2_6.java index f606c254d6..998cc6eb90 100644 --- a/api-2.6/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirMedicationDispenseDaoImpl_2_6.java +++ b/api-2.6/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirMedicationDispenseDaoImpl_2_6.java @@ -11,6 +11,7 @@ import javax.annotation.Nonnull; import javax.persistence.criteria.From; +import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; import java.util.List; @@ -92,7 +93,7 @@ protected Optional handleLastUpdated(OpenmrsFhirCriteriaContex } @Override - protected String paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @NonNull String param) { + protected Path paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @NonNull String param) { return super.paramToProp(criteriaContext, param); } } diff --git a/api/src/main/java/org/openmrs/module/fhir2/FhirConstants.java b/api/src/main/java/org/openmrs/module/fhir2/FhirConstants.java index 42a35c5053..e4272164c6 100644 --- a/api/src/main/java/org/openmrs/module/fhir2/FhirConstants.java +++ b/api/src/main/java/org/openmrs/module/fhir2/FhirConstants.java @@ -182,9 +182,9 @@ private FhirConstants() { public static final String GLOBAL_PROPERTY_MODERATE = "allergy.concept.severity.moderate"; public static final String GLOBAL_PROPERTY_OTHER = "allergy.concept.severity.other"; - + public static final String GLOBAL_PROPERTY_DEFAULT_CONCEPT_MAP_TYPE = "concept.defaultConceptMapType"; - + public static final String GLOBAL_PROPERTY_URI_PREFIX = "fhir2.uriPrefix"; public static final String ENCOUNTER_REFERENCE_SEARCH_HANDLER = "encounter.reference.search.handler"; diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BaseDao.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BaseDao.java index 58f980db6d..1d250ab7cb 100644 --- a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BaseDao.java +++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BaseDao.java @@ -16,6 +16,7 @@ import javax.persistence.criteria.From; import javax.persistence.criteria.Join; import javax.persistence.criteria.Order; +import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; @@ -245,7 +246,13 @@ protected , U extends IQueryParameterType> Option return Optional.empty(); } - return Optional.of(criteriaBuilder.and((toCriteriaArray(handleAndListParam(andListParam).map(handler))))); + Predicate[] predicates = toCriteriaArray(handleAndListParam(andListParam).map(handler)); + + if (predicates.length == 0) { + return Optional.empty(); + } + + return Optional.of(criteriaBuilder.and(predicates)); } protected , U extends IQueryParameterType> Optional handleAndListParamAsStream( @@ -255,8 +262,14 @@ protected , U extends IQueryParameterType> Option return Optional.empty(); } - return Optional.of(criteriaBuilder.and((toCriteriaArray(handleAndListParam(andListParam) - .map(orListParam -> handleOrListParamAsStream(criteriaBuilder, orListParam, handler)))))); + Predicate[] predicates = toCriteriaArray(handleAndListParam(andListParam) + .map(orListParam -> handleOrListParamAsStream(criteriaBuilder, orListParam, handler))); + + if (predicates.length == 0) { + return Optional.empty(); + } + + return Optional.of(criteriaBuilder.and(predicates)); } /** @@ -274,7 +287,13 @@ protected Optional handleOrListParam( return Optional.empty(); } - return Optional.of(criteriaBuilder.or(toCriteriaArray(handleOrListParam(orListParam).map(handler)))); + Predicate[] predicates = toCriteriaArray(handleOrListParam(orListParam).map(handler)); + + if (predicates.length == 0) { + return Optional.empty(); + } + + return Optional.of(criteriaBuilder.or(predicates)); } protected Optional handleOrListParamAsStream(CriteriaBuilder criteriaBuilder, @@ -283,7 +302,13 @@ protected Optional handleOrListParamA return Optional.empty(); } - return Optional.of(criteriaBuilder.or(toCriteriaArray(handleOrListParam(orListParam).flatMap(handler)))); + Predicate[] predicates = toCriteriaArray(handleOrListParam(orListParam).flatMap(handler)); + + if (predicates.length == 0) { + return Optional.empty(); + } + + return Optional.of(criteriaBuilder.or(predicates)); } /** @@ -710,10 +735,13 @@ protected Optional handleCodeableConcept(OpenmrsFhirCriteriaCo return handleAndListParamBySystem(criteriaContext.getCriteriaBuilder(), concepts, (system, tokens) -> { if (system.isEmpty()) { - - Predicate inConceptId = criteriaContext.getCriteriaBuilder().in(conceptAlias.get("conceptId")).value(criteriaContext.getCriteriaBuilder().literal(tokensToParams(tokens).map(NumberUtils::toInt).collect(Collectors.toList()))); - Predicate inUuid = criteriaContext.getCriteriaBuilder().in(conceptAlias.get("uuid")).value(criteriaContext.getCriteriaBuilder().literal(tokensToList(tokens))); - + + Predicate inConceptId = criteriaContext.getCriteriaBuilder().in(conceptAlias.get("conceptId")) + .value(criteriaContext.getCriteriaBuilder() + .literal(tokensToParams(tokens).map(NumberUtils::toInt).collect(Collectors.toList()))); + Predicate inUuid = criteriaContext.getCriteriaBuilder().in(conceptAlias.get("uuid")) + .value(criteriaContext.getCriteriaBuilder().literal(tokensToList(tokens))); + return Optional.of(criteriaContext.getCriteriaBuilder().or(inConceptId, inUuid)); } else { Join conceptMapAliasJoin = criteriaContext.addJoin(conceptAlias, "conceptMappings", conceptMapAlias); @@ -891,7 +919,7 @@ protected Optional handleMedicationReference(OpenmrsFhirCriter } protected Optional handleMedicationRequestReference(OpenmrsFhirCriteriaContext criteriaContext, - @Nonnull From drugOrderAlias, ReferenceAndListParam drugOrderReference) { + @Nonnull From drugOrderAlias, ReferenceAndListParam drugOrderReference) { if (drugOrderReference == null) { return Optional.empty(); } @@ -914,16 +942,16 @@ protected void handleSort(OpenmrsFhirCriteriaContext criteriaContex handleSort(criteriaContext, sort, this::paramToProps).ifPresent(l -> l.forEach(criteriaContext::addOrder)); } - protected Optional> handleSort( - OpenmrsFhirCriteriaContext criteriaContext, SortSpec sort, - BiFunction, SortState, Collection> paramToProp) { - List orderings = new ArrayList<>(); + protected Optional> handleSort(OpenmrsFhirCriteriaContext criteriaContext, SortSpec sort, + BiFunction, SortState, Collection> paramToProp) { + + List orderings = new ArrayList<>(); SortSpec sortSpec = sort; while (sortSpec != null) { - SortState state = SortState.builder().context(criteriaContext).sortOrder(sortSpec.getOrder()) + SortState state = SortState. builder().context(criteriaContext).sortOrder(sortSpec.getOrder()) .parameter(sortSpec.getParamName().toLowerCase()).build(); - Collection orders = paramToProp.apply(criteriaContext, state); + Collection orders = paramToProp.apply(criteriaContext, state); if (orders != null) { orderings.addAll(orders); } @@ -1029,18 +1057,15 @@ protected TokenAndListParam convertStringStatusToBoolean(TokenAndListParam statu * @param sortState a {@link SortState} object describing the current sort state * @return the corresponding ordering(s) needed for this property */ - protected Collection paramToProps(OpenmrsFhirCriteriaContext criteriaContext, - @Nonnull SortState sortState) { - Collection prop = paramToProps(criteriaContext, sortState.getParameter()); + protected Collection paramToProps(@Nonnull OpenmrsFhirCriteriaContext criteriaContext, + @Nonnull SortState sortState) { + Collection> prop = paramToProps(criteriaContext, sortState.getParameter()); if (prop != null) { switch (sortState.getSortOrder()) { case ASC: - return prop.stream().map(s -> criteriaContext.getCriteriaBuilder().asc(criteriaContext.getRoot().get(s))) - .collect(Collectors.toList()); + return prop.stream().map(p -> criteriaContext.getCriteriaBuilder().asc(p)).collect(Collectors.toList()); case DESC: - return prop.stream() - .map(s -> criteriaContext.getCriteriaBuilder().desc(criteriaContext.getRoot().get(s))) - .collect(Collectors.toList()); + return prop.stream().map(p -> criteriaContext.getCriteriaBuilder().desc(p)).collect(Collectors.toList()); } } @@ -1054,9 +1079,9 @@ protected Collection paramToProps(OpenmrsFhirCriteriaContext * @param param the FHIR parameter to map * @return the name of the corresponding property from the current query */ - protected Collection paramToProps(OpenmrsFhirCriteriaContext criteriaContext, + protected Collection> paramToProps(@Nonnull OpenmrsFhirCriteriaContext criteriaContext, @Nonnull String param) { - String prop = paramToProp(criteriaContext, param); + Path prop = paramToProp(criteriaContext, param); if (prop != null) { return Collections.singleton(prop); @@ -1072,7 +1097,7 @@ protected Collection paramToProps(OpenmrsFhirCriteriaContext String paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @Nonnull String param) { + protected Path paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @Nonnull String param) { return null; } @@ -1126,15 +1151,15 @@ protected Stream handleOrListParam(IQueryPara @SafeVarargs @SuppressWarnings("unused") - protected final Predicate[] toCriteriaArray(Optional... predicate) { + protected final @Nonnull Predicate[] toCriteriaArray(@Nonnull Optional... predicate) { return toCriteriaArray(Arrays.stream(predicate)); } - protected Predicate[] toCriteriaArray(Collection> collection) { + protected @Nonnull Predicate[] toCriteriaArray(@Nonnull Collection> collection) { return toCriteriaArray(collection.stream()); } - protected Predicate[] toCriteriaArray(Stream> predicateStream) { + protected @Nonnull Predicate[] toCriteriaArray(@Nonnull Stream> predicateStream) { return predicateStream.filter(Optional::isPresent).map(Optional::get).toArray(Predicate[]::new); } @@ -1144,9 +1169,9 @@ protected Predicate[] toCriteriaArray(Stream> pred @Data @Builder @EqualsAndHashCode - public static final class SortState { + public static final class SortState { - private OpenmrsFhirCriteriaContext context; + private OpenmrsFhirCriteriaContext context; private SortOrderEnum sortOrder; @@ -1272,7 +1297,7 @@ protected OpenmrsFhirCriteriaContext createCriteriaContext(Class cq = (CriteriaQuery) cb.createQuery(rootType); @SuppressWarnings("unchecked") Root root = (Root) cq.from(rootType); - + return new OpenmrsFhirCriteriaContext<>(em, cb, cq, root); } @@ -1284,13 +1309,13 @@ protected OpenmrsFhirCriteriaContext createCriteriaContext(Class From getRootOrJoin(OpenmrsFhirCriteriaContext criteriaContext, From alias) { + + protected From getRootOrJoin(OpenmrsFhirCriteriaContext criteriaContext, From alias) { if (alias == null) { return criteriaContext.getRoot(); } else { return criteriaContext.getJoin(alias).orElseThrow(() -> new IllegalStateException( - "Tried to reference alias " + alias + " before creating a join with that name")); + "Tried to reference alias " + alias + " before creating a join with that name")); } } diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BaseFhirDao.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BaseFhirDao.java index 7079db0b92..0cd6658454 100644 --- a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BaseFhirDao.java +++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BaseFhirDao.java @@ -18,6 +18,7 @@ import javax.persistence.EntityManager; import javax.persistence.NoResultException; import javax.persistence.TypedQuery; +import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Selection; @@ -158,20 +159,20 @@ protected OpenmrsFhirCriteriaContext getSearchResultCriteria(SearchPar return criteriaContext; } - + @Override @SuppressWarnings({ "unchecked", "UnstableApiUsage" }) public List getSearchResults(@Nonnull SearchParameterMap theParams) { List results; OpenmrsFhirCriteriaContext criteriaContext = getSearchResultCriteria(theParams); String idProperty = getIdPropertyName(criteriaContext); - + handleSort(criteriaContext, theParams.getSortSpec()); handleIdPropertyOrdering(criteriaContext, idProperty); - + TypedQuery executableQuery = (TypedQuery) criteriaContext.getEntityManager() - .createQuery(criteriaContext.finalizeQuery()); - + .createQuery(criteriaContext.finalizeQuery()); + executableQuery.setFirstResult(theParams.getFromIndex()); if (theParams.getToIndex() != Integer.MAX_VALUE) { int maxResults = theParams.getToIndex() - theParams.getFromIndex(); @@ -183,38 +184,39 @@ public List getSearchResults(@Nonnull SearchParameterMap theParams) { executableQuery.setMaxResults(negative); } } - + if (hasDistinctResults()) { - results = (List) criteriaContext.getEntityManager().createQuery(criteriaContext.getCriteriaQuery()) - .getResultList(); + results = executableQuery.getResultList(); } else { List> selectionList = new ArrayList<>(); selectionList.add(criteriaContext.getRoot().get(idProperty).alias("id")); criteriaContext.getCriteriaQuery().multiselect(selectionList).distinct(true); - + criteriaContext.getCriteriaQuery().select(criteriaContext.getRoot().get(getIdPropertyName(criteriaContext))) - .distinct(true); - + .distinct(true); + List ids = new ArrayList<>(); if (selectionList.size() > 1) { - for (Object[] o : ((List) criteriaContext.getEntityManager().createQuery(criteriaContext.getCriteriaQuery()).getResultList())) { + for (Object[] o : ((List) criteriaContext.getEntityManager() + .createQuery(criteriaContext.getCriteriaQuery()).getResultList())) { ids.add((Integer) o[0]); } } else { - ids = (List) criteriaContext.getEntityManager().createQuery(criteriaContext.getCriteriaQuery()).getResultList(); + ids = (List) criteriaContext.getEntityManager().createQuery(criteriaContext.getCriteriaQuery()) + .getResultList(); } - + // Use distinct ids from the original query to return entire objects OpenmrsFhirCriteriaContext wrapperQuery = createCriteriaContext(typeToken.getRawType()); wrapperQuery.getCriteriaQuery().where(wrapperQuery.getRoot().get(idProperty).in(ids)); - + // Need to reapply ordering handleSort(criteriaContext, theParams.getSortSpec()); handleIdPropertyOrdering(criteriaContext, idProperty); - + results = wrapperQuery.getEntityManager().createQuery(wrapperQuery.getCriteriaQuery()).getResultList(); } - + return results.stream().map(this::deproxyResult).collect(Collectors.toList()); } @@ -329,20 +331,18 @@ protected void setupSearchParams(OpenmrsFhirCriteriaContext criteriaCo @Override protected Collection paramToProps( - OpenmrsFhirCriteriaContext criteriaContext, @Nonnull SortState sortState) { + OpenmrsFhirCriteriaContext criteriaContext, @Nonnull SortState sortState) { String param = sortState.getParameter(); if (FhirConstants.SP_LAST_UPDATED.equalsIgnoreCase(param)) { if (isImmutable) { switch (sortState.getSortOrder()) { case ASC: - return Collections - .singletonList((javax.persistence.criteria.Order) criteriaContext.getCriteriaQuery().orderBy( - criteriaContext.getCriteriaBuilder().asc(criteriaContext.getRoot().get("dateCreated")))); + return Collections.singletonList( + criteriaContext.getCriteriaBuilder().asc(criteriaContext.getRoot().get("dateCreated"))); case DESC: return Collections.singletonList( - (javax.persistence.criteria.Order) criteriaContext.getCriteriaQuery().orderBy( - criteriaContext.getCriteriaBuilder().desc(criteriaContext.getRoot().get("dateCreated")))); + criteriaContext.getCriteriaBuilder().desc(criteriaContext.getRoot().get("dateCreated"))); } } @@ -358,14 +358,14 @@ protected Collection paramToProps( } @Override - protected String paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @Nonnull String param) { + protected Path paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @Nonnull String param) { if (DomainResource.SP_RES_ID.equals(param)) { - return "uuid"; + return criteriaContext.getRoot().get("uuid"); } return super.paramToProp(criteriaContext, param); } - + @SuppressWarnings("unchecked") protected void applyExactTotal(OpenmrsFhirCriteriaContext criteriaContext, SearchParameterMap theParams) { List> exactTotal = theParams.getParameters(EXACT_TOTAL_SEARCH_PARAMETER); diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BasePersonDao.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BasePersonDao.java index d8fa1f48e6..37b262f350 100644 --- a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BasePersonDao.java +++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BasePersonDao.java @@ -24,6 +24,7 @@ import javax.persistence.criteria.Join; import javax.persistence.criteria.JoinType; import javax.persistence.criteria.Order; +import javax.persistence.criteria.Path; import java.util.ArrayList; import java.util.Collection; @@ -66,7 +67,7 @@ public abstract class BasePersonDao extends @Override protected Collection paramToProps(OpenmrsFhirCriteriaContext criteriaContext, - @Nonnull SortState sortState) { + @Nonnull SortState sortState) { String param = sortState.getParameter(); if (param == null) { @@ -148,7 +149,7 @@ protected Collection paramToProps(OpenmrsFhirCriteriaContext // first patient name if there are no preferred patient name cb.and(cb.not(cb.exists(personNameSecondSubquery.finalizeQuery())), cb.equal(personNameJoin.get("personNameId"), personNameThirdSubquery.finalizeQuery())), - // ultimate fallback, I guess? + // ultimate fallback, I guess? cb.isNull(personNameJoin.get("personNameId"))))); String[] properties = null; @@ -179,29 +180,29 @@ protected Collection paramToProps(OpenmrsFhirCriteriaContext break; } - criteriaContext.getCriteriaQuery().orderBy(sortStateOrders); return sortStateOrders; - } return super.paramToProps(criteriaContext, sortState); } @Override - protected String paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @Nonnull String param) { + protected Path paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @Nonnull String param) { From person = getPersonProperty(criteriaContext); - From address = criteriaContext.addJoin(person, "addresses", "pad"); + From address = criteriaContext.getJoin("pad") + .orElseGet(() -> criteriaContext.addJoin(person, "addresses", "pad")); + switch (param) { case SP_BIRTHDATE: - return "birthdate"; + return person.get("birthdate"); case SP_ADDRESS_CITY: - address.get("cityVillage"); + return address.get("cityVillage"); case SP_ADDRESS_STATE: - address.get("stateProvince"); + return address.get("stateProvince"); case SP_ADDRESS_POSTALCODE: - address.get("postalCode"); + return address.get("postalCode"); case SP_ADDRESS_COUNTRY: - address.get("country"); + return address.get("country"); default: return null; } @@ -233,7 +234,7 @@ protected void handleAddresses(OpenmrsFhirCriteriaContext criteriaCont From person = getPersonProperty(criteriaContext); From padJoin = criteriaContext.addJoin(person, "addresses", "pad"); handlePersonAddress(criteriaContext, padJoin, city, state, postalCode, country) - .ifPresent(c -> criteriaContext.addPredicate(c).finalizeQuery()); + .ifPresent(criteriaContext::addPredicate); } protected void handleNames(OpenmrsFhirCriteriaContext criteriaContext, List> params) { diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirAllergyIntoleranceDaoImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirAllergyIntoleranceDaoImpl.java index 00b1d5a067..83859e39ce 100644 --- a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirAllergyIntoleranceDaoImpl.java +++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirAllergyIntoleranceDaoImpl.java @@ -12,6 +12,7 @@ import javax.annotation.Nonnull; import javax.persistence.criteria.From; import javax.persistence.criteria.Join; +import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; import java.util.Map; @@ -180,9 +181,9 @@ private Optional handleAllergenCategory(OpenmrsFhirCriteriaCon } @Override - protected String paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @NonNull String param) { + protected Path paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @NonNull String param) { if (AllergyIntolerance.SP_SEVERITY.equals(param)) { - return "severity"; + return criteriaContext.getRoot().get("severity"); } return super.paramToProp(criteriaContext, param); diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirConditionDaoImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirConditionDaoImpl.java index 9439143263..b70c0007be 100644 --- a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirConditionDaoImpl.java +++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirConditionDaoImpl.java @@ -11,6 +11,7 @@ import javax.annotation.Nonnull; import javax.persistence.criteria.From; +import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; import java.util.List; @@ -136,12 +137,12 @@ protected Optional handleLastUpdated(OpenmrsFhirCriteriaContex } @Override - protected String paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @NonNull String param) { + protected Path paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @NonNull String param) { switch (param) { case org.hl7.fhir.r4.model.Condition.SP_ONSET_DATE: - return "onsetDate"; + return criteriaContext.getRoot().get("onsetDate"); case org.hl7.fhir.r4.model.Condition.SP_RECORDED_DATE: - return "dateCreated"; + return criteriaContext.getRoot().get("dateCreated"); } return super.paramToProp(criteriaContext, param); } diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirDiagnosticReportDaoImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirDiagnosticReportDaoImpl.java index 13ee7f0d24..b8e0705a2a 100644 --- a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirDiagnosticReportDaoImpl.java +++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirDiagnosticReportDaoImpl.java @@ -11,6 +11,7 @@ import javax.persistence.criteria.From; import javax.persistence.criteria.Join; +import javax.persistence.criteria.Path; import java.util.Optional; @@ -86,9 +87,9 @@ private void handleObservationReference(OpenmrsFhirCriteriaContext String paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @NonNull String param) { + protected Path paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @NonNull String param) { if (DiagnosticReport.SP_ISSUED.equals(param)) { - return "issued"; + return criteriaContext.getRoot().get("issued"); } return super.paramToProp(criteriaContext, param); diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirEncounterDaoImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirEncounterDaoImpl.java index 93814dcb4f..7890419930 100644 --- a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirEncounterDaoImpl.java +++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirEncounterDaoImpl.java @@ -18,6 +18,7 @@ import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.From; import javax.persistence.criteria.Join; +import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; @@ -111,10 +112,10 @@ protected void handleParticipant(OpenmrsFhirCriteriaContext cr } @Override - protected String paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @NonNull String param) { + protected Path paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @NonNull String param) { switch (param) { case SP_DATE: - return "encounterDatetime"; + return criteriaContext.getRoot().get("encounterDatetime"); default: return null; } diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirLocationDaoImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirLocationDaoImpl.java index cb2f4e6eeb..1db9833df6 100644 --- a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirLocationDaoImpl.java +++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirLocationDaoImpl.java @@ -11,6 +11,7 @@ import javax.annotation.Nonnull; import javax.persistence.criteria.From; +import javax.persistence.criteria.Path; import java.util.List; @@ -141,18 +142,18 @@ private void handleParentLocation(OpenmrsFhirCriteriaContext cr } @Override - protected String paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @NonNull String param) { + protected Path paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @NonNull String param) { switch (param) { case org.hl7.fhir.r4.model.Location.SP_NAME: - return "name"; + return criteriaContext.getRoot().get("name"); case org.hl7.fhir.r4.model.Location.SP_ADDRESS_CITY: - return "cityVillage"; + return criteriaContext.getRoot().get("cityVillage"); case org.hl7.fhir.r4.model.Location.SP_ADDRESS_STATE: - return "stateProvince"; + return criteriaContext.getRoot().get("stateProvince"); case org.hl7.fhir.r4.model.Location.SP_ADDRESS_COUNTRY: - return "country"; + return criteriaContext.getRoot().get("country"); case org.hl7.fhir.r4.model.Location.SP_ADDRESS_POSTALCODE: - return "postalCode"; + return criteriaContext.getRoot().get("postalCode"); default: return super.paramToProp(criteriaContext, param); diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirObservationDaoImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirObservationDaoImpl.java index 8523d8b594..73a4dcfa9a 100644 --- a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirObservationDaoImpl.java +++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirObservationDaoImpl.java @@ -12,6 +12,7 @@ import javax.annotation.Nonnull; import javax.persistence.criteria.From; import javax.persistence.criteria.Join; +import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; import java.util.Date; @@ -290,9 +291,9 @@ private void handleValueCodedConcept(OpenmrsFhirCriteriaContext crit } @Override - protected String paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @NonNull String paramName) { + protected Path paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @NonNull String paramName) { if (Observation.SP_DATE.equals(paramName)) { - return "obsDatetime"; + return criteriaContext.getRoot().get("obsDatetime"); } return null; diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirPatientDaoImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirPatientDaoImpl.java index be78e0ed13..fe232cb21c 100644 --- a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirPatientDaoImpl.java +++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirPatientDaoImpl.java @@ -12,13 +12,10 @@ import static org.hl7.fhir.r4.model.Patient.SP_DEATH_DATE; import javax.annotation.Nonnull; -import javax.persistence.EntityManager; import javax.persistence.TypedQuery; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Join; +import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; -import javax.persistence.criteria.Root; import java.util.ArrayList; import java.util.Collection; @@ -48,7 +45,8 @@ public class FhirPatientDaoImpl extends BasePersonDao implements FhirPa @Override public Patient getPatientById(@Nonnull Integer id) { OpenmrsFhirCriteriaContext criteriaContext = createCriteriaContext(Patient.class); - criteriaContext.getCriteriaQuery().select(criteriaContext.getRoot()).where(criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("patientId"), id)); + criteriaContext.getCriteriaQuery().select(criteriaContext.getRoot()) + .where(criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("patientId"), id)); TypedQuery query = criteriaContext.getEntityManager().createQuery(criteriaContext.getCriteriaQuery()); return query.getResultList().stream().findFirst().orElse(null); @@ -65,10 +63,19 @@ public List getPatientsByIds(@Nonnull Collection ids) { @Override public PatientIdentifierType getPatientIdentifierTypeByNameOrUuid(String name, String uuid) { - OpenmrsFhirCriteriaContext criteriaContext = createCriteriaContext(PatientIdentifierType.class); - criteriaContext.getCriteriaQuery().select(criteriaContext.getRoot()).where(criteriaContext.getCriteriaBuilder().or(criteriaContext.getCriteriaBuilder().and(criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("name"), name), - criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("retired"), false)), criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("uuid"), uuid))); - List identifierTypes = criteriaContext.getEntityManager().createQuery(criteriaContext.getCriteriaQuery()).getResultList(); + OpenmrsFhirCriteriaContext criteriaContext = createCriteriaContext( + PatientIdentifierType.class); + criteriaContext.getCriteriaQuery().select(criteriaContext.getRoot()) + .where( + criteriaContext + .getCriteriaBuilder().or( + criteriaContext.getCriteriaBuilder().and( + criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("name"), name), + criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("retired"), + false)), + criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("uuid"), uuid))); + List identifierTypes = criteriaContext.getEntityManager() + .createQuery(criteriaContext.getCriteriaQuery()).getResultList(); if (identifierTypes.isEmpty()) { return null; @@ -188,9 +195,9 @@ protected void handleIdentifier(OpenmrsFhirCriteriaContext crite } @Override - protected String paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @NonNull String param) { + protected Path paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @NonNull String param) { if (SP_DEATH_DATE.equalsIgnoreCase(param)) { - return "deathDate"; + return criteriaContext.getRoot().get("deathDate"); } return super.paramToProp(criteriaContext, param); } diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirRelatedPersonDaoImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirRelatedPersonDaoImpl.java index 137685bd92..123ff66f9d 100644 --- a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirRelatedPersonDaoImpl.java +++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirRelatedPersonDaoImpl.java @@ -64,9 +64,9 @@ protected void setupSearchParams(OpenmrsFhirCriteriaContext .ifPresent(c -> criteriaContext.addPredicate(c).finalizeQuery())); break; case FhirConstants.DATE_RANGE_SEARCH_HANDLER: - entry.getValue().forEach( - param -> handleDateRange(criteriaContext, personJoin, (DateRangeParam) param.getParam()) - .ifPresent(c -> criteriaContext.addPredicate(c).finalizeQuery())); + entry.getValue() + .forEach(param -> handleDateRange(criteriaContext, personJoin, (DateRangeParam) param.getParam()) + .ifPresent(c -> criteriaContext.addPredicate(c).finalizeQuery())); break; case FhirConstants.ADDRESS_SEARCH_HANDLER: handleAddresses(criteriaContext, entry); @@ -78,23 +78,25 @@ protected void setupSearchParams(OpenmrsFhirCriteriaContext } }); } - + // TODO: find a way of integrating this with the handleDateRange functionality in BaseDao! - private Optional handleDateRange(OpenmrsFhirCriteriaContext criteriaContext, From personJoin, DateRangeParam param) { + private Optional handleDateRange(OpenmrsFhirCriteriaContext criteriaContext, + From personJoin, DateRangeParam param) { if (param == null) { return Optional.empty(); } - + return Optional.ofNullable(criteriaContext.getCriteriaBuilder() - .and(toCriteriaArray(Stream.of(handleDate(criteriaContext, personJoin, param.getLowerBound()), - handleDate(criteriaContext, personJoin, param.getUpperBound()))))); - } - - private Optional handleDate(OpenmrsFhirCriteriaContext criteriaContext, From personJoin, DateParam dateParam) { + .and(toCriteriaArray(Stream.of(handleDate(criteriaContext, personJoin, param.getLowerBound()), + handleDate(criteriaContext, personJoin, param.getUpperBound()))))); + } + + private Optional handleDate(OpenmrsFhirCriteriaContext criteriaContext, + From personJoin, DateParam dateParam) { if (dateParam == null) { return Optional.empty(); } - + int calendarPrecision = dateParam.getPrecision().getCalendarConstant(); if (calendarPrecision > Calendar.SECOND) { calendarPrecision = Calendar.SECOND; @@ -102,42 +104,40 @@ private Optional handleDate(OpenmrsFhirCriteriaContext Collection paramToProps(OpenmrsFhirCriteriaContext criteriaContext, - @Nonnull SortState sortState) { + @Nonnull SortState sortState) { String param = sortState.getParameter(); if (param == null) { @@ -209,7 +209,7 @@ protected Collection paramToProps(OpenmrsFhirCriteriaContext } @Override - protected String paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @Nonnull String param) { + protected Path paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @Nonnull String param) { From personJoin = criteriaContext.addJoin("personA", "m"); From pad = criteriaContext.addJoin(personJoin, "addresses", "pad", javax.persistence.criteria.JoinType.LEFT); switch (param) { @@ -254,7 +254,7 @@ private void handleAddresses(OpenmrsFhirCriteriaContext cri From personJoin = criteriaContext.addJoin("personA", "m"); From padJoin = criteriaContext.addJoin(personJoin, "addresses", "pad"); handlePersonAddress(criteriaContext, padJoin, city, state, postalCode, country) - .ifPresent(c -> criteriaContext.addPredicate(c).finalizeQuery()); + .ifPresent(criteriaContext::addPredicate); } } diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirVisitDaoImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirVisitDaoImpl.java index 5a95356aef..bc2e1374df 100644 --- a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirVisitDaoImpl.java +++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirVisitDaoImpl.java @@ -13,6 +13,7 @@ import javax.persistence.criteria.From; import javax.persistence.criteria.Join; +import javax.persistence.criteria.Path; import java.util.Optional; @@ -54,10 +55,10 @@ protected void handleParticipant(OpenmrsFhirCriteriaContext criter } @Override - protected String paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @NonNull String param) { + protected Path paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @NonNull String param) { switch (param) { case SP_DATE: - return "startDatetime"; + return criteriaContext.getRoot().get("startDatetime"); default: return null; } diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/OpenmrsFhirCriteriaContext.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/OpenmrsFhirCriteriaContext.java index d7221938e0..a6e6d53aae 100644 --- a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/OpenmrsFhirCriteriaContext.java +++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/OpenmrsFhirCriteriaContext.java @@ -214,8 +214,8 @@ public OpenmrsFhirCriteriaContext addResults(T result) { public Optional> getJoin(String alias) { return Optional.ofNullable(aliases.get(alias)); } - - public Optional> getJoin(From alias) { + + public Optional> getJoin(From alias) { return Optional.ofNullable(aliases.get(alias.getAlias())); } diff --git a/api/src/test/java/org/openmrs/module/fhir2/api/search/PersonSearchQueryTest.java b/api/src/test/java/org/openmrs/module/fhir2/api/search/PersonSearchQueryTest.java index 57f37f3619..4b04f6ca34 100644 --- a/api/src/test/java/org/openmrs/module/fhir2/api/search/PersonSearchQueryTest.java +++ b/api/src/test/java/org/openmrs/module/fhir2/api/search/PersonSearchQueryTest.java @@ -33,6 +33,7 @@ import static org.hl7.fhir.r4.model.Person.SP_ADDRESS_STATE; import static org.hl7.fhir.r4.model.Person.SP_BIRTHDATE; import static org.hl7.fhir.r4.model.Person.SP_NAME; +import static org.junit.Assert.fail; import static org.openmrs.util.OpenmrsUtil.compareWithNullAsGreatest; import java.text.ParseException; @@ -113,8 +114,12 @@ public class PersonSearchQueryTest extends BaseModuleContextSensitiveTest { ret = compareWithNullAsGreatest(o1.getFamily(), o2.getFamily()); // familyName if (ret == 0) { // familyName2 - ret = compareWithNullAsGreatest(o1.getExtension().get(2).getValue().toString(), - o2.getExtension().get(2).getValue().toString()); + if (o1.hasExtension(FhirConstants.OPENMRS_FHIR_EXT_NAME + "#familyName2") + && o2.hasExtension(FhirConstants.OPENMRS_FHIR_EXT_NAME + "#familyName2")) { + ret = compareWithNullAsGreatest( + o1.getExtensionByUrl(FhirConstants.OPENMRS_FHIR_EXT_NAME + "#familyName2").getValue().toString(), + o2.getExtensionByUrl(FhirConstants.OPENMRS_FHIR_EXT_NAME + "#familyName2").getValue().toString()); + } } if (ret == 0) { // givenName + middleName @@ -122,13 +127,21 @@ public class PersonSearchQueryTest extends BaseModuleContextSensitiveTest { } if (ret == 0) { // familyNamePrefix - ret = compareWithNullAsGreatest(o1.getExtension().get(1).getValue().toString(), - o2.getExtension().get(1).getValue().toString()); + if (o1.hasExtension(FhirConstants.OPENMRS_FHIR_EXT_NAME + "#familyNamePrefix") + && o2.hasExtension(FhirConstants.OPENMRS_FHIR_EXT_NAME + "#familyNamePrefix")) { + ret = compareWithNullAsGreatest( + o1.getExtensionByUrl(FhirConstants.OPENMRS_FHIR_EXT_NAME + "#familyNamePrefix").getValue().toString(), + o2.getExtensionByUrl(FhirConstants.OPENMRS_FHIR_EXT_NAME + "#familyNamePrefix").getValue().toString()); + } } if (ret == 0) { // familyNameSuffix - ret = compareWithNullAsGreatest(o1.getExtension().get(3).getValue().toString(), - o2.getExtension().get(3).getValue().toString()); + if (o1.hasExtension(FhirConstants.OPENMRS_FHIR_EXT_NAME + "#familyNameSuffix") + && o2.hasExtension(FhirConstants.OPENMRS_FHIR_EXT_NAME + "#familyNameSuffix")) { + ret = compareWithNullAsGreatest( + o1.getExtensionByUrl(FhirConstants.OPENMRS_FHIR_EXT_NAME + "#familyNameSuffix").getValue().toString(), + o2.getExtensionByUrl(FhirConstants.OPENMRS_FHIR_EXT_NAME + "#familyNameSuffix").getValue().toString()); + } } if (ret == 0) { @@ -478,7 +491,7 @@ public void shouldAddPersonLinksToResultListWhenIncluded() { } @Test - public void shouldReturnCollectionOfPeopleSortedByGivenName() { + public void shouldReturnCollectionOfPeopleSortedByName() { SortSpec sort = new SortSpec(); sort.setParamName(SP_NAME); sort.setOrder(SortOrderEnum.ASC); @@ -496,7 +509,28 @@ public void shouldReturnCollectionOfPeopleSortedByGivenName() { assertThat(people, hasSize(greaterThan(1))); for (int i = 1; i < people.size(); i++) { - assertThat(people.get(i - 1).getNameFirstRep(), NAME_MATCHER.lessThanOrEqualTo(people.get(i).getNameFirstRep())); + if (people.get(i).getName().size() > 1 || people.get(i - 1).getName().size() > 1) { + boolean found = false; + + search: for (int j = 0; j < people.get(i).getName().size(); j++) { + for (int k = 0; k < people.get(i - 1).getName().size(); k++) { + if (NAME_MATCHER.lessThanOrEqualTo(people.get(i).getName().get(j)) + .matches(people.get(i - 1).getName().get(k))) { + found = true; + break search; + } + } + } + + if (!found) { + fail("Expected " + people.get(i).getNameFirstRep().getText() + + " to have at least one name greater than one name of " + + people.get(i - 1).getNameFirstRep().getText()); + } + } else { + assertThat(people.get(i - 1).getNameFirstRep(), + NAME_MATCHER.lessThanOrEqualTo(people.get(i).getNameFirstRep())); + } } sort.setOrder(SortOrderEnum.DESC); @@ -513,8 +547,28 @@ public void shouldReturnCollectionOfPeopleSortedByGivenName() { assertThat(people, hasSize(greaterThan(1))); for (int i = 1; i < people.size(); i++) { - assertThat(people.get(i - 1).getNameFirstRep(), - NAME_MATCHER.greaterThanOrEqualTo(people.get(i).getNameFirstRep())); + if (people.get(i).getName().size() > 1 || people.get(i - 1).getName().size() > 1) { + boolean found = false; + + search: for (int j = 0; j < people.get(i).getName().size(); j++) { + for (int k = 0; k < people.get(i - 1).getName().size(); k++) { + if (NAME_MATCHER.greaterThanOrEqualTo(people.get(i).getName().get(j)) + .matches(people.get(i - 1).getName().get(k))) { + found = true; + break search; + } + } + } + + if (!found) { + fail("Expected " + people.get(i).getNameFirstRep().getText() + + " to have at least one name greater than one name of " + + people.get(i - 1).getNameFirstRep().getText()); + } + } else { + assertThat(people.get(i - 1).getNameFirstRep(), + NAME_MATCHER.greaterThanOrEqualTo(people.get(i).getNameFirstRep())); + } } } diff --git a/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/ValueSetTranslatorImplTest.java b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/ValueSetTranslatorImplTest.java index 03a01aa1a2..2e084e0ba5 100644 --- a/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/ValueSetTranslatorImplTest.java +++ b/api/src/test/java/org/openmrs/module/fhir2/api/translators/impl/ValueSetTranslatorImplTest.java @@ -53,7 +53,7 @@ public class ValueSetTranslatorImplTest extends BaseModuleContextSensitiveTest { @Mock private Concept concept; - + @Autowired private FhirGlobalPropertyService globalPropertyService; @@ -63,7 +63,7 @@ public class ValueSetTranslatorImplTest extends BaseModuleContextSensitiveTest { public void setup() { valueSetTranslator.setConceptSourceService(conceptSourceService); } - + @Before public void setGlobalProperty() { when(globalPropertyService.getGlobalProperty(GLOBAL_PROPERTY_DEFAULT_CONCEPT_MAP_TYPE)).thenReturn("NARROWER-THAN");