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 417fff8e5..e107ad381 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 @@ -13,8 +13,8 @@ import javax.persistence.EntityManager; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.From; import javax.persistence.criteria.Join; -import javax.persistence.criteria.JoinType; import javax.persistence.criteria.Order; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; @@ -78,7 +78,6 @@ import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Practitioner; import org.hl7.fhir.r4.model.codesystems.AdministrativeGender; -import org.openmrs.PatientIdentifier; import org.openmrs.module.fhir2.FhirConstants; import org.openmrs.module.fhir2.api.search.param.PropParam; import org.openmrs.module.fhir2.api.util.LocalDateTimeFactory; @@ -160,1228 +159,1037 @@ *

*/ public abstract class BaseDao { - - private static final BigDecimal APPROX_RANGE = new BigDecimal("0.1"); - - @Autowired - private LocalDateTimeFactory localDateTimeFactory; - - @Autowired - @Getter(AccessLevel.PUBLIC) - @Setter(AccessLevel.PUBLIC) - @Qualifier("sessionFactory") - protected SessionFactory sessionFactory; - - /** - * Converts an {@link Iterable} to a {@link Stream} - * - * @param iterable the iterable - * @param any type - * @return a stream containing the same objects as the iterable - */ - @SuppressWarnings("unused") - protected static Stream stream(Iterable iterable) { - return stream(iterable.iterator()); - } - - /** - * Converts an {@link Iterator} to a {@link Stream} - * - * @param iterator the iterator - * @param any type - * @return a stream containing the same objects as the iterator - */ - protected static Stream stream(Iterator iterator) { - return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false); - } - - /** - * Converts an {@link Iterable} to a {@link Stream} operated on in parallel - * - * @param iterable the iterable - * @param any type - * @return a stream containing the same objects as the iterable - */ - @SuppressWarnings("unused") - protected static Stream parallelStream(Iterable iterable) { - return parallelStream(iterable.iterator()); - } - - /** - * Converts an {@link Iterator} to a {@link Stream} operated on in parallel - * - * @param iterator the iterator - * @param any type - * @return a stream containing the same objects as the iterator - */ - protected static Stream parallelStream(Iterator iterator) { - return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), true); - } - - /** - * Determines whether the given criteria object already has a given alias. This is useful to - * determine whether a mapping has already been made or whether a given alias is already in use. - * - * @param alias the alias to look for - * @return true if the alias exists in this criteria object, false otherwise - */ - protected boolean lacksAlias(@Nonnull OpenmrsFhirCriteriaContext criteriaContext, @Nonnull String alias) { - return !criteriaContext.hasAlias(alias); - } - - /** - * Determines whether any of the {@link CriteriaImpl.Subcriteria} objects returned by a given - * iterator are mapped to the specified alias. - * - * @param subcriteriaIterator an {@link Iterator} of {@link CriteriaImpl.Subcriteria} to check for - * the given alias - * @param alias the alias to look for - * @return true if any of the given subcriteria use the specified alias, false otherwise - */ - protected boolean containsAlias(Iterator subcriteriaIterator, @Nonnull String alias) { - return stream(subcriteriaIterator).noneMatch(sc -> sc.getAlias().equals(alias)); - } - - /** - * A generic handler for any subtype of {@link IQueryParameterAnd} which creates a criterion that - * represents the intersection of all of the parameters contained - * - * @param andListParam the {@link IQueryParameterAnd} to handle - * @param handler a {@link Function} which maps a parameter to a {@link Criterion} - * @param the subtype of {@link IQueryParameterOr} that this {@link IQueryParameterAnd} contains - * @param the subtype of {@link IQueryParameterType} for this parameter - * @return the resulting criterion, which is the intersection of all of the unions of contained - * parameters - */ - protected , U extends IQueryParameterType> Optional handleAndListParam( - IQueryParameterAnd andListParam, Function> handler) { - if (andListParam == null) { - return Optional.empty(); - } - - EntityManager entityManager = sessionFactory.getCurrentSession(); - CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); - - return Optional.ofNullable(criteriaBuilder.and( - toCriteriaArray(handleAndListParam(andListParam).map(orListParam -> handleOrListParam(orListParam, handler))))); - } - - @SuppressWarnings("unused") - protected , U extends IQueryParameterType> Optional handleAndListParamBy( - IQueryParameterAnd andListParam, Function, Optional> handler) { - if (andListParam == null) { - return Optional.empty(); - } - - EntityManager entityManager = sessionFactory.getCurrentSession(); - CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); - - return Optional.of(criteriaBuilder.and((toCriteriaArray(handleAndListParam(andListParam).map(handler))))); - } - - protected , U extends IQueryParameterType> Optional handleAndListParamAsStream( - IQueryParameterAnd andListParam, Function>> handler) { - if (andListParam == null) { - return Optional.empty(); - } - - EntityManager entityManager = sessionFactory.getCurrentSession(); - CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); - - return Optional.of(criteriaBuilder.and((toCriteriaArray( - handleAndListParam(andListParam).map(orListParam -> handleOrListParamAsStream(orListParam, handler)))))); - } - - /** - * A generic handler for any subtype of {@link IQueryParameterOr} which creates a criterion that - * represents the union of all the parameters - * - * @param orListParam the {@link IQueryParameterOr} to handle - * @param handler a {@link Function} which maps a parameter to a {@link Criterion} - * @param the subtype of {@link IQueryParameterType} for this parameter - * @return the resulting criterion, which is the union of all contained parameters - */ - protected Optional handleOrListParam(IQueryParameterOr orListParam, - Function> handler) { - if (orListParam == null) { - return Optional.empty(); - } - - EntityManager entityManager = sessionFactory.getCurrentSession(); - CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); - - return Optional.of(criteriaBuilder.or(toCriteriaArray(handleOrListParam(orListParam).map(handler)))); - } - - protected Optional handleOrListParamAsStream(IQueryParameterOr orListParam, - Function>> handler) { - if (orListParam == null) { - return Optional.empty(); - } - - EntityManager entityManager = sessionFactory.getCurrentSession(); - CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); - - return Optional.of(criteriaBuilder.or(toCriteriaArray(handleOrListParam(orListParam).flatMap(handler)))); - } - - /** - * Handler for a {@link IQueryParameterAnd} of {@link TokenParam}s where tokens should be grouped - * and handled according to the system they belong to This is useful for queries drawing their - * values from CodeableConcepts - * - * @param andListParam the {@link IQueryParameterAnd} to handle - * @param systemTokenHandler a {@link BiFunction} taking the system and associated list of - * {@link TokenParam}s and returning a {@link Criterion} - * @return a {@link Criterion} representing the intersection of all produced {@link Criterion} - */ - protected > Optional handleAndListParamBySystem( - IQueryParameterAnd andListParam, - BiFunction, Optional> systemTokenHandler) { - if (andListParam == null) { - return Optional.empty(); - } - - EntityManager entityManager = sessionFactory.getCurrentSession(); - CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); - return Optional.of(criteriaBuilder.and(toCriteriaArray( - handleAndListParam(andListParam).map(param -> handleOrListParamBySystem(param, systemTokenHandler))))); - } - - /** - * Handler for a {@link IQueryParameterOr} of {@link TokenParam}s where tokens should be grouped and - * handled according to the system they belong to This is useful for queries drawing their values - * from CodeableConcepts - * - * @param orListParam the {@link IQueryParameterOr} to handle - * @param systemTokenHandler a {@link BiFunction} taking the system and associated list of - * {@link TokenParam}s and returning a {@link Criterion} - * @return a {@link Criterion} representing the union of all produced {@link Criterion} - */ - protected Optional handleOrListParamBySystem(IQueryParameterOr orListParam, - BiFunction, Optional> systemTokenHandler) { - - if (orListParam == null) { - return Optional.empty(); - } - - EntityManager entityManager = sessionFactory.getCurrentSession(); - CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); - - return Optional.of(criteriaBuilder - .or(toCriteriaArray(handleOrListParam(orListParam).collect(Collectors.groupingBy(this::groupBySystem)) - .entrySet().stream().map(e -> systemTokenHandler.apply(e.getKey(), e.getValue()))))); - } - - /** - * Handler for a {@link TokenOrListParam} that represents boolean values - * - * @param propertyName the name of the property in the query to use - * @param booleanToken the {@link TokenOrListParam} to handle - * @return a {@link Criterion} to be added to the query indicating that the property matches the - * given value - */ - protected Optional handleBoolean(OpenmrsFhirCriteriaContext criteriaContext, String propertyName, - TokenAndListParam booleanToken) { - if (booleanToken == null) { - return Optional.empty(); - } - - // note that we use a custom implementation here as Boolean.valueOf() and Boolean.parse() only determine whether - // the string matches "true". We could potentially be passed a non-valid Boolean value here. - return handleAndListParam(booleanToken, token -> { - if (token.getValue().equalsIgnoreCase("true")) { - return handleBooleanProperty(criteriaContext, propertyName, true); - } else if (token.getValue().equalsIgnoreCase("false")) { - return handleBooleanProperty(criteriaContext, propertyName, false); - } - - return Optional.empty(); - }); - } - - protected Optional handleBooleanProperty(OpenmrsFhirCriteriaContext criteriaContext, - String propertyName, boolean booleanVal) { - return Optional - .of(criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get(propertyName), booleanVal)); - } - - /** - * A handler for a {@link DateRangeParam}, which represents an inclusive set of {@link DateParam}s - * - * @param propertyName the name of the property in the query to use - * @param dateRangeParam the {@link DateRangeParam} to handle - * @return a {@link Criterion} to be added to the query for the indicated date range - */ - protected Optional handleDateRange(OpenmrsFhirCriteriaContext criteriaContext, String propertyName, - DateRangeParam dateRangeParam) { - if (dateRangeParam == null) { - return Optional.empty(); - } - - return Optional.ofNullable(criteriaContext.getCriteriaBuilder() - .and(toCriteriaArray(Stream.of(handleDate(criteriaContext, propertyName, dateRangeParam.getLowerBound()), - handleDate(criteriaContext, propertyName, dateRangeParam.getUpperBound()))))); - } - - /** - * A handler for a {@link DateParam}, which represents a day and an comparator - * - * @param propertyName the name of the property in the query to use - * @param dateParam the {@link DateParam} to handle - * @return a {@link Predicate} to be added to the query for the indicate date param - */ - protected Optional handleDate(OpenmrsFhirCriteriaContext criteriaContext, String propertyName, - DateParam dateParam) { - if (dateParam == null) { - return Optional.empty(); - } - - int calendarPrecision = dateParam.getPrecision().getCalendarConstant(); - if (calendarPrecision > Calendar.SECOND) { - calendarPrecision = Calendar.SECOND; - } - // TODO We may want to not use the default Calendar - Date dateStart = DateUtils.truncate(dateParam.getValue(), calendarPrecision); - Date dateEnd = DateUtils.ceiling(dateParam.getValue(), calendarPrecision); - - // TODO This does not properly handle FHIR Periods and Timings, though its unclear if we are using those - // see https://www.hl7.org/fhir/search.html#date - switch (dateParam.getPrefix()) { - case EQUAL: - return Optional.of(criteriaContext.getCriteriaBuilder().and( - criteriaContext.getCriteriaBuilder().greaterThanOrEqualTo(criteriaContext.getRoot().get(propertyName), - dateStart), - criteriaContext.getCriteriaBuilder().lessThan(criteriaContext.getRoot().get(propertyName), dateEnd))); - case NOT_EQUAL: - return Optional.of(criteriaContext.getCriteriaBuilder().not(criteriaContext.getCriteriaBuilder().and( - criteriaContext.getCriteriaBuilder().greaterThanOrEqualTo(criteriaContext.getRoot().get(propertyName), - dateStart), - criteriaContext.getCriteriaBuilder().lessThan(criteriaContext.getRoot().get(propertyName), dateEnd)))); - case LESSTHAN_OR_EQUALS: - case LESSTHAN: - return Optional.of(criteriaContext.getCriteriaBuilder() - .lessThanOrEqualTo(criteriaContext.getRoot().get(propertyName), dateEnd)); - case GREATERTHAN_OR_EQUALS: - case GREATERTHAN: - return Optional.of(criteriaContext.getCriteriaBuilder() - .greaterThanOrEqualTo(criteriaContext.getRoot().get(propertyName), dateStart)); - case STARTS_AFTER: - return Optional.of( - criteriaContext.getCriteriaBuilder().greaterThan(criteriaContext.getRoot().get(propertyName), dateEnd)); - case ENDS_BEFORE: - return Optional.of( - criteriaContext.getCriteriaBuilder().lessThan(criteriaContext.getRoot().get(propertyName), dateEnd)); - } - - return Optional.empty(); - } - - protected Optional handleQuantity(OpenmrsFhirCriteriaContext criteriaContext, String propertyName, - QuantityParam quantityParam) { - if (quantityParam == null) { - return Optional.empty(); - } - - BigDecimal value = quantityParam.getValue(); - if (quantityParam.getPrefix() == null || quantityParam.getPrefix() == ParamPrefixEnum.APPROXIMATE) { - String plainString = quantityParam.getValue().toPlainString(); - int dotIdx = plainString.indexOf('.'); - - BigDecimal approxRange = APPROX_RANGE.multiply(value); - if (dotIdx == -1) { - double lowerBound = value.subtract(approxRange).doubleValue(); - double upperBound = value.add(approxRange).doubleValue(); - return Optional.of(criteriaContext.getCriteriaBuilder().between(criteriaContext.getRoot().get(propertyName), - lowerBound, upperBound)); - } else { - int precision = plainString.length() - (dotIdx); - double mul = Math.pow(10, -precision); - double val = mul * 5.0d; - double lowerBound = value.subtract(new BigDecimal(val)).doubleValue(); - double upperBound = value.add(new BigDecimal(val)).doubleValue(); - return Optional.of(criteriaContext.getCriteriaBuilder().between(criteriaContext.getRoot().get(propertyName), - lowerBound, upperBound)); - } - } else { - double val = value.doubleValue(); - switch (quantityParam.getPrefix()) { - case EQUAL: - return Optional.of( - criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get(propertyName), val)); - case NOT_EQUAL: - return Optional.of( - criteriaContext.getCriteriaBuilder().notEqual(criteriaContext.getRoot().get(propertyName), val)); - case LESSTHAN_OR_EQUALS: - return Optional.of(criteriaContext.getCriteriaBuilder() - .lessThanOrEqualTo(criteriaContext.getRoot().get(propertyName), val)); - case LESSTHAN: - return Optional.of( - criteriaContext.getCriteriaBuilder().lessThan(criteriaContext.getRoot().get(propertyName), val)); - case GREATERTHAN_OR_EQUALS: - return Optional.of(criteriaContext.getCriteriaBuilder() - .greaterThanOrEqualTo(criteriaContext.getRoot().get(propertyName), val)); - case GREATERTHAN: - return Optional.of( - criteriaContext.getCriteriaBuilder().greaterThan(criteriaContext.getRoot().get(propertyName), val)); - } - } - - return Optional.empty(); - } - - protected Optional handleQuantity(OpenmrsFhirCriteriaContext criteriaContext, - @Nonnull String propertyName, QuantityAndListParam quantityAndListParam) { - if (quantityAndListParam == null) { - return Optional.empty(); - } - - return handleAndListParam(quantityAndListParam, - quantityParam -> handleQuantity(criteriaContext, propertyName, quantityParam)); - } - - protected void handleEncounterReference(OpenmrsFhirCriteriaContext criteriaContext, - ReferenceAndListParam encounterReference, @Nonnull String encounterAlias) { - handleEncounterReference(criteriaContext, encounterReference, encounterAlias, "encounter"); - } - - protected void handleEncounterReference(OpenmrsFhirCriteriaContext criteriaContext, - ReferenceAndListParam encounterReference, @Nonnull String encounterAlias, @Nonnull String associationPath) { - - if (encounterReference == null) { - return; - } - - if (lacksAlias(criteriaContext, encounterAlias)) { - criteriaContext.getRoot().join(associationPath).alias(encounterAlias); - } - - handleAndListParam(encounterReference, token -> { - if (token.getChain() != null) { - switch (token.getChain()) { - case Encounter.SP_TYPE: - if (lacksAlias(criteriaContext, "et")) { - criteriaContext.getRoot().join(String.format("%s.encounterType", encounterAlias)).alias("et"); - } - return propertyLike(criteriaContext, "et.uuid", new StringParam(token.getValue(), true)); - } - } else { - return Optional.of(criteriaContext.getCriteriaBuilder() - .equal(criteriaContext.getRoot().get(String.format("%s.uuid", encounterAlias)), token.getIdPart())); - } - - return Optional.empty(); - }).ifPresent(criteriaContext::addPredicate); - } - - protected Optional handleGender(OpenmrsFhirCriteriaContext criteriaContext, - @Nonnull String propertyName, TokenAndListParam gender) { - if (gender == null) { - return Optional.empty(); - } - - return handleAndListParam(gender, token -> { - try { - AdministrativeGender administrativeGender = AdministrativeGender.fromCode(token.getValue()); - - if (administrativeGender == null) { - return Optional - .of(criteriaContext.getCriteriaBuilder().isNull(criteriaContext.getRoot().get(propertyName))); - } - - switch (administrativeGender) { - case MALE: - return Optional.of( - criteriaContext.getCriteriaBuilder().like(criteriaContext.getRoot().get(propertyName), "M")); - case FEMALE: - return Optional.of( - criteriaContext.getCriteriaBuilder().like(criteriaContext.getRoot().get(propertyName), "F")); - case OTHER: - case UNKNOWN: - case NULL: - return Optional.of( - criteriaContext.getCriteriaBuilder().isNull(criteriaContext.getRoot().get(propertyName))); - } - } - catch (FHIRException ignored) {} - return Optional.of( - criteriaContext.getCriteriaBuilder().like(criteriaContext.getRoot().get(propertyName), token.getValue())); - }); - } - - protected Optional handleLocationReference(OpenmrsFhirCriteriaContext criteriaContext, - @Nonnull String locationAlias, ReferenceAndListParam locationReference) { - - if (locationReference == null) { - return Optional.empty(); - } - - return handleAndListParam(locationReference, token -> { - if (token.getChain() != null) { - switch (token.getChain()) { - case Location.SP_NAME: - return propertyLike(criteriaContext, String.format("%s.name", locationAlias), token.getValue()); - case Location.SP_ADDRESS_CITY: - return propertyLike(criteriaContext, String.format("%s.cityVillage", locationAlias), - token.getValue()); - case Location.SP_ADDRESS_STATE: - return propertyLike(criteriaContext, String.format("%s.stateProvince", locationAlias), - token.getValue()); - case Location.SP_ADDRESS_POSTALCODE: - return propertyLike(criteriaContext, String.format("%s.postalCode", locationAlias), - token.getValue()); - case Location.SP_ADDRESS_COUNTRY: - return propertyLike(criteriaContext, String.format("%s.country", locationAlias), token.getValue()); - } - } else { - return Optional.of(criteriaContext.getCriteriaBuilder() - .equal(criteriaContext.getRoot().get(String.format("%s.uuid", locationAlias)), token.getValue())); - } - - return Optional.empty(); - }); - } - - protected void handleParticipantReference(OpenmrsFhirCriteriaContext criteriaContext, - ReferenceAndListParam participantReference) { - if (participantReference != null) { - if (lacksAlias(criteriaContext, "ep")) { - return; - } - - handleAndListParam(participantReference, participantToken -> { - if (participantToken.getChain() != null) { - switch (participantToken.getChain()) { - case Practitioner.SP_IDENTIFIER: - if (lacksAlias(criteriaContext, "p")) { - criteriaContext.getRoot().join("ep.provider").alias("p"); - } - return Optional.of(criteriaContext.getCriteriaBuilder() - .like(criteriaContext.getRoot().get("p.identifier"), participantToken.getValue())); - case Practitioner.SP_GIVEN: - if ((lacksAlias(criteriaContext, "pro") - && (lacksAlias(criteriaContext, "ps") && (lacksAlias(criteriaContext, "pn"))))) { - criteriaContext.getRoot().join("ep.provider").alias("pro"); - criteriaContext.getRoot().join("pro.person").alias("ps"); - criteriaContext.getRoot().join("ps.names").alias("pn"); - } - return Optional.of(criteriaContext.getCriteriaBuilder() - .like(criteriaContext.getRoot().get("pn.givenName"), participantToken.getValue())); - case Practitioner.SP_FAMILY: - if ((lacksAlias(criteriaContext, "pro") - && (lacksAlias(criteriaContext, "ps") && (lacksAlias(criteriaContext, "pn"))))) { - criteriaContext.getRoot().join("ep.provider").alias("pro"); - criteriaContext.getRoot().join("pro.person").alias("ps"); - criteriaContext.getRoot().join("ps.names").alias("pn"); - } - return Optional.of(criteriaContext.getCriteriaBuilder() - .like(criteriaContext.getRoot().get("pn.familyName"), participantToken.getValue())); - case Practitioner.SP_NAME: - if ((lacksAlias(criteriaContext, "pro") - && (lacksAlias(criteriaContext, "ps") && (lacksAlias(criteriaContext, "pn"))))) { - criteriaContext.getRoot().join("ep.provider").alias("pro"); - criteriaContext.getRoot().join("pro.person").alias("ps"); - criteriaContext.getRoot().join("ps.names").alias("pn"); - } - - List> predicateList = new ArrayList<>(); - - for (String token : StringUtils.split(participantToken.getValue(), " \t,")) { - predicateList.add(propertyLike(criteriaContext, "pn.givenName", token)); - predicateList.add(propertyLike(criteriaContext, "pn.middleName", token)); - predicateList.add(propertyLike(criteriaContext, "pn.familyName", token)); - } - - return Optional.of(criteriaContext.getCriteriaBuilder().or(toCriteriaArray(predicateList))); - } - } else { - if (lacksAlias(criteriaContext, "pro")) { - criteriaContext.getRoot().join("ep.provider").alias("pro"); - } - return Optional.of(criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("pro.uuid"), - participantToken.getValue())); - } - - return Optional.empty(); - }).ifPresent(criteriaContext::addPredicate); - criteriaContext.finalizeQuery(); - } - } - - //Added this method to allow handling classes with provider instead of encounterProvider - protected void handleProviderReference(OpenmrsFhirCriteriaContext criteriaContext, - ReferenceAndListParam providerReference) { - if (providerReference != null) { - criteriaContext.getCriteriaQuery().select(criteriaContext.getRoot().get("orderer")); - handleAndListParam(providerReference, participantToken -> { - if (participantToken.getChain() != null) { - switch (participantToken.getChain()) { - case Practitioner.SP_IDENTIFIER: - return Optional.of(criteriaContext.getCriteriaBuilder() - .like(criteriaContext.getRoot().get("or.identifier"), participantToken.getValue())); - case Practitioner.SP_GIVEN: - if ((lacksAlias(criteriaContext, "ps") && (lacksAlias(criteriaContext, "pn")))) { - criteriaContext.getRoot().join("or.person").alias("ps"); - criteriaContext.getRoot().join("ps.names").alias("pn"); - } - return Optional.of(criteriaContext.getCriteriaBuilder() - .like(criteriaContext.getRoot().get("pn.givenName"), participantToken.getValue())); - case Practitioner.SP_FAMILY: - if ((lacksAlias(criteriaContext, "ps") && (lacksAlias(criteriaContext, "pn")))) { - criteriaContext.getRoot().join("or.person").alias("ps"); - criteriaContext.getRoot().join("ps.names").alias("pn"); - } - return Optional.of(criteriaContext.getCriteriaBuilder() - .like(criteriaContext.getRoot().get("pn.familyName"), participantToken.getValue())); - case Practitioner.SP_NAME: - if ((lacksAlias(criteriaContext, "ps") && (lacksAlias(criteriaContext, "pn")))) { - criteriaContext.getRoot().join("or.person").alias("ps"); - criteriaContext.getRoot().join("ps.names").alias("pn"); - } - - List> predicateList = new ArrayList<>(); - - for (String token : StringUtils.split(participantToken.getValue(), " \t,")) { - predicateList.add(propertyLike(criteriaContext, "pn.givenName", token)); - predicateList.add(propertyLike(criteriaContext, "pn.middleName", token)); - predicateList.add(propertyLike(criteriaContext, "pn.familyName", token)); - } - - return Optional.of(criteriaContext.getCriteriaBuilder().or(toCriteriaArray(predicateList))); - } - } else { - return Optional.of(criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("ro.uuid"), - participantToken.getValue())); - } - - return Optional.empty(); - }).ifPresent(criteriaContext::addPredicate); - } - } - - protected Optional handleCodeableConcept(OpenmrsFhirCriteriaContext criteriaContext, - TokenAndListParam concepts, @Nonnull String conceptAlias, @Nonnull String conceptMapAlias, - @Nonnull String conceptReferenceTermAlias) { - if (concepts == null) { - return Optional.empty(); - } - - return handleAndListParamBySystem(concepts, (system, tokens) -> { - if (system.isEmpty()) { - criteriaContext.getCriteriaBuilder() - .literal(tokensToParams(tokens).map(NumberUtils::toInt).collect(Collectors.toList())); - return Optional.of( - criteriaContext - .getCriteriaBuilder().or( - criteriaContext.getCriteriaBuilder() - .in(criteriaContext.getRoot().get(String.format("%s.conceptId", conceptAlias)) - .in(criteriaContext.getCriteriaBuilder() - .literal(tokensToParams(tokens).map(NumberUtils::toInt) - .collect(Collectors.toList())))), - criteriaContext.getCriteriaBuilder() - .in(criteriaContext.getRoot().get(String.format("%s.uuid", conceptAlias)) - .in(criteriaContext.getCriteriaBuilder().literal(tokensToList(tokens)))))); - - } else { - if (lacksAlias(criteriaContext, conceptMapAlias)) { - criteriaContext.getRoot().join(String.format("%s.conceptMappings", conceptAlias)).alias(conceptMapAlias); - criteriaContext.getRoot().join(String.format("%s.conceptReferenceTerm", conceptMapAlias)) - .alias(conceptReferenceTermAlias); - } - - return Optional - .of(generateSystemQuery(criteriaContext, system, tokensToList(tokens), conceptReferenceTermAlias)); - } - }); - } - - protected void handleNames(OpenmrsFhirCriteriaContext criteriaContext, StringAndListParam name, - StringAndListParam given, StringAndListParam family) { - handleNames(criteriaContext, name, given, family, null); - } - - protected void handleNames(OpenmrsFhirCriteriaContext criteriaContext, StringAndListParam name, - StringAndListParam given, StringAndListParam family, String personAlias) { - - if (name == null && given == null && family == null) { - return; - } - - if (lacksAlias(criteriaContext, "pn")) { - if (StringUtils.isNotBlank(personAlias)) { - criteriaContext.getRoot() - .join(String.format("%s.names", personAlias), javax.persistence.criteria.JoinType.INNER).alias("pn"); - criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("pn.voided"), false); - } else { - criteriaContext.getRoot().join("names", javax.persistence.criteria.JoinType.INNER).alias("pn"); - criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("pn.voided"), false); - } - } - - if (name != null) { - handleAndListParamAsStream(name, - (nameParam) -> Arrays.stream(StringUtils.split(nameParam.getValue(), " \t,")) - .map(token -> new StringParam().setValue(token).setExact(nameParam.isExact()) - .setContains(nameParam.isContains())) - .map(tokenParam -> Arrays.asList(propertyLike(criteriaContext, "pn.givenName", tokenParam), - propertyLike(criteriaContext, "pn.middleName", tokenParam), - propertyLike(criteriaContext, "pn.familyName", tokenParam))) - .flatMap(Collection::stream)).ifPresent(criteriaContext::addPredicate); - criteriaContext.finalizeQuery(); - } - - if (given != null) { - handleAndListParam(given, (givenName) -> propertyLike(criteriaContext, "pn.givenName", givenName)) - .ifPresent(criteriaContext::addPredicate); - criteriaContext.finalizeQuery(); - } - - if (family != null) { - handleAndListParam(family, (familyName) -> propertyLike(criteriaContext, "pn.familyName", familyName)) - .ifPresent(criteriaContext::addPredicate); - criteriaContext.finalizeQuery(); - } - } - - protected void handlePatientReference(OpenmrsFhirCriteriaContext criteriaContext, - ReferenceAndListParam patientReference) { - handlePatientReference(criteriaContext, patientReference, "patient"); - } - - protected void handlePatientReference(OpenmrsFhirCriteriaContext criteriaContext, - ReferenceAndListParam patientReference, String associationPath) { - if (patientReference != null) { - criteriaContext.addJoin(associationPath,"p").finalizeQuery(); - - handleAndListParam(patientReference, patientToken -> { - if (patientToken.getChain() != null) { - switch (patientToken.getChain()) { - case Patient.SP_IDENTIFIER: - if (lacksAlias(criteriaContext, "pi")) { - criteriaContext.addJoin("p.identifiers","pi").finalizeQuery(); - } - return Optional.of(criteriaContext.getCriteriaBuilder() - .like(criteriaContext.getRoot().get("pi.identifier"), patientToken.getValue())); - case Patient.SP_GIVEN: - if (lacksAlias(criteriaContext, "pn")) { - criteriaContext.addJoin("p.names","pn").finalizeQuery(); - } - return Optional.of(criteriaContext.getCriteriaBuilder() - .like(criteriaContext.getRoot().get("pn.givenName"), patientToken.getValue())); - case Patient.SP_FAMILY: - if (lacksAlias(criteriaContext, "pn")) { - criteriaContext.addJoin("p.names","pn").finalizeQuery(); - } - return Optional.of(criteriaContext.getCriteriaBuilder() - .like(criteriaContext.getRoot().get("pn.familyName"), patientToken.getValue())); - case Patient.SP_NAME: - if (lacksAlias(criteriaContext, "pn")) { - criteriaContext.addJoin("p.names","pn").finalizeQuery(); - } - - List> criterionList = new ArrayList<>(); - - for (String token : StringUtils.split(patientToken.getValue(), " \t,")) { - criterionList.add(propertyLike(criteriaContext, "pn.givenName", token)); - criterionList.add(propertyLike(criteriaContext, "pn.middleName", token)); - criterionList.add(propertyLike(criteriaContext, "pn.familyName", token)); - } - return Optional.of(criteriaContext.getCriteriaBuilder().or(toCriteriaArray(criterionList))); - } - } else { - return Optional.of(criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("p.uuid"), - patientToken.getValue())); - } - - return Optional.empty(); - }).ifPresent(criteriaContext::addPredicate); - } - } - - protected Optional handleCommonSearchParameters(OpenmrsFhirCriteriaContext criteriaContext, - List> theCommonParams) { - List> criterionList = new ArrayList<>(); - - for (PropParam commonSearchParam : theCommonParams) { - switch (commonSearchParam.getPropertyName()) { - case FhirConstants.ID_PROPERTY: - criterionList.add(handleAndListParam((TokenAndListParam) commonSearchParam.getParam(), - param -> Optional.of(criteriaContext.getCriteriaBuilder() - .equal(criteriaContext.getRoot().get("uuid"), param.getValue())))); - break; - case FhirConstants.LAST_UPDATED_PROPERTY: - criterionList.add(handleLastUpdated(criteriaContext, (DateRangeParam) commonSearchParam.getParam())); - break; - } - } - return Optional.of(criteriaContext.getCriteriaBuilder().and(toCriteriaArray(criterionList.stream()))); - } - - /** - * This function should be overridden by implementations. It is used to return a criterion for - * _lastUpdated from resources where there are multiple properties to be considered. - * - * @param param the DateRangeParam used to query for _lastUpdated - * @return an optional criterion for the query - */ - protected abstract Optional handleLastUpdated(OpenmrsFhirCriteriaContext criteriaContext, - DateRangeParam param); - - protected Optional handlePersonAddress(OpenmrsFhirCriteriaContext criteriaContext, String aliasPrefix, - StringAndListParam city, StringAndListParam state, StringAndListParam postalCode, StringAndListParam country) { - if (city == null && state == null && postalCode == null && country == null) { - return Optional.empty(); - } - - List> predicateList = new ArrayList<>(); - - if (city != null) { - predicateList.add(handleAndListParam(city, - c -> propertyLike(criteriaContext, String.format("%s.cityVillage", aliasPrefix), c))); - } - - if (state != null) { - predicateList.add(handleAndListParam(state, - c -> propertyLike(criteriaContext, String.format("%s.stateProvince", aliasPrefix), c))); - } - - if (postalCode != null) { - predicateList.add(handleAndListParam(postalCode, - c -> propertyLike(criteriaContext, String.format("%s.postalCode", aliasPrefix), c))); - } - - if (country != null) { - predicateList.add(handleAndListParam(country, - c -> propertyLike(criteriaContext, String.format("%s.country", aliasPrefix), c))); - } - - return Optional.of(criteriaContext.getCriteriaBuilder().and(toCriteriaArray(predicateList.stream()))); - } - - protected Optional handleMedicationReference(OpenmrsFhirCriteriaContext criteriaContext, - @Nonnull String medicationAlias, ReferenceAndListParam medicationReference) { - if (medicationReference == null) { - return Optional.empty(); - } - - return handleAndListParam(medicationReference, token -> Optional.of(criteriaContext.getCriteriaBuilder() - .equal(criteriaContext.getRoot().get(String.format("%s.uuid", medicationAlias)), token.getIdPart()))); - } - - protected Optional handleMedicationRequestReference(OpenmrsFhirCriteriaContext criteriaContext, - @Nonnull String drugOrderAlias, ReferenceAndListParam drugOrderReference) { - if (drugOrderReference == null) { - return Optional.empty(); - } - - return handleAndListParam(drugOrderReference, token -> Optional.of(criteriaContext.getCriteriaBuilder() - .equal(criteriaContext.getRoot().get(String.format("%s.uuid", drugOrderAlias)), token.getIdPart()))); - } - - /** - * Use this method to properly implement sorting for your query. Note that for this method to work, - * you must override one or more of: {@link #paramToProps(OpenmrsFhirCriteriaContext, SortState)}, - * {@link #paramToProps(OpenmrsFhirCriteriaContext, String)}, or - * {@link #paramToProp(OpenmrsFhirCriteriaContext, String)}. - * - * @param criteriaContext The {@link OpenmrsFhirCriteriaContext} for the current query - * @param sort the {@link SortSpec} which defines the sorting to be translated - */ - protected void handleSort(OpenmrsFhirCriteriaContext criteriaContext, SortSpec sort) { - handleSort(criteriaContext, sort, this::paramToProps) - .ifPresent(l -> l.forEach(criteriaContext.getCriteriaQuery()::orderBy)); - } - - 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().criteriaBuilder(criteriaContext.getCriteriaBuilder()) - .sortOrder(sortSpec.getOrder()).parameter(sortSpec.getParamName().toLowerCase()).build(); - - Collection orders = paramToProp.apply(criteriaContext, state); - if (orders != null) { - orderings.addAll(orders); - } - - sortSpec = sortSpec.getChain(); - } - - if (orderings.isEmpty()) { - return Optional.empty(); - } - - return Optional.of(orderings); - } - - @SuppressWarnings("unchecked") - protected Predicate generateSystemQuery(OpenmrsFhirCriteriaContext criteriaContext, String system, - List codes, String conceptReferenceTermAlias) { - //detached criteria - Specification spec = (root, query, - cb) -> (Predicate) query.select(root.get("conceptSource")).where(cb.equal(root.get("url"), system)); - - criteriaContext.getCriteriaQuery().where(spec.toPredicate((Root) criteriaContext.getRoot(), - (CriteriaQuery) criteriaContext.getCriteriaQuery(), criteriaContext.getCriteriaBuilder())); - - if (codes.size() > 1) { - return criteriaContext.getCriteriaBuilder().and( - criteriaContext.getCriteriaBuilder().equal( - criteriaContext.getRoot().get(String.format("%s.conceptSource", conceptReferenceTermAlias)), - criteriaContext.getCriteriaQuery()), - criteriaContext.getCriteriaBuilder().in(criteriaContext.getRoot() - .get(String.format("%s.code", conceptReferenceTermAlias)).get(codes.toString()))); - } else { - return criteriaContext.getCriteriaBuilder().and( - criteriaContext.getCriteriaBuilder().equal( - criteriaContext.getRoot().get(String.format("%s.conceptSource", conceptReferenceTermAlias)), - criteriaContext.getCriteriaQuery()), - criteriaContext.getCriteriaBuilder().equal( - criteriaContext.getRoot().get(String.format("%s.code", conceptReferenceTermAlias)), codes.get(0))); - } - } - - protected Predicate generateActiveOrderQuery(OpenmrsFhirCriteriaContext criteriaContext, String path, - Date onDate) { - // ACTIVE = date activated null or less than or equal to current datetime, date stopped null or in the future, auto expire date null or in the future - return criteriaContext.getCriteriaBuilder().and( - criteriaContext.getCriteriaBuilder().or( - criteriaContext.getCriteriaBuilder().isNull(criteriaContext.getRoot().join(path).get("dateActivated")), - criteriaContext.getCriteriaBuilder().lessThan(criteriaContext.getRoot().join(path).get("dateActivated"), - onDate)), - criteriaContext.getCriteriaBuilder().or( - criteriaContext.getCriteriaBuilder().isNull(criteriaContext.getRoot().join(path).get("dateStopped")), - criteriaContext.getCriteriaBuilder().greaterThan(criteriaContext.getRoot().join(path).get("dateStopped"), - onDate)), - criteriaContext.getCriteriaBuilder().or( - criteriaContext.getCriteriaBuilder().isNull(criteriaContext.getRoot().join(path).get("autoExpireDate")), - criteriaContext.getCriteriaBuilder().greaterThan(criteriaContext.getRoot().join(path).get("autoExpireDate"), - onDate))); - } - - protected Predicate generateActiveOrderQuery(OpenmrsFhirCriteriaContext criteriaContext, String path) { - return generateActiveOrderQuery(criteriaContext, path, new Date()); - } - - protected Predicate generateActiveOrderQuery(OpenmrsFhirCriteriaContext criteriaContext, Date onDate) { - return generateActiveOrderQuery(criteriaContext, "", onDate); - } - - protected Predicate generateActiveOrderQuery(OpenmrsFhirCriteriaContext criteriaContext) { - return generateActiveOrderQuery(criteriaContext, new Date()); - } - - protected Predicate generateNotCancelledOrderQuery(OpenmrsFhirCriteriaContext criteriaContext) { - return generateNotCancelledOrderQuery(criteriaContext, ""); - } - - protected Predicate generateNotCancelledOrderQuery(OpenmrsFhirCriteriaContext criteriaContext, String path) { - Date now = new Date(); - - return criteriaContext.getCriteriaBuilder().or( - criteriaContext.getCriteriaBuilder().isNull(criteriaContext.getRoot().join(path).get("dateStopped")), - criteriaContext.getCriteriaBuilder().greaterThan(criteriaContext.getRoot().join(path).get("dateStopped"), now)); - } - - protected TokenOrListParam convertStringStatusToBoolean(TokenOrListParam statusParam) { - if (statusParam != null) { - return handleOrListParam(statusParam).map(s -> { - switch (s.getValue()) { - case "active": - return Optional.of("false"); - case "inactive": - return Optional.of("true"); - default: - return Optional.empty(); - } - }).filter(Optional::isPresent).map(Optional::get).collect(TokenOrListParam::new, - (tp, v) -> tp.add(String.valueOf(v)), (tp1, tp2) -> tp2.getListAsCodings().forEach(tp1::add)); - } - - return null; - } - - protected TokenAndListParam convertStringStatusToBoolean(TokenAndListParam statusParam) { - if (statusParam != null) { - return handleAndListParam(statusParam).map(this::convertStringStatusToBoolean).collect(TokenAndListParam::new, - TokenAndListParam::addAnd, (tp1, tp2) -> tp2.getValuesAsQueryTokens().forEach(tp1::addAnd)); - } - - return null; - } - - /** - * This function should be overridden by implementations. It is used to map FHIR parameter names to - * their corresponding values in the query. - * - * @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()); - if (prop != null) { - switch (sortState.getSortOrder()) { - case ASC: - return prop.stream().map(s -> criteriaContext.getCriteriaBuilder().asc(criteriaContext.getRoot().get(s))) - .collect(Collectors.toList()); - case DESC: - return prop.stream() - .map(s -> criteriaContext.getCriteriaBuilder().desc(criteriaContext.getRoot().get(s))) - .collect(Collectors.toList()); - } - } - - return null; - } - - /** - * This function should be overridden by implementations. It is used to map FHIR parameter names to - * properties where there is only a single property. - * - * @param param the FHIR parameter to map - * @return the name of the corresponding property from the current query - */ - protected Collection paramToProps(OpenmrsFhirCriteriaContext criteriaContext, @Nonnull String param) { - String prop = paramToProp(criteriaContext, param); - - if (prop != null) { - return Collections.singleton(prop); - } - - return null; - } - - /** - * This function should be overridden by implementations. It is used to map FHIR parameter names to - * properties where there is only a single property. - * - * @param param the FHIR parameter to map - * @return the name of the corresponding property from the current query - */ - protected String paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @Nonnull String param) { - return null; - } - - protected Optional propertyLike(OpenmrsFhirCriteriaContext criteriaContext, - @Nonnull String propertyName, String value) { - if (value == null) { - return Optional.empty(); - } - - return propertyLike(criteriaContext, propertyName, new StringParam(value)); - } - - protected Optional propertyLike(OpenmrsFhirCriteriaContext criteriaContext, - @Nonnull String propertyName, StringParam param) { - if (param == null) { - return Optional.empty(); - } - - Predicate likePredicate; - if (param.isExact()) { - likePredicate = criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get(propertyName), - param.getValue()); - } else if (param.isContains()) { - likePredicate = criteriaContext.getCriteriaBuilder().like(criteriaContext.getRoot().get(propertyName), - "%" + param.getValue() + "%"); - } else { - likePredicate = criteriaContext.getCriteriaBuilder().like(criteriaContext.getRoot().get(propertyName), - param.getValue() + "%"); - } - - return Optional.of(likePredicate); - } - - protected List tokensToList(List tokens) { - return tokensToParams(tokens).collect(Collectors.toList()); - } - - protected Stream tokensToParams(List tokens) { - return tokens.stream().map(TokenParam::getValue); - } - - private String groupBySystem(@Nonnull TokenParam token) { - return StringUtils.trimToEmpty(token.getSystem()); - } - - protected , U extends IQueryParameterType> Stream handleAndListParam( - IQueryParameterAnd andListParameter) { - return andListParameter.getValuesAsQueryTokens().stream(); - } - - protected Stream handleOrListParam(IQueryParameterOr orListParameter) { - return orListParameter.getValuesAsQueryTokens().stream(); - } - - @SafeVarargs - @SuppressWarnings("unused") - protected final Predicate[] toCriteriaArray(Optional... predicate) { - return toCriteriaArray(Arrays.stream(predicate)); - } - - protected Predicate[] toCriteriaArray(Collection> collection) { - return toCriteriaArray(collection.stream()); - } - - protected Predicate[] toCriteriaArray(Stream> predicateStream) { - return predicateStream.filter(Optional::isPresent).map(Optional::get).toArray(Predicate[]::new); - } - - /** - * This object is used to store the state of the sorting - */ - @Data - @Builder - @EqualsAndHashCode - public static final class SortState { - - private CriteriaBuilder criteriaBuilder; - - private SortOrderEnum sortOrder; - - private String parameter; - } - - protected Optional handleAgeByDateProperty(OpenmrsFhirCriteriaContext criteriaContext, - @Nonnull String datePropertyName, @Nonnull QuantityParam age) { - BigDecimal value = age.getValue(); - if (value == null) { - throw new IllegalArgumentException("Age value should be provided in " + age); - } - - String unit = age.getUnits(); - if (unit == null) { - throw new IllegalArgumentException("Age unit should be provided in " + age); - } - - LocalDateTime localDateTime = localDateTimeFactory.now(); - - TemporalAmount temporalAmount; - TemporalUnit temporalUnit; - // TODO check if HAPI FHIR already defines these constant strings. These are mostly from - // http://www.hl7.org/fhir/valueset-age-units.html with the exception of "s" which is not - // listed but was seen in FHIR examples: http://www.hl7.org/fhir/datatypes-examples.html#Quantity - switch (unit) { - case "s": - temporalUnit = ChronoUnit.SECONDS; - temporalAmount = Duration.ofSeconds(value.longValue()); - break; - case "min": - temporalUnit = ChronoUnit.MINUTES; - temporalAmount = Duration.ofMinutes(value.longValue()); - break; - case "h": - temporalUnit = ChronoUnit.HOURS; - temporalAmount = Duration.ofHours(value.longValue()); - break; - case "d": - temporalUnit = ChronoUnit.DAYS; - temporalAmount = Period.ofDays(value.intValue()); - break; - case "wk": - temporalUnit = ChronoUnit.WEEKS; - temporalAmount = Period.ofWeeks(value.intValue()); - break; - case "mo": - temporalUnit = ChronoUnit.MONTHS; - temporalAmount = Period.ofMonths(value.intValue()); - break; - case "a": - temporalUnit = ChronoUnit.YEARS; - temporalAmount = Period.ofYears(value.intValue()); - break; - default: - throw new IllegalArgumentException( - "Invalid unit " + unit + " in age " + age + " should be one of 'min', 'h', 'd', 'wk', 'mo', 'a'"); - } - - localDateTime = localDateTime.minus(temporalAmount); - - ParamPrefixEnum prefix = age.getPrefix(); - if (prefix == null) { - prefix = ParamPrefixEnum.EQUAL; - } - - if (prefix == ParamPrefixEnum.EQUAL || prefix == ParamPrefixEnum.NOT_EQUAL) { - // Create a range for the targeted unit; the interval length is determined by the unit and - // its center is `offsetSeconds` in the past. - final long offset; - - // Duration only supports hours as a chunk of seconds - if (temporalUnit == ChronoUnit.HOURS) { - offset = temporalAmount.get(ChronoUnit.SECONDS) / (2 * 3600); - } else { - offset = temporalAmount.get(temporalUnit) / 2; - } - - LocalDateTime lowerBoundDateTime = LocalDateTime.from(localDateTime).minus(Duration.of(offset, temporalUnit)); - Date lowerBound = Date.from(lowerBoundDateTime.atZone(ZoneId.systemDefault()).toInstant()); - - LocalDateTime upperBoundDateTime = LocalDateTime.from(localDateTime).plus(offset, temporalUnit); - Date upperBound = Date.from(upperBoundDateTime.atZone(ZoneId.systemDefault()).toInstant()); - - if (prefix == ParamPrefixEnum.EQUAL) { - return Optional.ofNullable(criteriaContext.getCriteriaBuilder().and( - criteriaContext.getCriteriaBuilder() - .greaterThanOrEqualTo(criteriaContext.getRoot().get(datePropertyName), lowerBound), - criteriaContext.getCriteriaBuilder().lessThanOrEqualTo(criteriaContext.getRoot().get(datePropertyName), - upperBound))); - } else { - return Optional.ofNullable(criteriaContext.getCriteriaBuilder() - .not(criteriaContext.getCriteriaBuilder().and( - criteriaContext.getCriteriaBuilder() - .greaterThanOrEqualTo(criteriaContext.getRoot().get(datePropertyName), lowerBound), - criteriaContext.getCriteriaBuilder() - .lessThanOrEqualTo(criteriaContext.getRoot().get(datePropertyName), upperBound)))); - } - } - - switch (prefix) { - case LESSTHAN_OR_EQUALS: - case LESSTHAN: - case STARTS_AFTER: - return Optional.ofNullable(criteriaContext.getCriteriaBuilder().greaterThanOrEqualTo( - criteriaContext.getRoot().get("datePropertyName"), - Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()))); - case GREATERTHAN_OR_EQUALS: - case GREATERTHAN: - return Optional.ofNullable( - criteriaContext.getCriteriaBuilder().lessThanOrEqualTo(criteriaContext.getRoot().get("datePropertyName"), - Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()))); - // Ignoring ENDS_BEFORE as it is not meaningful for age. - } - - return Optional.empty(); - } - - protected OpenmrsFhirCriteriaContext createCriteriaContext(Class rootType) { - EntityManager em = sessionFactory.getCurrentSession(); - CriteriaBuilder cb = em.getCriteriaBuilder(); - @SuppressWarnings("unchecked") - CriteriaQuery cq = (CriteriaQuery) cb.createQuery(rootType); - @SuppressWarnings("unchecked") - Root root = (Root) cq.from(rootType); - - return new OpenmrsFhirCriteriaContext<>(em, cb, cq, root); - } - - protected interface Specification { - - /** - * Creates a WHERE clause for a query of the referenced entity in form of a {@link Predicate} for - * the given {@link Root} and {@link CriteriaQuery}. - * - * @param root must not be {@literal null}. - * @param query must not be {@literal null}. - * @param cb must not be {@literal null}. - * @return a {@link Predicate}, may be {@literal null}. to - * spring-data jpa implementation - */ - Predicate toPredicate(@Nonnull Root root, @Nonnull CriteriaQuery query, @Nonnull CriteriaBuilder cb); - } - + + private static final BigDecimal APPROX_RANGE = new BigDecimal("0.1"); + + @Autowired + private LocalDateTimeFactory localDateTimeFactory; + + @Autowired + @Getter(AccessLevel.PUBLIC) + @Setter(AccessLevel.PUBLIC) + @Qualifier("sessionFactory") + protected SessionFactory sessionFactory; + + /** + * Converts an {@link Iterable} to a {@link Stream} + * + * @param iterable the iterable + * @param any type + * @return a stream containing the same objects as the iterable + */ + @SuppressWarnings("unused") + protected static Stream stream(Iterable iterable) { + return stream(iterable.iterator()); + } + + /** + * Converts an {@link Iterator} to a {@link Stream} + * + * @param iterator the iterator + * @param any type + * @return a stream containing the same objects as the iterator + */ + protected static Stream stream(Iterator iterator) { + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false); + } + + /** + * Converts an {@link Iterable} to a {@link Stream} operated on in parallel + * + * @param iterable the iterable + * @param any type + * @return a stream containing the same objects as the iterable + */ + @SuppressWarnings("unused") + protected static Stream parallelStream(Iterable iterable) { + return parallelStream(iterable.iterator()); + } + + /** + * Converts an {@link Iterator} to a {@link Stream} operated on in parallel + * + * @param iterator the iterator + * @param any type + * @return a stream containing the same objects as the iterator + */ + protected static Stream parallelStream(Iterator iterator) { + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), true); + } + + /** + * Determines whether the given criteria object already has a given alias. This is useful to + * determine whether a mapping has already been made or whether a given alias is already in use. + * + * @param alias the alias to look for + * @return true if the alias exists in this criteria object, false otherwise + */ + protected boolean lacksAlias(@Nonnull OpenmrsFhirCriteriaContext criteriaContext, @Nonnull String alias) { + return !criteriaContext.hasAlias(alias); + } + + /** + * Determines whether any of the {@link CriteriaImpl.Subcriteria} objects returned by a given + * iterator are mapped to the specified alias. + * + * @param subcriteriaIterator an {@link Iterator} of {@link CriteriaImpl.Subcriteria} to check for + * the given alias + * @param alias the alias to look for + * @return true if any of the given subcriteria use the specified alias, false otherwise + */ + protected boolean containsAlias(Iterator subcriteriaIterator, @Nonnull String alias) { + return stream(subcriteriaIterator).noneMatch(sc -> sc.getAlias().equals(alias)); + } + + /** + * A generic handler for any subtype of {@link IQueryParameterAnd} which creates a criterion that + * represents the intersection of all the parameters contained + * + * @param andListParam the {@link IQueryParameterAnd} to handle + * @param handler a {@link Function} which maps a parameter to a {@link Criterion} + * @param the subtype of {@link IQueryParameterOr} that this {@link IQueryParameterAnd} contains + * @param the subtype of {@link IQueryParameterType} for this parameter + * @return the resulting criterion, which is the intersection of all the unions of contained + * parameters + */ + protected , U extends IQueryParameterType> Optional handleAndListParam(CriteriaBuilder criteriaBuilder, IQueryParameterAnd andListParam, Function> handler) { + if (andListParam == null) { + return Optional.empty(); + } + + return Optional.ofNullable(criteriaBuilder.and(toCriteriaArray(handleAndListParam(andListParam).map(orListParam -> handleOrListParam(orListParam, handler))))); + } + + @SuppressWarnings("unused") + protected , U extends IQueryParameterType> Optional handleAndListParamBy(CriteriaBuilder criteriaBuilder, IQueryParameterAnd andListParam, Function, Optional> handler) { + if (andListParam == null) { + return Optional.empty(); + } + + return Optional.of(criteriaBuilder.and((toCriteriaArray(handleAndListParam(andListParam).map(handler))))); + } + + protected , U extends IQueryParameterType> Optional handleAndListParamAsStream(CriteriaBuilder criteriaBuilder, IQueryParameterAnd andListParam, Function>> handler) { + if (andListParam == null) { + return Optional.empty(); + } + + return Optional.of(criteriaBuilder.and((toCriteriaArray(handleAndListParam(andListParam).map(orListParam -> handleOrListParamAsStream(criteriaBuilder, orListParam, handler)))))); + } + + /** + * A generic handler for any subtype of {@link IQueryParameterOr} which creates a criterion that + * represents the union of all the parameters + * + * @param orListParam the {@link IQueryParameterOr} to handle + * @param handler a {@link Function} which maps a parameter to a {@link Criterion} + * @param the subtype of {@link IQueryParameterType} for this parameter + * @return the resulting criterion, which is the union of all contained parameters + */ + protected Optional handleOrListParam(CriteriaBuilder criteriaBuilder, IQueryParameterOr orListParam, Function> handler) { + if (orListParam == null) { + return Optional.empty(); + } + + return Optional.of(criteriaBuilder.or(toCriteriaArray(handleOrListParam(criteriaBuilder, orListParam).map(handler)))); + } + + protected Optional handleOrListParamAsStream(CriteriaBuilder criteriaBuilder, IQueryParameterOr orListParam, Function>> handler) { + if (orListParam == null) { + return Optional.empty(); + } + + return Optional.of(criteriaBuilder.or(toCriteriaArray(handleOrListParam(criteriaBuilder, orListParam).flatMap(handler)))); + } + + /** + * Handler for a {@link IQueryParameterAnd} of {@link TokenParam}s where tokens should be grouped + * and handled according to the system they belong to This is useful for queries drawing their + * values from CodeableConcepts + * + * @param andListParam the {@link IQueryParameterAnd} to handle + * @param systemTokenHandler a {@link BiFunction} taking the system and associated list of + * {@link TokenParam}s and returning a {@link Criterion} + * @return a {@link Criterion} representing the intersection of all produced {@link Criterion} + */ + protected > Optional handleAndListParamBySystem(CriteriaBuilder criteriaBuilder, IQueryParameterAnd andListParam, BiFunction, Optional> systemTokenHandler) { + if (andListParam == null) { + return Optional.empty(); + } + + return Optional.of(criteriaBuilder.and(toCriteriaArray(handleAndListParam(andListParam).map(param -> handleOrListParamBySystem(criteriaBuilder, param, systemTokenHandler))))); + } + + /** + * Handler for a {@link IQueryParameterOr} of {@link TokenParam}s where tokens should be grouped and + * handled according to the system they belong to This is useful for queries drawing their values + * from CodeableConcepts + * + * @param orListParam the {@link IQueryParameterOr} to handle + * @param systemTokenHandler a {@link BiFunction} taking the system and associated list of + * {@link TokenParam}s and returning a {@link Criterion} + * @return a {@link Criterion} representing the union of all produced {@link Criterion} + */ + protected Optional handleOrListParamBySystem(CriteriaBuilder criteriaBuilder, IQueryParameterOr orListParam, BiFunction, Optional> systemTokenHandler) { + + if (orListParam == null) { + return Optional.empty(); + } + + return Optional.of(criteriaBuilder.or(toCriteriaArray(handleOrListParam(orListParam).collect(Collectors.groupingBy(this::groupBySystem)).entrySet().stream().map(e -> systemTokenHandler.apply(e.getKey(), e.getValue()))))); + } + + /** + * Handler for a {@link TokenOrListParam} that represents boolean values + * + * @param propertyName the name of the property in the query to use + * @param booleanToken the {@link TokenOrListParam} to handle + * @return a {@link Criterion} to be added to the query indicating that the property matches the + * given value + */ + protected Optional handleBoolean(OpenmrsFhirCriteriaContext criteriaContext, String propertyName, TokenAndListParam booleanToken) { + if (booleanToken == null) { + return Optional.empty(); + } + + // note that we use a custom implementation here as Boolean.valueOf() and Boolean.parse() only determine whether + // the string matches "true". We could potentially be passed a non-valid Boolean value here. + return handleAndListParam(criteriaContext.getCriteriaBuilder(), booleanToken, token -> { + if (token.getValue().equalsIgnoreCase("true")) { + return handleBooleanProperty(criteriaContext, propertyName, true); + } else if (token.getValue().equalsIgnoreCase("false")) { + return handleBooleanProperty(criteriaContext, propertyName, false); + } + + return Optional.empty(); + }); + } + + protected Optional handleBooleanProperty(OpenmrsFhirCriteriaContext criteriaContext, String propertyName, boolean booleanVal) { + return Optional.of(criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get(propertyName), booleanVal)); + } + + /** + * A handler for a {@link DateRangeParam}, which represents an inclusive set of {@link DateParam}s + * + * @param propertyName the name of the property in the query to use + * @param dateRangeParam the {@link DateRangeParam} to handle + * @return a {@link Criterion} to be added to the query for the indicated date range + */ + protected Optional handleDateRange(OpenmrsFhirCriteriaContext criteriaContext, String propertyName, DateRangeParam dateRangeParam) { + if (dateRangeParam == null) { + return Optional.empty(); + } + + return Optional.ofNullable(criteriaContext.getCriteriaBuilder().and(toCriteriaArray(Stream.of(handleDate(criteriaContext, propertyName, dateRangeParam.getLowerBound()), handleDate(criteriaContext, propertyName, dateRangeParam.getUpperBound()))))); + } + + /** + * A handler for a {@link DateParam}, which represents a day and an comparator + * + * @param propertyName the name of the property in the query to use + * @param dateParam the {@link DateParam} to handle + * @return a {@link Predicate} to be added to the query for the indicate date param + */ + protected Optional handleDate(OpenmrsFhirCriteriaContext criteriaContext, String propertyName, DateParam dateParam) { + if (dateParam == null) { + return Optional.empty(); + } + + int calendarPrecision = dateParam.getPrecision().getCalendarConstant(); + if (calendarPrecision > Calendar.SECOND) { + calendarPrecision = Calendar.SECOND; + } + // TODO We may want to not use the default Calendar + Date dateStart = DateUtils.truncate(dateParam.getValue(), calendarPrecision); + Date dateEnd = DateUtils.ceiling(dateParam.getValue(), calendarPrecision); + + // TODO This does not properly handle FHIR Periods and Timings, though its unclear if we are using those + // see https://www.hl7.org/fhir/search.html#date + switch (dateParam.getPrefix()) { + case EQUAL: + return Optional.of(criteriaContext.getCriteriaBuilder().and(criteriaContext.getCriteriaBuilder().greaterThanOrEqualTo(criteriaContext.getRoot().get(propertyName), dateStart), criteriaContext.getCriteriaBuilder().lessThan(criteriaContext.getRoot().get(propertyName), dateEnd))); + case NOT_EQUAL: + return Optional.of(criteriaContext.getCriteriaBuilder().not(criteriaContext.getCriteriaBuilder().and(criteriaContext.getCriteriaBuilder().greaterThanOrEqualTo(criteriaContext.getRoot().get(propertyName), dateStart), criteriaContext.getCriteriaBuilder().lessThan(criteriaContext.getRoot().get(propertyName), dateEnd)))); + case LESSTHAN_OR_EQUALS: + case LESSTHAN: + return Optional.of(criteriaContext.getCriteriaBuilder().lessThanOrEqualTo(criteriaContext.getRoot().get(propertyName), dateEnd)); + case GREATERTHAN_OR_EQUALS: + case GREATERTHAN: + return Optional.of(criteriaContext.getCriteriaBuilder().greaterThanOrEqualTo(criteriaContext.getRoot().get(propertyName), dateStart)); + case STARTS_AFTER: + return Optional.of(criteriaContext.getCriteriaBuilder().greaterThan(criteriaContext.getRoot().get(propertyName), dateEnd)); + case ENDS_BEFORE: + return Optional.of(criteriaContext.getCriteriaBuilder().lessThan(criteriaContext.getRoot().get(propertyName), dateEnd)); + } + + return Optional.empty(); + } + + protected Optional handleQuantity(OpenmrsFhirCriteriaContext criteriaContext, String propertyName, QuantityParam quantityParam) { + if (quantityParam == null) { + return Optional.empty(); + } + + BigDecimal value = quantityParam.getValue(); + if (quantityParam.getPrefix() == null || quantityParam.getPrefix() == ParamPrefixEnum.APPROXIMATE) { + String plainString = quantityParam.getValue().toPlainString(); + int dotIdx = plainString.indexOf('.'); + + BigDecimal approxRange = APPROX_RANGE.multiply(value); + if (dotIdx == -1) { + double lowerBound = value.subtract(approxRange).doubleValue(); + double upperBound = value.add(approxRange).doubleValue(); + return Optional.of(criteriaContext.getCriteriaBuilder().between(criteriaContext.getRoot().get(propertyName), lowerBound, upperBound)); + } else { + int precision = plainString.length() - (dotIdx); + double mul = Math.pow(10, -precision); + double val = mul * 5.0d; + double lowerBound = value.subtract(new BigDecimal(val)).doubleValue(); + double upperBound = value.add(new BigDecimal(val)).doubleValue(); + return Optional.of(criteriaContext.getCriteriaBuilder().between(criteriaContext.getRoot().get(propertyName), lowerBound, upperBound)); + } + } else { + double val = value.doubleValue(); + switch (quantityParam.getPrefix()) { + case EQUAL: + return Optional.of(criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get(propertyName), val)); + case NOT_EQUAL: + return Optional.of(criteriaContext.getCriteriaBuilder().notEqual(criteriaContext.getRoot().get(propertyName), val)); + case LESSTHAN_OR_EQUALS: + return Optional.of(criteriaContext.getCriteriaBuilder().lessThanOrEqualTo(criteriaContext.getRoot().get(propertyName), val)); + case LESSTHAN: + return Optional.of(criteriaContext.getCriteriaBuilder().lessThan(criteriaContext.getRoot().get(propertyName), val)); + case GREATERTHAN_OR_EQUALS: + return Optional.of(criteriaContext.getCriteriaBuilder().greaterThanOrEqualTo(criteriaContext.getRoot().get(propertyName), val)); + case GREATERTHAN: + return Optional.of(criteriaContext.getCriteriaBuilder().greaterThan(criteriaContext.getRoot().get(propertyName), val)); + } + } + + return Optional.empty(); + } + + protected Optional handleQuantity(OpenmrsFhirCriteriaContext criteriaContext, @Nonnull String propertyName, QuantityAndListParam quantityAndListParam) { + if (quantityAndListParam == null) { + return Optional.empty(); + } + + return handleAndListParam(criteriaContext.getCriteriaBuilder(), quantityAndListParam, quantityParam -> handleQuantity(criteriaContext, propertyName, quantityParam)); + } + + protected void handleEncounterReference(OpenmrsFhirCriteriaContext criteriaContext, ReferenceAndListParam encounterReference, @Nonnull String encounterAlias) { + handleEncounterReference(criteriaContext, encounterReference, encounterAlias, "encounter"); + } + + protected void handleEncounterReference(OpenmrsFhirCriteriaContext criteriaContext, ReferenceAndListParam encounterReference, @Nonnull String encounterAlias, @Nonnull String associationPath) { + + if (encounterReference == null) { + return; + } + + if (lacksAlias(criteriaContext, encounterAlias)) { + criteriaContext.getRoot().join(associationPath).alias(encounterAlias); + } + + handleAndListParam(criteriaContext.getCriteriaBuilder(), encounterReference, token -> { + if (token.getChain() != null) { + switch (token.getChain()) { + case Encounter.SP_TYPE: + criteriaContext.addJoin(String.format("%s.encounterType", encounterAlias), "et"); + return propertyLike(criteriaContext, getRootOrJoin(criteriaContext, "et"), "uuid", new StringParam(token.getValue(), true)); + } + } else { + return Optional.of(criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get(String.format("%s.uuid", encounterAlias)), token.getIdPart())); + } + + return Optional.empty(); + }).ifPresent(criteriaContext::addPredicate); + } + + protected Optional handleGender(OpenmrsFhirCriteriaContext criteriaContext, @Nonnull String propertyName, TokenAndListParam gender) { + if (gender == null) { + return Optional.empty(); + } + + return handleAndListParam(criteriaContext.getCriteriaBuilder(), gender, token -> { + try { + AdministrativeGender administrativeGender = AdministrativeGender.fromCode(token.getValue()); + + if (administrativeGender == null) { + return Optional.of(criteriaContext.getCriteriaBuilder().isNull(criteriaContext.getRoot().get(propertyName))); + } + + switch (administrativeGender) { + case MALE: + return Optional.of(criteriaContext.getCriteriaBuilder().like(criteriaContext.getRoot().get(propertyName), "M")); + case FEMALE: + return Optional.of(criteriaContext.getCriteriaBuilder().like(criteriaContext.getRoot().get(propertyName), "F")); + case OTHER: + case UNKNOWN: + case NULL: + return Optional.of(criteriaContext.getCriteriaBuilder().isNull(criteriaContext.getRoot().get(propertyName))); + } + } catch (FHIRException ignored) { + } + return Optional.of(criteriaContext.getCriteriaBuilder().like(criteriaContext.getRoot().get(propertyName), token.getValue())); + }); + } + + protected Optional handleLocationReference(OpenmrsFhirCriteriaContext criteriaContext, @Nonnull String locationAlias, ReferenceAndListParam locationReference) { + + if (locationReference == null) { + return Optional.empty(); + } + + return handleAndListParam(criteriaContext.getCriteriaBuilder(), locationReference, token -> { + if (token.getChain() != null) { + switch (token.getChain()) { + case Location.SP_NAME: + return propertyLike(criteriaContext, getRootOrJoin(criteriaContext, locationAlias), "name", token.getValue()); + case Location.SP_ADDRESS_CITY: + return propertyLike(criteriaContext, getRootOrJoin(criteriaContext, locationAlias), "cityVillage", token.getValue()); + case Location.SP_ADDRESS_STATE: + return propertyLike(criteriaContext, getRootOrJoin(criteriaContext, locationAlias), "stateProvince", token.getValue()); + case Location.SP_ADDRESS_POSTALCODE: + return propertyLike(criteriaContext, getRootOrJoin(criteriaContext, locationAlias), "postalCode", token.getValue()); + case Location.SP_ADDRESS_COUNTRY: + return propertyLike(criteriaContext, getRootOrJoin(criteriaContext, locationAlias), "country", token.getValue()); + } + } else { + return Optional.of(criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get(String.format("%s.uuid", locationAlias)), token.getValue())); + } + + return Optional.empty(); + }); + } + + protected void handleParticipantReference(OpenmrsFhirCriteriaContext criteriaContext, ReferenceAndListParam participantReference) { + if (participantReference != null) { + Optional> encounterProvider = criteriaContext.getJoin("ep"); + if (!encounterProvider.isPresent()) { + return; + } + + handleAndListParam(criteriaContext.getCriteriaBuilder(), participantReference, participantToken -> { + if (participantToken.getChain() != null) { + switch (participantToken.getChain()) { + case Practitioner.SP_IDENTIFIER: + criteriaContext.addJoin(encounterProvider.get(), "provider", "p"); + return criteriaContext.getJoin("p").map(providerJoin -> criteriaContext.getCriteriaBuilder().like(providerJoin.get("identifier"), participantToken.getValue())); + case Practitioner.SP_GIVEN: { + Join encounterProviderProvider = criteriaContext.addJoin(encounterProvider.get(), "provider", "pro"); + Join encounterProviderPerson = criteriaContext.addJoin(encounterProviderProvider, "person", "ps"); + Join encounterProviderPersonName = criteriaContext.addJoin(encounterProviderPerson, "names", "pn"); + + return Optional.of(criteriaContext.getCriteriaBuilder().like(encounterProviderPersonName.get("givenName"), participantToken.getValue())); + } + case Practitioner.SP_FAMILY: { + Join encounterProviderProvider = criteriaContext.addJoin(encounterProvider.get(), "provider", "pro"); + Join encounterProviderPerson = criteriaContext.addJoin(encounterProviderProvider, "person", "ps"); + Join encounterProviderPersonName = criteriaContext.addJoin(encounterProviderPerson, "names", "pn"); + + return Optional.of(criteriaContext.getCriteriaBuilder().like(encounterProviderPersonName.get("familyName"), participantToken.getValue())); + } + case Practitioner.SP_NAME: { + Join encounterProviderProvider = criteriaContext.addJoin(encounterProvider.get(), "provider", "pro"); + Join encounterProviderPerson = criteriaContext.addJoin(encounterProviderProvider, "person", "ps"); + Join encounterProviderPersonName = criteriaContext.addJoin(encounterProviderPerson, "names", "pn"); + + List> predicateList = new ArrayList<>(); + + for (String token : StringUtils.split(participantToken.getValue(), " \t,")) { + predicateList.add(propertyLike(criteriaContext, encounterProviderPersonName, "givenName", token)); + predicateList.add(propertyLike(criteriaContext, encounterProviderPersonName, "middleName", token)); + predicateList.add(propertyLike(criteriaContext, encounterProviderPersonName, "familyName", token)); + } + + return Optional.of(criteriaContext.getCriteriaBuilder().or(toCriteriaArray(predicateList))); + } + } + } else { + if (lacksAlias(criteriaContext, "pro")) { + criteriaContext.getRoot().join("ep.provider").alias("pro"); + } + return Optional.of(criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("pro.uuid"), participantToken.getValue())); + } + + return Optional.empty(); + }).ifPresent(criteriaContext::addPredicate); + criteriaContext.finalizeQuery(); + } + } + + //Added this method to allow handling classes with provider instead of encounterProvider + protected void handleProviderReference(OpenmrsFhirCriteriaContext criteriaContext, ReferenceAndListParam providerReference) { + if (providerReference != null) { + Join orderer = criteriaContext.addJoin("orderer", "or"); + + handleAndListParam(criteriaContext.getCriteriaBuilder(), providerReference, participantToken -> { + if (participantToken.getChain() != null) { + switch (participantToken.getChain()) { + case Practitioner.SP_IDENTIFIER: + return Optional.of(criteriaContext.getCriteriaBuilder().like(orderer.get("identifier"), participantToken.getValue())); + case Practitioner.SP_GIVEN: { + Join ordererPerson = criteriaContext.addJoin(orderer, "person", "ps"); + Join ordererName = criteriaContext.addJoin(ordererPerson, "names", "pn"); + + return Optional.of(criteriaContext.getCriteriaBuilder().like(ordererName.get("givenName"), participantToken.getValue())); + } + case Practitioner.SP_FAMILY: { + Join ordererPerson = criteriaContext.addJoin(orderer, "person", "ps"); + Join ordererName = criteriaContext.addJoin(ordererPerson, "names", "pn"); + + return Optional.of(criteriaContext.getCriteriaBuilder().like(ordererName.get("pn.familyName"), participantToken.getValue())); + } + case Practitioner.SP_NAME: { + Join ordererPerson = criteriaContext.addJoin(orderer, "person", "ps"); + Join ordererName = criteriaContext.addJoin(ordererPerson, "names", "pn"); + + List> predicateList = new ArrayList<>(); + + for (String token : StringUtils.split(participantToken.getValue(), " \t,")) { + predicateList.add(propertyLike(criteriaContext, ordererName, "givenName", token)); + predicateList.add(propertyLike(criteriaContext, ordererName, "middleName", token)); + predicateList.add(propertyLike(criteriaContext, ordererName, "familyName", token)); + } + + return Optional.of(criteriaContext.getCriteriaBuilder().or(toCriteriaArray(predicateList))); + } + } + } else { + return Optional.of(criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("ro.uuid"), participantToken.getValue())); + } + + return Optional.empty(); + }).ifPresent(criteriaContext::addPredicate); + } + } + + protected Optional handleCodeableConcept(OpenmrsFhirCriteriaContext criteriaContext, TokenAndListParam concepts, @Nonnull String conceptAlias, @Nonnull String conceptMapAlias, @Nonnull String conceptReferenceTermAlias) { + if (concepts == null) { + return Optional.empty(); + } + + return handleAndListParamBySystem(criteriaContext.getCriteriaBuilder(), concepts, (system, tokens) -> { + if (system.isEmpty()) { + criteriaContext.getCriteriaBuilder().literal(tokensToParams(tokens).map(NumberUtils::toInt).collect(Collectors.toList())); + return Optional.of(criteriaContext.getCriteriaBuilder().or(criteriaContext.getCriteriaBuilder().in(criteriaContext.getRoot().get(String.format("%s.conceptId", conceptAlias)).in(criteriaContext.getCriteriaBuilder().literal(tokensToParams(tokens).map(NumberUtils::toInt).collect(Collectors.toList())))), criteriaContext.getCriteriaBuilder().in(criteriaContext.getRoot().get(String.format("%s.uuid", conceptAlias)).in(criteriaContext.getCriteriaBuilder().literal(tokensToList(tokens)))))); + + } else { + if (lacksAlias(criteriaContext, conceptMapAlias)) { + criteriaContext.getRoot().join(String.format("%s.conceptMappings", conceptAlias)).alias(conceptMapAlias); + criteriaContext.getRoot().join(String.format("%s.conceptReferenceTerm", conceptMapAlias)).alias(conceptReferenceTermAlias); + } + + return Optional.of(generateSystemQuery(criteriaContext, system, tokensToList(tokens), conceptReferenceTermAlias)); + } + }); + } + + protected void handleNames(OpenmrsFhirCriteriaContext criteriaContext, StringAndListParam name, StringAndListParam given, StringAndListParam family) { + handleNames(criteriaContext, name, given, family, null); + } + + protected void handleNames(OpenmrsFhirCriteriaContext criteriaContext, StringAndListParam name, StringAndListParam given, StringAndListParam family, String personAlias) { + + if (name == null && given == null && family == null) { + return; + } + + if (lacksAlias(criteriaContext, "pn")) { + if (StringUtils.isNotBlank(personAlias)) { + criteriaContext.getRoot().join(String.format("%s.names", personAlias), javax.persistence.criteria.JoinType.INNER).alias("pn"); + criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("pn.voided"), false); + } else { + criteriaContext.getRoot().join("names", javax.persistence.criteria.JoinType.INNER).alias("pn"); + criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("pn.voided"), false); + } + } + + if (name != null) { + handleAndListParamAsStream(name, (nameParam) -> Arrays.stream(StringUtils.split(nameParam.getValue(), " \t,")).map(token -> new StringParam().setValue(token).setExact(nameParam.isExact()).setContains(nameParam.isContains())).map(tokenParam -> Arrays.asList(propertyLike(criteriaContext, "pn.givenName", tokenParam), propertyLike(criteriaContext, "pn.middleName", tokenParam), propertyLike(criteriaContext, "pn.familyName", tokenParam))).flatMap(Collection::stream)).ifPresent(criteriaContext::addPredicate); + criteriaContext.finalizeQuery(); + } + + if (given != null) { + handleAndListParam(given, (givenName) -> propertyLike(criteriaContext, "pn.givenName", givenName)).ifPresent(criteriaContext::addPredicate); + criteriaContext.finalizeQuery(); + } + + if (family != null) { + handleAndListParam(family, (familyName) -> propertyLike(criteriaContext, "pn.familyName", familyName)).ifPresent(criteriaContext::addPredicate); + criteriaContext.finalizeQuery(); + } + } + + protected void handlePatientReference(OpenmrsFhirCriteriaContext criteriaContext, ReferenceAndListParam patientReference) { + handlePatientReference(criteriaContext, patientReference, "patient"); + } + + protected void handlePatientReference(OpenmrsFhirCriteriaContext criteriaContext, ReferenceAndListParam patientReference, String associationPath) { + if (patientReference != null) { + criteriaContext.addJoin(associationPath, "p").finalizeQuery(); + + handleAndListParam(patientReference, patientToken -> { + if (patientToken.getChain() != null) { + switch (patientToken.getChain()) { + case Patient.SP_IDENTIFIER: + if (lacksAlias(criteriaContext, "pi")) { + criteriaContext.addJoin("p.identifiers", "pi").finalizeQuery(); + } + return Optional.of(criteriaContext.getCriteriaBuilder().like(criteriaContext.getRoot().get("pi.identifier"), patientToken.getValue())); + case Patient.SP_GIVEN: + if (lacksAlias(criteriaContext, "pn")) { + criteriaContext.addJoin("p.names", "pn").finalizeQuery(); + } + return Optional.of(criteriaContext.getCriteriaBuilder().like(criteriaContext.getRoot().get("pn.givenName"), patientToken.getValue())); + case Patient.SP_FAMILY: + if (lacksAlias(criteriaContext, "pn")) { + criteriaContext.addJoin("p.names", "pn").finalizeQuery(); + } + return Optional.of(criteriaContext.getCriteriaBuilder().like(criteriaContext.getRoot().get("pn.familyName"), patientToken.getValue())); + case Patient.SP_NAME: + if (lacksAlias(criteriaContext, "pn")) { + criteriaContext.addJoin("p.names", "pn").finalizeQuery(); + } + + List> criterionList = new ArrayList<>(); + + for (String token : StringUtils.split(patientToken.getValue(), " \t,")) { + criterionList.add(propertyLike(criteriaContext, "pn.givenName", token)); + criterionList.add(propertyLike(criteriaContext, "pn.middleName", token)); + criterionList.add(propertyLike(criteriaContext, "pn.familyName", token)); + } + return Optional.of(criteriaContext.getCriteriaBuilder().or(toCriteriaArray(criterionList))); + } + } else { + return Optional.of(criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("p.uuid"), patientToken.getValue())); + } + + return Optional.empty(); + }).ifPresent(criteriaContext::addPredicate); + } + } + + protected Optional handleCommonSearchParameters(OpenmrsFhirCriteriaContext criteriaContext, List> theCommonParams) { + List> criterionList = new ArrayList<>(); + + for (PropParam commonSearchParam : theCommonParams) { + switch (commonSearchParam.getPropertyName()) { + case FhirConstants.ID_PROPERTY: + criterionList.add(handleAndListParam(criteriaContext.getCriteriaBuilder(), (TokenAndListParam) commonSearchParam.getParam(), param -> Optional.of(criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("uuid"), param.getValue())))); + break; + case FhirConstants.LAST_UPDATED_PROPERTY: + criterionList.add(handleLastUpdated(criteriaContext, (DateRangeParam) commonSearchParam.getParam())); + break; + } + } + return Optional.of(criteriaContext.getCriteriaBuilder().and(toCriteriaArray(criterionList.stream()))); + } + + /** + * This function should be overridden by implementations. It is used to return a criterion for + * _lastUpdated from resources where there are multiple properties to be considered. + * + * @param param the DateRangeParam used to query for _lastUpdated + * @return an optional criterion for the query + */ + protected abstract Optional handleLastUpdated(OpenmrsFhirCriteriaContext criteriaContext, DateRangeParam param); + + protected Optional handlePersonAddress(OpenmrsFhirCriteriaContext criteriaContext, String aliasPrefix, StringAndListParam city, StringAndListParam state, StringAndListParam postalCode, StringAndListParam country) { + if (city == null && state == null && postalCode == null && country == null) { + return Optional.empty(); + } + + List> predicateList = new ArrayList<>(); + + if (city != null) { + predicateList.add(handleAndListParam(criteriaContext.getCriteriaBuilder(), city, c -> propertyLike(criteriaContext, String.format("%s.cityVillage", aliasPrefix), c))); + } + + if (state != null) { + predicateList.add(handleAndListParam(state, c -> propertyLike(criteriaContext, String.format("%s.stateProvince", aliasPrefix), c))); + } + + if (postalCode != null) { + predicateList.add(handleAndListParam(postalCode, c -> propertyLike(criteriaContext, String.format("%s.postalCode", aliasPrefix), c))); + } + + if (country != null) { + predicateList.add(handleAndListParam(country, c -> propertyLike(criteriaContext, String.format("%s.country", aliasPrefix), c))); + } + + return Optional.of(criteriaContext.getCriteriaBuilder().and(toCriteriaArray(predicateList.stream()))); + } + + protected Optional handleMedicationReference(OpenmrsFhirCriteriaContext criteriaContext, @Nonnull String medicationAlias, ReferenceAndListParam medicationReference) { + if (medicationReference == null) { + return Optional.empty(); + } + + return handleAndListParam(medicationReference, token -> Optional.of(criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get(String.format("%s.uuid", medicationAlias)), token.getIdPart()))); + } + + protected Optional handleMedicationRequestReference(OpenmrsFhirCriteriaContext criteriaContext, @Nonnull String drugOrderAlias, ReferenceAndListParam drugOrderReference) { + if (drugOrderReference == null) { + return Optional.empty(); + } + + return handleAndListParam(drugOrderReference, token -> Optional.of(criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get(String.format("%s.uuid", drugOrderAlias)), token.getIdPart()))); + } + + /** + * Use this method to properly implement sorting for your query. Note that for this method to work, + * you must override one or more of: {@link #paramToProps(OpenmrsFhirCriteriaContext, SortState)}, + * {@link #paramToProps(OpenmrsFhirCriteriaContext, String)}, or + * {@link #paramToProp(OpenmrsFhirCriteriaContext, String)}. + * + * @param criteriaContext The {@link OpenmrsFhirCriteriaContext} for the current query + * @param sort the {@link SortSpec} which defines the sorting to be translated + */ + protected void handleSort(OpenmrsFhirCriteriaContext criteriaContext, SortSpec sort) { + handleSort(criteriaContext, sort, this::paramToProps).ifPresent(l -> l.forEach(criteriaContext.getCriteriaQuery()::orderBy)); + } + + 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().criteriaBuilder(criteriaContext.getCriteriaBuilder()).sortOrder(sortSpec.getOrder()).parameter(sortSpec.getParamName().toLowerCase()).build(); + + Collection orders = paramToProp.apply(criteriaContext, state); + if (orders != null) { + orderings.addAll(orders); + } + + sortSpec = sortSpec.getChain(); + } + + if (orderings.isEmpty()) { + return Optional.empty(); + } + + return Optional.of(orderings); + } + + @SuppressWarnings("unchecked") + protected Predicate generateSystemQuery(OpenmrsFhirCriteriaContext criteriaContext, String system, List codes, String conceptReferenceTermAlias) { + //detached criteria + Specification spec = (root, query, cb) -> (Predicate) query.select(root.get("conceptSource")).where(cb.equal(root.get("url"), system)); + + criteriaContext.getCriteriaQuery().where(spec.toPredicate((Root) criteriaContext.getRoot(), (CriteriaQuery) criteriaContext.getCriteriaQuery(), criteriaContext.getCriteriaBuilder())); + + if (codes.size() > 1) { + return criteriaContext.getCriteriaBuilder().and(criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get(String.format("%s.conceptSource", conceptReferenceTermAlias)), criteriaContext.getCriteriaQuery()), criteriaContext.getCriteriaBuilder().in(criteriaContext.getRoot().get(String.format("%s.code", conceptReferenceTermAlias)).get(codes.toString()))); + } else { + return criteriaContext.getCriteriaBuilder().and(criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get(String.format("%s.conceptSource", conceptReferenceTermAlias)), criteriaContext.getCriteriaQuery()), criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get(String.format("%s.code", conceptReferenceTermAlias)), codes.get(0))); + } + } + + protected Predicate generateActiveOrderQuery(OpenmrsFhirCriteriaContext criteriaContext, String path, Date onDate) { + // ACTIVE = date activated null or less than or equal to current datetime, date stopped null or in the future, auto expire date null or in the future + return criteriaContext.getCriteriaBuilder().and(criteriaContext.getCriteriaBuilder().or(criteriaContext.getCriteriaBuilder().isNull(criteriaContext.getRoot().join(path).get("dateActivated")), criteriaContext.getCriteriaBuilder().lessThan(criteriaContext.getRoot().join(path).get("dateActivated"), onDate)), criteriaContext.getCriteriaBuilder().or(criteriaContext.getCriteriaBuilder().isNull(criteriaContext.getRoot().join(path).get("dateStopped")), criteriaContext.getCriteriaBuilder().greaterThan(criteriaContext.getRoot().join(path).get("dateStopped"), onDate)), criteriaContext.getCriteriaBuilder().or(criteriaContext.getCriteriaBuilder().isNull(criteriaContext.getRoot().join(path).get("autoExpireDate")), criteriaContext.getCriteriaBuilder().greaterThan(criteriaContext.getRoot().join(path).get("autoExpireDate"), onDate))); + } + + protected Predicate generateActiveOrderQuery(OpenmrsFhirCriteriaContext criteriaContext, String path) { + return generateActiveOrderQuery(criteriaContext, path, new Date()); + } + + protected Predicate generateActiveOrderQuery(OpenmrsFhirCriteriaContext criteriaContext, Date onDate) { + return generateActiveOrderQuery(criteriaContext, "", onDate); + } + + protected Predicate generateActiveOrderQuery(OpenmrsFhirCriteriaContext criteriaContext) { + return generateActiveOrderQuery(criteriaContext, new Date()); + } + + protected Predicate generateNotCancelledOrderQuery(OpenmrsFhirCriteriaContext criteriaContext) { + return generateNotCancelledOrderQuery(criteriaContext, ""); + } + + protected Predicate generateNotCancelledOrderQuery(OpenmrsFhirCriteriaContext criteriaContext, String path) { + Date now = new Date(); + + return criteriaContext.getCriteriaBuilder().or(criteriaContext.getCriteriaBuilder().isNull(criteriaContext.getRoot().join(path).get("dateStopped")), criteriaContext.getCriteriaBuilder().greaterThan(criteriaContext.getRoot().join(path).get("dateStopped"), now)); + } + + protected TokenOrListParam convertStringStatusToBoolean(TokenOrListParam statusParam) { + if (statusParam != null) { + return handleOrListParam(statusParam).map(s -> { + switch (s.getValue()) { + case "active": + return Optional.of("false"); + case "inactive": + return Optional.of("true"); + default: + return Optional.empty(); + } + }).filter(Optional::isPresent).map(Optional::get).collect(TokenOrListParam::new, (tp, v) -> tp.add(String.valueOf(v)), (tp1, tp2) -> tp2.getListAsCodings().forEach(tp1::add)); + } + + return null; + } + + protected TokenAndListParam convertStringStatusToBoolean(TokenAndListParam statusParam) { + if (statusParam != null) { + return handleAndListParam(statusParam).map(this::convertStringStatusToBoolean).collect(TokenAndListParam::new, TokenAndListParam::addAnd, (tp1, tp2) -> tp2.getValuesAsQueryTokens().forEach(tp1::addAnd)); + } + + return null; + } + + /** + * This function should be overridden by implementations. It is used to map FHIR parameter names to + * their corresponding values in the query. + * + * @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()); + if (prop != null) { + switch (sortState.getSortOrder()) { + case ASC: + return prop.stream().map(s -> criteriaContext.getCriteriaBuilder().asc(criteriaContext.getRoot().get(s))).collect(Collectors.toList()); + case DESC: + return prop.stream().map(s -> criteriaContext.getCriteriaBuilder().desc(criteriaContext.getRoot().get(s))).collect(Collectors.toList()); + } + } + + return null; + } + + /** + * This function should be overridden by implementations. It is used to map FHIR parameter names to + * properties where there is only a single property. + * + * @param param the FHIR parameter to map + * @return the name of the corresponding property from the current query + */ + protected Collection paramToProps(OpenmrsFhirCriteriaContext criteriaContext, @Nonnull String param) { + String prop = paramToProp(criteriaContext, param); + + if (prop != null) { + return Collections.singleton(prop); + } + + return null; + } + + /** + * This function should be overridden by implementations. It is used to map FHIR parameter names to + * properties where there is only a single property. + * + * @param param the FHIR parameter to map + * @return the name of the corresponding property from the current query + */ + protected String paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @Nonnull String param) { + return null; + } + + protected Optional propertyLike(OpenmrsFhirCriteriaContext criteriaContext, From from, @Nonnull String propertyName, String value) { + if (value == null) { + return Optional.empty(); + } + + return propertyLike(criteriaContext, from, propertyName, new StringParam(value)); + } + + protected Optional propertyLike(OpenmrsFhirCriteriaContext criteriaContext, From from, @Nonnull String propertyName, StringParam param) { + if (param == null) { + return Optional.empty(); + } + + Predicate likePredicate; + if (param.isExact()) { + likePredicate = criteriaContext.getCriteriaBuilder().equal(from.get(propertyName), param.getValue()); + } else if (param.isContains()) { + likePredicate = criteriaContext.getCriteriaBuilder().like(from.get(propertyName), "%" + param.getValue() + "%"); + } else { + likePredicate = criteriaContext.getCriteriaBuilder().like(from.get(propertyName), param.getValue() + "%"); + } + + return Optional.of(likePredicate); + } + + protected List tokensToList(List tokens) { + return tokensToParams(tokens).collect(Collectors.toList()); + } + + protected Stream tokensToParams(List tokens) { + return tokens.stream().map(TokenParam::getValue); + } + + private String groupBySystem(@Nonnull TokenParam token) { + return StringUtils.trimToEmpty(token.getSystem()); + } + + protected , U extends IQueryParameterType> Stream handleAndListParam(IQueryParameterAnd andListParameter) { + return andListParameter.getValuesAsQueryTokens().stream(); + } + + protected Stream handleOrListParam(IQueryParameterOr orListParameter) { + return orListParameter.getValuesAsQueryTokens().stream(); + } + + @SafeVarargs + @SuppressWarnings("unused") + protected final Predicate[] toCriteriaArray(Optional... predicate) { + return toCriteriaArray(Arrays.stream(predicate)); + } + + protected Predicate[] toCriteriaArray(Collection> collection) { + return toCriteriaArray(collection.stream()); + } + + protected Predicate[] toCriteriaArray(Stream> predicateStream) { + return predicateStream.filter(Optional::isPresent).map(Optional::get).toArray(Predicate[]::new); + } + + /** + * This object is used to store the state of the sorting + */ + @Data + @Builder + @EqualsAndHashCode + public static final class SortState { + + private CriteriaBuilder criteriaBuilder; + + private SortOrderEnum sortOrder; + + private String parameter; + } + + protected Optional handleAgeByDateProperty(OpenmrsFhirCriteriaContext criteriaContext, @Nonnull String datePropertyName, @Nonnull QuantityParam age) { + BigDecimal value = age.getValue(); + if (value == null) { + throw new IllegalArgumentException("Age value should be provided in " + age); + } + + String unit = age.getUnits(); + if (unit == null) { + throw new IllegalArgumentException("Age unit should be provided in " + age); + } + + LocalDateTime localDateTime = localDateTimeFactory.now(); + + TemporalAmount temporalAmount; + TemporalUnit temporalUnit; + // TODO check if HAPI FHIR already defines these constant strings. These are mostly from + // http://www.hl7.org/fhir/valueset-age-units.html with the exception of "s" which is not + // listed but was seen in FHIR examples: http://www.hl7.org/fhir/datatypes-examples.html#Quantity + switch (unit) { + case "s": + temporalUnit = ChronoUnit.SECONDS; + temporalAmount = Duration.ofSeconds(value.longValue()); + break; + case "min": + temporalUnit = ChronoUnit.MINUTES; + temporalAmount = Duration.ofMinutes(value.longValue()); + break; + case "h": + temporalUnit = ChronoUnit.HOURS; + temporalAmount = Duration.ofHours(value.longValue()); + break; + case "d": + temporalUnit = ChronoUnit.DAYS; + temporalAmount = Period.ofDays(value.intValue()); + break; + case "wk": + temporalUnit = ChronoUnit.WEEKS; + temporalAmount = Period.ofWeeks(value.intValue()); + break; + case "mo": + temporalUnit = ChronoUnit.MONTHS; + temporalAmount = Period.ofMonths(value.intValue()); + break; + case "a": + temporalUnit = ChronoUnit.YEARS; + temporalAmount = Period.ofYears(value.intValue()); + break; + default: + throw new IllegalArgumentException("Invalid unit " + unit + " in age " + age + " should be one of 'min', 'h', 'd', 'wk', 'mo', 'a'"); + } + + localDateTime = localDateTime.minus(temporalAmount); + + ParamPrefixEnum prefix = age.getPrefix(); + if (prefix == null) { + prefix = ParamPrefixEnum.EQUAL; + } + + if (prefix == ParamPrefixEnum.EQUAL || prefix == ParamPrefixEnum.NOT_EQUAL) { + // Create a range for the targeted unit; the interval length is determined by the unit and + // its center is `offsetSeconds` in the past. + final long offset; + + // Duration only supports hours as a chunk of seconds + if (temporalUnit == ChronoUnit.HOURS) { + offset = temporalAmount.get(ChronoUnit.SECONDS) / (2 * 3600); + } else { + offset = temporalAmount.get(temporalUnit) / 2; + } + + LocalDateTime lowerBoundDateTime = LocalDateTime.from(localDateTime).minus(Duration.of(offset, temporalUnit)); + Date lowerBound = Date.from(lowerBoundDateTime.atZone(ZoneId.systemDefault()).toInstant()); + + LocalDateTime upperBoundDateTime = LocalDateTime.from(localDateTime).plus(offset, temporalUnit); + Date upperBound = Date.from(upperBoundDateTime.atZone(ZoneId.systemDefault()).toInstant()); + + if (prefix == ParamPrefixEnum.EQUAL) { + return Optional.ofNullable(criteriaContext.getCriteriaBuilder().and(criteriaContext.getCriteriaBuilder().greaterThanOrEqualTo(criteriaContext.getRoot().get(datePropertyName), lowerBound), criteriaContext.getCriteriaBuilder().lessThanOrEqualTo(criteriaContext.getRoot().get(datePropertyName), upperBound))); + } else { + return Optional.ofNullable(criteriaContext.getCriteriaBuilder().not(criteriaContext.getCriteriaBuilder().and(criteriaContext.getCriteriaBuilder().greaterThanOrEqualTo(criteriaContext.getRoot().get(datePropertyName), lowerBound), criteriaContext.getCriteriaBuilder().lessThanOrEqualTo(criteriaContext.getRoot().get(datePropertyName), upperBound)))); + } + } + + switch (prefix) { + case LESSTHAN_OR_EQUALS: + case LESSTHAN: + case STARTS_AFTER: + return Optional.ofNullable(criteriaContext.getCriteriaBuilder().greaterThanOrEqualTo(criteriaContext.getRoot().get("datePropertyName"), Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()))); + case GREATERTHAN_OR_EQUALS: + case GREATERTHAN: + return Optional.ofNullable(criteriaContext.getCriteriaBuilder().lessThanOrEqualTo(criteriaContext.getRoot().get("datePropertyName"), Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()))); + // Ignoring ENDS_BEFORE as it is not meaningful for age. + } + + return Optional.empty(); + } + + protected OpenmrsFhirCriteriaContext createCriteriaContext(Class rootType) { + EntityManager em = sessionFactory.getCurrentSession(); + CriteriaBuilder cb = em.getCriteriaBuilder(); + @SuppressWarnings("unchecked") CriteriaQuery cq = (CriteriaQuery) cb.createQuery(rootType); + @SuppressWarnings("unchecked") Root root = (Root) cq.from(rootType); + + return new OpenmrsFhirCriteriaContext<>(em, cb, cq, root); + } + + protected From getRootOrJoin(OpenmrsFhirCriteriaContext criteriaContext, String alias) { + if (alias.isEmpty()) { + return criteriaContext.getRoot(); + } else { + return criteriaContext.getJoin(alias).orElseThrow(() -> new IllegalStateException("Tried to reference alias " + alias + " before creating a join with that name")); + } + } + + protected interface Specification { + + /** + * Creates a WHERE clause for a query of the referenced entity in form of a {@link Predicate} for + * the given {@link Root} and {@link CriteriaQuery}. + * + * @param root must not be {@literal null}. + * @param query must not be {@literal null}. + * @param cb must not be {@literal null}. + * @return a {@link Predicate}, may be {@literal null}. to + * spring-data jpa implementation + */ + Predicate toPredicate(@Nonnull Root root, @Nonnull CriteriaQuery query, @Nonnull CriteriaBuilder cb); + } + } diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BaseEncounterDao.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BaseEncounterDao.java index 3f93951db..7f0f40100 100644 --- a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BaseEncounterDao.java +++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/BaseEncounterDao.java @@ -112,13 +112,13 @@ protected void handleHasAndListParam(OpenmrsFhirCriteriaContext criteriaConte criteriaContext.addJoin("en.orders", "orders"); } } - Join join = criteriaContext.getRoot().join("orders"); + Join join = criteriaContext.getRoot().join("orders"); // Constrain only on non-voided Drug Orders // TODO Do these criteria still work? - criteriaContext.addPredicate(criteriaContext.getCriteriaBuilder() - .equal(join.get("voided"), false)); - criteriaContext.addPredicate(criteriaContext.getCriteriaBuilder() - .notEqual(join.get("action"), Order.Action.DISCONTINUE)); + criteriaContext + .addPredicate(criteriaContext.getCriteriaBuilder().equal(join.get("voided"), false)); + criteriaContext.addPredicate( + criteriaContext.getCriteriaBuilder().notEqual(join.get("action"), Order.Action.DISCONTINUE)); String paramName = hasParam.getParameterName(); String paramValue = hasParam.getParameterValue(); @@ -146,7 +146,8 @@ protected void handleHasAndListParam(OpenmrsFhirCriteriaContext criteriaConte } if (MedicationRequest.MedicationRequestStatus.COMPLETED.toString() .equalsIgnoreCase(paramValue)) { - Predicate notCompletedCriterion = generateNotCompletedOrderQuery(criteriaContext,"orders"); + Predicate notCompletedCriterion = generateNotCompletedOrderQuery(criteriaContext, + "orders"); if (notCompletedCriterion != null) { criteriaContext.getCriteriaBuilder().and(notCompletedCriterion); } @@ -156,12 +157,12 @@ protected void handleHasAndListParam(OpenmrsFhirCriteriaContext criteriaConte } else if ((FhirConstants.SP_FULFILLER_STATUS).equalsIgnoreCase(paramName)) { if (paramValue != null) { criteriaContext.getCriteriaBuilder() - .and(generateFulfillerStatusRestriction(criteriaContext,"orders", paramValue)); + .and(generateFulfillerStatusRestriction(criteriaContext, "orders", paramValue)); } } else if ((FhirConstants.SP_FULFILLER_STATUS + ":not").equalsIgnoreCase(paramName)) { if (paramValue != null) { - criteriaContext.getCriteriaBuilder() - .and(generateNotFulfillerStatusRestriction(criteriaContext,"orders", paramValue)); + criteriaContext.getCriteriaBuilder().and( + generateNotFulfillerStatusRestriction(criteriaContext, "orders", paramValue)); } } criteriaContext.finalizeQuery(); @@ -189,12 +190,14 @@ protected Predicate generateNotCompletedOrderQuery(OpenmrsFhirCriteriaContex return null; } - protected Predicate generateFulfillerStatusRestriction(OpenmrsFhirCriteriaContext criteriaContext, String path, String fulfillerStatus) { + protected Predicate generateFulfillerStatusRestriction(OpenmrsFhirCriteriaContext criteriaContext, String path, + String fulfillerStatus) { // not implemented in Core until 2.2; see override in FhirEncounterDaoImpl_2_2 return null; } - protected Predicate generateNotFulfillerStatusRestriction(OpenmrsFhirCriteriaContext criteriaContext, String path, String fulfillerStatus) { + protected Predicate generateNotFulfillerStatusRestriction(OpenmrsFhirCriteriaContext criteriaContext, String path, + String fulfillerStatus) { // not implemented in Core until 2.2; see override in FhirEncounterDaoImpl_2_2 return null; } 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 a580ff3e9..1dbf58c2f 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 @@ -161,46 +161,46 @@ protected OpenmrsFhirCriteriaContext getSearchResultCriteria(SearchParameterM } @Override - @SuppressWarnings({ "unchecked","UnstableApiUsage"}) + @SuppressWarnings({ "unchecked", "UnstableApiUsage" }) public List getSearchResults(@Nonnull SearchParameterMap theParams) { OpenmrsFhirCriteriaContext criteriaContext = getSearchResultCriteria(theParams); + + handleSort(criteriaContext, theParams.getSortSpec()); + + //the id property differs across various openmrs entities + if (Person.class.isAssignableFrom(typeToken.getRawType())) { + criteriaContext.getCriteriaBuilder().asc(criteriaContext.getRoot().get("personId")); + } else if (Encounter.class.isAssignableFrom(typeToken.getRawType())) { + criteriaContext.getCriteriaBuilder().asc(criteriaContext.getRoot().get("encounterId")); + } else if (Obs.class.isAssignableFrom(typeToken.getRawType())) { + criteriaContext.getCriteriaBuilder().asc(criteriaContext.getRoot().get("obsId")); + } + + criteriaContext.getEntityManager().createQuery(criteriaContext.getCriteriaQuery()) + .setFirstResult(theParams.getFromIndex()); + if (theParams.getToIndex() != Integer.MAX_VALUE) { + int maxResults = theParams.getToIndex() - theParams.getFromIndex(); + criteriaContext.getEntityManager().createQuery(criteriaContext.getCriteriaQuery()).setMaxResults(maxResults); + } + + List results; + if (hasDistinctResults()) { + results = criteriaContext.getEntityManager().createQuery(criteriaContext.getCriteriaQuery()).getResultList(); + } else { - handleSort(criteriaContext, theParams.getSortSpec()); - - //the id property differs across various openmrs entities - if (Person.class.isAssignableFrom(typeToken.getRawType())) { - criteriaContext.getCriteriaBuilder().asc(criteriaContext.getRoot().get("personId")); - } else if (Encounter.class.isAssignableFrom(typeToken.getRawType())) { - criteriaContext.getCriteriaBuilder().asc(criteriaContext.getRoot().get("encounterId")); - } else if (Obs.class.isAssignableFrom(typeToken.getRawType())) { - criteriaContext.getCriteriaBuilder().asc(criteriaContext.getRoot().get("obsId")); - } + EntityManager em = sessionFactory.getCurrentSession(); + CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder(); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Long.class); + Root root = (Root) criteriaQuery.from(typeToken.getRawType()); - criteriaContext.getEntityManager().createQuery(criteriaContext.getCriteriaQuery()) - .setFirstResult(theParams.getFromIndex()); - if (theParams.getToIndex() != Integer.MAX_VALUE) { - int maxResults = theParams.getToIndex() - theParams.getFromIndex(); - criteriaContext.getEntityManager().createQuery(criteriaContext.getCriteriaQuery()).setMaxResults(maxResults); - } + OpenmrsFhirCriteriaContext longOpenmrsFhirCriteriaContext = createCriteriaContext(Long.class); + longOpenmrsFhirCriteriaContext.getCriteriaQuery().subquery(Long.class).select(longOpenmrsFhirCriteriaContext + .getCriteriaBuilder().countDistinct(longOpenmrsFhirCriteriaContext.getRoot().get("id"))); - List results; - if (hasDistinctResults()) { - results = criteriaContext.getEntityManager().createQuery(criteriaContext.getCriteriaQuery()).getResultList(); - } else { - - EntityManager em = sessionFactory.getCurrentSession(); - CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder(); - CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Long.class); - Root root = (Root) criteriaQuery.from(typeToken.getRawType()); - - OpenmrsFhirCriteriaContext longOpenmrsFhirCriteriaContext = createCriteriaContext(Long.class); - longOpenmrsFhirCriteriaContext.getCriteriaQuery().subquery(Long.class).select(longOpenmrsFhirCriteriaContext - .getCriteriaBuilder().countDistinct(longOpenmrsFhirCriteriaContext.getRoot().get("id"))); - - longOpenmrsFhirCriteriaContext.getCriteriaQuery().select(longOpenmrsFhirCriteriaContext.getRoot()) - .where(longOpenmrsFhirCriteriaContext.getCriteriaBuilder() - .in(longOpenmrsFhirCriteriaContext.getRoot().get("id")) - .value(longOpenmrsFhirCriteriaContext.getCriteriaQuery().subquery(Long.class))); + longOpenmrsFhirCriteriaContext.getCriteriaQuery().select(longOpenmrsFhirCriteriaContext.getRoot()) + .where(longOpenmrsFhirCriteriaContext.getCriteriaBuilder() + .in(longOpenmrsFhirCriteriaContext.getRoot().get("id")) + .value(longOpenmrsFhirCriteriaContext.getCriteriaQuery().subquery(Long.class))); //TODO: gonna come back to it later // handleSort(projectionCriteriaBuilder, theParams.getSortSpec(), this::paramToProps).ifPresent( @@ -227,7 +227,7 @@ public List getSearchResults(@Nonnull SearchParameterMap theParams) { } @Override - @SuppressWarnings({"unchecked","UnstableApiUsage"}) + @SuppressWarnings({ "unchecked", "UnstableApiUsage" }) public int getSearchResultsCount(@Nonnull SearchParameterMap theParams) { OpenmrsFhirCriteriaContext criteriaContext = getSearchResultCriteria(theParams); applyExactTotal(criteriaContext, theParams); diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirConceptDaoImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirConceptDaoImpl.java index 60f0d6a48..20312ecdf 100644 --- a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirConceptDaoImpl.java +++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirConceptDaoImpl.java @@ -12,7 +12,6 @@ import static org.openmrs.module.fhir2.FhirConstants.TITLE_SEARCH_HANDLER; import javax.annotation.Nonnull; -import javax.persistence.criteria.Join; import java.util.Collections; import java.util.List; @@ -24,7 +23,6 @@ import org.openmrs.Concept; import org.openmrs.ConceptMap; import org.openmrs.ConceptMapType; -import org.openmrs.ConceptReferenceTerm; import org.openmrs.ConceptSource; import org.openmrs.api.ConceptService; import org.openmrs.api.context.Context; @@ -50,14 +48,16 @@ public Optional getConceptWithSameAsMappingInSource(@Nonnull ConceptSou @Nonnull String mappingCode) { OpenmrsFhirCriteriaContext criteriaContext = createCriteriaContext(Concept.class); - createConceptMapCriteriaBuilder(conceptSource,mappingCode); + createConceptMapCriteriaBuilder(conceptSource, mappingCode); - criteriaContext.addPredicate(criteriaContext.getCriteriaBuilder() - .or(criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().join("conceptMapType").get("mapType.uuid"), - ConceptMapType.SAME_AS_MAP_TYPE_UUID), - criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().join("conceptMapType").get("mapType.name"), "SAME-AS"))); + criteriaContext.addPredicate(criteriaContext.getCriteriaBuilder().or( + criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().join("conceptMapType").get("mapType.uuid"), + ConceptMapType.SAME_AS_MAP_TYPE_UUID), + criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().join("conceptMapType").get("mapType.name"), + "SAME-AS"))); - criteriaContext.addOrder(criteriaContext.getCriteriaBuilder().asc(criteriaContext.getRoot().join("concept").get("retired"))); + criteriaContext.addOrder( + criteriaContext.getCriteriaBuilder().asc(criteriaContext.getRoot().join("concept").get("retired"))); criteriaContext.finalizeQuery(); return Optional.ofNullable( @@ -72,8 +72,8 @@ public List getConceptsWithAnyMappingInSource(@Nonnull ConceptSource co } OpenmrsFhirCriteriaContext criteriaContext = createCriteriaContext(Concept.class); - createConceptMapCriteriaBuilder(conceptSource,mappingCode); - + createConceptMapCriteriaBuilder(conceptSource, mappingCode); + criteriaContext.addOrder(criteriaContext.getCriteriaBuilder().asc(criteriaContext.getRoot().get("concept.retired"))); return criteriaContext.getEntityManager().createQuery(criteriaContext.getCriteriaQuery()).getResultList(); @@ -106,18 +106,17 @@ protected void createConceptMapCriteriaBuilder(@Nonnull ConceptSource conceptSou criteriaContext.addJoin("conceptMapType", "mapType").finalizeQuery(); criteriaContext.addJoin("concept", "concept").finalizeQuery(); - if (Context.getAdministrationService().isDatabaseStringComparisonCaseSensitive()) { - criteriaContext.addPredicate(criteriaContext.getCriteriaBuilder() - .equal(criteriaContext.getCriteriaBuilder() - .lower(criteriaContext.getRoot().get("term.code")), mappingCode.toLowerCase())); + criteriaContext.addPredicate(criteriaContext.getCriteriaBuilder().equal( + criteriaContext.getCriteriaBuilder().lower(criteriaContext.getRoot().get("term.code")), + mappingCode.toLowerCase())); } else { - criteriaContext.addPredicate(criteriaContext.getCriteriaBuilder() - .equal(criteriaContext.getRoot().get("term.code"), mappingCode)); + criteriaContext.addPredicate( + criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("term.code"), mappingCode)); } - - criteriaContext.addPredicate(criteriaContext.getCriteriaBuilder() - .equal(criteriaContext.getRoot().get("term.conceptSource"), conceptSource)) - .finalizeQuery(); + + criteriaContext.addPredicate( + criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("term.conceptSource"), conceptSource)) + .finalizeQuery(); } } diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirConceptSourceDaoImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirConceptSourceDaoImpl.java index 0419d3ec0..ed70d8579 100644 --- a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirConceptSourceDaoImpl.java +++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirConceptSourceDaoImpl.java @@ -57,13 +57,14 @@ public Collection getFhirConceptSources() { public Optional getFhirConceptSourceByUrl(@Nonnull String url) { OpenmrsFhirCriteriaContext criteriaContext = openmrsFhirCriteriaContext(); criteriaContext.getCriteriaQuery().select(criteriaContext.getRoot()); - - criteriaContext.addPredicate(criteriaContext.getCriteriaBuilder().and(criteriaContext.getCriteriaBuilder() - .equal(criteriaContext.getRoot().get("url"), url),criteriaContext.getCriteriaBuilder() - .equal(criteriaContext.getRoot().get("retired"), false))); + + criteriaContext.addPredicate(criteriaContext.getCriteriaBuilder().and( + criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("url"), url), + criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("retired"), false))); try { - return Optional.ofNullable(criteriaContext.getEntityManager().createQuery(criteriaContext.finalizeQuery()).getSingleResult()); + return Optional.ofNullable( + criteriaContext.getEntityManager().createQuery(criteriaContext.finalizeQuery()).getSingleResult()); } catch (NoResultException e) { return Optional.empty(); @@ -77,14 +78,15 @@ public Optional getFhirConceptSourceByConceptSourceName(@Nonn criteriaContext.getCriteriaQuery().select(criteriaContext.getRoot()); Join conceptSourceJoin = criteriaContext.getRoot().join("conceptSource"); - - criteriaContext.addPredicate(criteriaContext.getCriteriaBuilder().and(criteriaContext.getCriteriaBuilder() - .equal(conceptSourceJoin.get("name"), sourceName),criteriaContext.getCriteriaBuilder() - .equal(conceptSourceJoin.get("retired"), false),criteriaContext.getCriteriaBuilder() - .equal(criteriaContext.getRoot().get("retired"), false))); + + criteriaContext.addPredicate(criteriaContext.getCriteriaBuilder().and( + criteriaContext.getCriteriaBuilder().equal(conceptSourceJoin.get("name"), sourceName), + criteriaContext.getCriteriaBuilder().equal(conceptSourceJoin.get("retired"), false), + criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("retired"), false))); try { - return Optional.ofNullable(criteriaContext.getEntityManager().createQuery(criteriaContext.finalizeQuery()).getSingleResult()); + return Optional.ofNullable( + criteriaContext.getEntityManager().createQuery(criteriaContext.finalizeQuery()).getSingleResult()); } catch (NoResultException e) { return Optional.empty(); diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirContactPointMapDaoImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirContactPointMapDaoImpl.java index 4ff76978f..67b6e8bff 100644 --- a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirContactPointMapDaoImpl.java +++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirContactPointMapDaoImpl.java @@ -42,9 +42,11 @@ public class FhirContactPointMapDaoImpl implements FhirContactPointMapDao { @Override public Optional getFhirContactPointMapByUuid(String uuid) { OpenmrsFhirCriteriaContext criteriaContext = openmrsFhirCriteriaContext(); - criteriaContext.addPredicate(criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("uuid"), uuid)); + criteriaContext + .addPredicate(criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("uuid"), uuid)); - return Optional.ofNullable(criteriaContext.getEntityManager().createQuery(criteriaContext.finalizeQuery()).getSingleResult()); + return Optional.ofNullable( + criteriaContext.getEntityManager().createQuery(criteriaContext.finalizeQuery()).getSingleResult()); } @Override 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 61e48a30e..ec02d2387 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 @@ -33,7 +33,6 @@ import lombok.Setter; import org.openmrs.Encounter; import org.openmrs.EncounterType; -import org.openmrs.Obs; import org.openmrs.Order; import org.openmrs.module.fhir2.FhirConstants; import org.openmrs.module.fhir2.api.dao.FhirEncounterDao; @@ -62,7 +61,7 @@ public List getSearchResultUuids(@Nonnull SearchParameterMap theParams) CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Object[].class); Root root = criteriaQuery.from(Encounter.class); - criteriaQuery.multiselect(root.get("uuid"), root.get("encounterDatetime")); + criteriaQuery.multiselect(root.get("uuid"), root.get("encounterDatetime")); List> results = em.createQuery(criteriaQuery).getResultList().stream() .map(array -> new LastnResult(array)).collect(Collectors.toList()); @@ -122,25 +121,26 @@ protected String paramToProp(OpenmrsFhirCriteriaContext criteriaContext, } @Override - protected Predicate generateNotCompletedOrderQuery(OpenmrsFhirCriteriaContext criteriaContext,String path) { - return criteriaContext.getCriteriaBuilder().or(criteriaContext.getCriteriaBuilder().isNull(criteriaContext.getRoot().join(path).get("fulfillerStatus")), - criteriaContext.getCriteriaBuilder().notEqual(criteriaContext.getRoot().join(path).get("fulfillerStatus"),Order.FulfillerStatus.COMPLETED)); + protected Predicate generateNotCompletedOrderQuery(OpenmrsFhirCriteriaContext criteriaContext, String path) { + return criteriaContext.getCriteriaBuilder().or( + criteriaContext.getCriteriaBuilder().isNull(criteriaContext.getRoot().join(path).get("fulfillerStatus")), + criteriaContext.getCriteriaBuilder().notEqual(criteriaContext.getRoot().join(path).get("fulfillerStatus"), + Order.FulfillerStatus.COMPLETED)); } @Override protected Predicate generateFulfillerStatusRestriction(OpenmrsFhirCriteriaContext criteriaContext, String path, - String fulfillerStatus) { - return criteriaContext.getCriteriaBuilder() - .equal(criteriaContext.getRoot().join(path) - .get("fulfillerStatus"),Order.FulfillerStatus.valueOf(fulfillerStatus.toUpperCase())); + String fulfillerStatus) { + return criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().join(path).get("fulfillerStatus"), + Order.FulfillerStatus.valueOf(fulfillerStatus.toUpperCase())); } @Override protected Predicate generateNotFulfillerStatusRestriction(OpenmrsFhirCriteriaContext criteriaContext, String path, - String fulfillerStatus) { - return criteriaContext.getCriteriaBuilder().or(criteriaContext.getCriteriaBuilder().isNull(criteriaContext.getRoot().join(path).get("fulfillerStatus")), - criteriaContext.getCriteriaBuilder() - .notEqual(criteriaContext.getRoot() - .join(path).get("fulfillerStatus"),Order.FulfillerStatus.valueOf(fulfillerStatus.toUpperCase()))); + String fulfillerStatus) { + return criteriaContext.getCriteriaBuilder().or( + criteriaContext.getCriteriaBuilder().isNull(criteriaContext.getRoot().join(path).get("fulfillerStatus")), + criteriaContext.getCriteriaBuilder().notEqual(criteriaContext.getRoot().join(path).get("fulfillerStatus"), + Order.FulfillerStatus.valueOf(fulfillerStatus.toUpperCase()))); } } 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 7882f716f..69ef8af92 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 @@ -16,7 +16,6 @@ import javax.persistence.criteria.Root; import java.util.List; -import java.util.Optional; import ca.uhn.fhir.rest.param.ReferenceAndListParam; import ca.uhn.fhir.rest.param.StringAndListParam; @@ -38,149 +37,150 @@ @Component @Setter(AccessLevel.PACKAGE) public class FhirLocationDaoImpl extends BaseFhirDao implements FhirLocationDao { - - @Autowired - LocationService locationService; - - @Override - protected void setupSearchParams(OpenmrsFhirCriteriaContext criteriaContext, SearchParameterMap theParams) { - theParams.getParameters().forEach(entry -> { - switch (entry.getKey()) { - case FhirConstants.NAME_SEARCH_HANDLER: - entry.getValue().forEach(param -> handleName(criteriaContext, (StringAndListParam) param.getParam())); - break; - case FhirConstants.CITY_SEARCH_HANDLER: - entry.getValue().forEach(param -> handleCity(criteriaContext, (StringAndListParam) param.getParam())); - break; - case FhirConstants.STATE_SEARCH_HANDLER: - entry.getValue().forEach(param -> handleState(criteriaContext, (StringAndListParam) param.getParam())); - break; - case FhirConstants.COUNTRY_SEARCH_HANDLER: - entry.getValue().forEach(param -> handleCountry(criteriaContext, (StringAndListParam) param.getParam())); - break; - case FhirConstants.POSTALCODE_SEARCH_HANDLER: - entry.getValue() - .forEach(param -> handlePostalCode(criteriaContext, (StringAndListParam) param.getParam())); - break; - case FhirConstants.LOCATION_REFERENCE_SEARCH_HANDLER: - entry.getValue().forEach( - param -> handleParentLocation(criteriaContext, (ReferenceAndListParam) param.getParam())); - break; - case FhirConstants.TAG_SEARCH_HANDLER: - entry.getValue().forEach(param -> handleTag(criteriaContext, (TokenAndListParam) param.getParam())); - break; - case FhirConstants.COMMON_SEARCH_HANDLER: - handleCommonSearchParameters(criteriaContext, entry.getValue()).ifPresent(criteriaContext::addPredicate); - criteriaContext.finalizeQuery(); - break; - } - }); - } - - @Override - public List getActiveAttributesByLocationAndAttributeTypeUuid(@Nonnull Location location, - @Nonnull String locationAttributeTypeUuid) { - OpenmrsFhirCriteriaContext criteriaContext = openmrsFhirCriteriaContext(); - criteriaContext.getCriteriaQuery().select(criteriaContext.getRoot()); - - criteriaContext.addPredicate(criteriaContext.getCriteriaBuilder().and(criteriaContext.getCriteriaBuilder() - .equal(criteriaContext.getRoot().join("location").get("locationId"), location.getId()),criteriaContext.getCriteriaBuilder() - .equal(criteriaContext.getRoot().join("attributeType").get("uuid"), locationAttributeTypeUuid),criteriaContext.getCriteriaBuilder() - .equal(criteriaContext.getRoot().get("voided"), false))); - - return criteriaContext.getEntityManager().createQuery(criteriaContext.finalizeQuery()).getResultList(); - } - - private void handleName(OpenmrsFhirCriteriaContext criteriaContext, StringAndListParam namePattern) { - handleAndListParam(namePattern, (name) -> propertyLike(criteriaContext, "name", name)) - .ifPresent(criteriaContext::addPredicate); - criteriaContext.finalizeQuery(); - } - - private void handleCity(OpenmrsFhirCriteriaContext criteriaContext, StringAndListParam cityPattern) { - handleAndListParam(cityPattern, (city) -> propertyLike(criteriaContext, "cityVillage", city)) - .ifPresent(criteriaContext::addPredicate); - criteriaContext.finalizeQuery(); - } - - private void handleCountry(OpenmrsFhirCriteriaContext criteriaContext, StringAndListParam countryPattern) { - handleAndListParam(countryPattern, (country) -> propertyLike(criteriaContext, "country", country)) - .ifPresent(criteriaContext::addPredicate); - criteriaContext.finalizeQuery(); - } - - private void handlePostalCode(OpenmrsFhirCriteriaContext criteriaContext, - StringAndListParam postalCodePattern) { - handleAndListParam(postalCodePattern, (postalCode) -> propertyLike(criteriaContext, "postalCode", postalCode)) - .ifPresent(criteriaContext::addPredicate); - criteriaContext.finalizeQuery(); - } - - private void handleState(OpenmrsFhirCriteriaContext criteriaContext, StringAndListParam statePattern) { - handleAndListParam(statePattern, (state) -> propertyLike(criteriaContext, "stateProvince", state)) - .ifPresent(criteriaContext::addPredicate); - criteriaContext.finalizeQuery(); - } - - private void handleTag(OpenmrsFhirCriteriaContext criteriaContext, TokenAndListParam tags) { - if (tags != null) { - criteriaContext.getRoot().join("tags").alias("t"); - handleAndListParam(tags, - (tag) -> Optional.of( - criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("t.name"), tag.getValue()))) - .ifPresent(criteriaContext::addPredicate); - criteriaContext.finalizeQuery(); - } - } - - private void handleParentLocation(OpenmrsFhirCriteriaContext criteriaContext, ReferenceAndListParam parent) { - handleLocationReference(criteriaContext, "loc", parent).ifPresent(loc -> { - criteriaContext.getRoot().join("parentLocation").alias("loc"); - criteriaContext.addPredicate(loc); - criteriaContext.finalizeQuery(); - }); - } - - @Override - protected String paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @NonNull String param) { - switch (param) { - case org.hl7.fhir.r4.model.Location.SP_NAME: - return "name"; - case org.hl7.fhir.r4.model.Location.SP_ADDRESS_CITY: - return "cityVillage"; - case org.hl7.fhir.r4.model.Location.SP_ADDRESS_STATE: - return "stateProvince"; - case org.hl7.fhir.r4.model.Location.SP_ADDRESS_COUNTRY: - return "country"; - case org.hl7.fhir.r4.model.Location.SP_ADDRESS_POSTALCODE: - return "postalCode"; - default: - return super.paramToProp(criteriaContext, param); - - } - } - - @Override - public LocationTag getLocationTagByName(String tag) { - return locationService.getLocationTagByName(tag); - } - - @Override - public LocationTag saveLocationTag(LocationTag tag) { - return locationService.saveLocationTag(tag); - } - - @Override - public LocationAttributeType getLocationAttributeTypeByUuid(String uuid) { - return locationService.getLocationAttributeTypeByUuid(uuid); - } - - protected OpenmrsFhirCriteriaContext openmrsFhirCriteriaContext() { - EntityManager em = sessionFactory.getCurrentSession(); - CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaQuery cq = cb.createQuery(LocationAttribute.class); - Root root = cq.from(LocationAttribute.class); - - return new OpenmrsFhirCriteriaContext<>(em, cb, cq, root); - } + + @Autowired + LocationService locationService; + + @Override + protected void setupSearchParams(OpenmrsFhirCriteriaContext criteriaContext, SearchParameterMap theParams) { + theParams.getParameters().forEach(entry -> { + switch (entry.getKey()) { + case FhirConstants.NAME_SEARCH_HANDLER: + entry.getValue().forEach(param -> handleName(criteriaContext, (StringAndListParam) param.getParam())); + break; + case FhirConstants.CITY_SEARCH_HANDLER: + entry.getValue().forEach(param -> handleCity(criteriaContext, (StringAndListParam) param.getParam())); + break; + case FhirConstants.STATE_SEARCH_HANDLER: + entry.getValue().forEach(param -> handleState(criteriaContext, (StringAndListParam) param.getParam())); + break; + case FhirConstants.COUNTRY_SEARCH_HANDLER: + entry.getValue().forEach(param -> handleCountry(criteriaContext, (StringAndListParam) param.getParam())); + break; + case FhirConstants.POSTALCODE_SEARCH_HANDLER: + entry.getValue() + .forEach(param -> handlePostalCode(criteriaContext, (StringAndListParam) param.getParam())); + break; + case FhirConstants.LOCATION_REFERENCE_SEARCH_HANDLER: + entry.getValue().forEach( + param -> handleParentLocation(criteriaContext, (ReferenceAndListParam) param.getParam())); + break; + case FhirConstants.TAG_SEARCH_HANDLER: + entry.getValue().forEach(param -> handleTag(criteriaContext, (TokenAndListParam) param.getParam())); + break; + case FhirConstants.COMMON_SEARCH_HANDLER: + handleCommonSearchParameters(criteriaContext, entry.getValue()).ifPresent(criteriaContext::addPredicate); + criteriaContext.finalizeQuery(); + break; + } + }); + } + + @Override + public List getActiveAttributesByLocationAndAttributeTypeUuid(@Nonnull Location location, + @Nonnull String locationAttributeTypeUuid) { + OpenmrsFhirCriteriaContext criteriaContext = openmrsFhirCriteriaContext(); + criteriaContext.getCriteriaQuery().select(criteriaContext.getRoot()); + + criteriaContext.addPredicate(criteriaContext.getCriteriaBuilder().and( + criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().join("location").get("locationId"), + location.getId()), + criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().join("attributeType").get("uuid"), + locationAttributeTypeUuid), + criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("voided"), false))); + + return criteriaContext.getEntityManager().createQuery(criteriaContext.finalizeQuery()).getResultList(); + } + + private void handleName(OpenmrsFhirCriteriaContext criteriaContext, StringAndListParam namePattern) { + handleAndListParam(criteriaContext.getCriteriaBuilder(), namePattern, (name) -> propertyLike(criteriaContext, criteriaContext.getRoot(), "name", name)) + .ifPresent(criteriaContext::addPredicate); + criteriaContext.finalizeQuery(); + } + + private void handleCity(OpenmrsFhirCriteriaContext criteriaContext, StringAndListParam cityPattern) { + handleAndListParam(criteriaContext.getCriteriaBuilder(), cityPattern, (city) -> propertyLike(criteriaContext, criteriaContext.getRoot(), "cityVillage", city)) + .ifPresent(criteriaContext::addPredicate); + criteriaContext.finalizeQuery(); + } + + private void handleCountry(OpenmrsFhirCriteriaContext criteriaContext, StringAndListParam countryPattern) { + handleAndListParam(criteriaContext.getCriteriaBuilder(), countryPattern, (country) -> propertyLike(criteriaContext, criteriaContext.getRoot(), "country", country)) + .ifPresent(criteriaContext::addPredicate); + criteriaContext.finalizeQuery(); + } + + private void handlePostalCode(OpenmrsFhirCriteriaContext criteriaContext, + StringAndListParam postalCodePattern) { + handleAndListParam(criteriaContext.getCriteriaBuilder(), postalCodePattern, (postalCode) -> propertyLike(criteriaContext, criteriaContext.getRoot(), "postalCode", postalCode)) + .ifPresent(criteriaContext::addPredicate); + criteriaContext.finalizeQuery(); + } + + private void handleState(OpenmrsFhirCriteriaContext criteriaContext, StringAndListParam statePattern) { + handleAndListParam(criteriaContext.getCriteriaBuilder(), statePattern, (state) -> propertyLike(criteriaContext, criteriaContext.getRoot(), "stateProvince", state)) + .ifPresent(criteriaContext::addPredicate); + criteriaContext.finalizeQuery(); + } + + private void handleTag(OpenmrsFhirCriteriaContext criteriaContext, TokenAndListParam tags) { + if (tags != null) { + criteriaContext.addJoin("tags", "t"); + handleAndListParam(criteriaContext.getCriteriaBuilder(), tags, + (tag) -> criteriaContext.getJoin("t").map(locationTag -> criteriaContext.getCriteriaBuilder().equal(locationTag.get("name"), tag.getValue())) + ).ifPresent(criteriaContext::addPredicate); + criteriaContext.finalizeQuery(); + } + } + + private void handleParentLocation(OpenmrsFhirCriteriaContext criteriaContext, ReferenceAndListParam parent) { + handleLocationReference(criteriaContext, "loc", parent).ifPresent(loc -> { + criteriaContext.getRoot().join("parentLocation").alias("loc"); + criteriaContext.addPredicate(loc); + criteriaContext.finalizeQuery(); + }); + } + + @Override + protected String paramToProp(OpenmrsFhirCriteriaContext criteriaContext, @NonNull String param) { + switch (param) { + case org.hl7.fhir.r4.model.Location.SP_NAME: + return "name"; + case org.hl7.fhir.r4.model.Location.SP_ADDRESS_CITY: + return "cityVillage"; + case org.hl7.fhir.r4.model.Location.SP_ADDRESS_STATE: + return "stateProvince"; + case org.hl7.fhir.r4.model.Location.SP_ADDRESS_COUNTRY: + return "country"; + case org.hl7.fhir.r4.model.Location.SP_ADDRESS_POSTALCODE: + return "postalCode"; + default: + return super.paramToProp(criteriaContext, param); + + } + } + + @Override + public LocationTag getLocationTagByName(String tag) { + return locationService.getLocationTagByName(tag); + } + + @Override + public LocationTag saveLocationTag(LocationTag tag) { + return locationService.saveLocationTag(tag); + } + + @Override + public LocationAttributeType getLocationAttributeTypeByUuid(String uuid) { + return locationService.getLocationAttributeTypeByUuid(uuid); + } + + protected OpenmrsFhirCriteriaContext openmrsFhirCriteriaContext() { + EntityManager em = sessionFactory.getCurrentSession(); + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(LocationAttribute.class); + Root root = cq.from(LocationAttribute.class); + + return new OpenmrsFhirCriteriaContext<>(em, cb, cq, root); + } } diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirMedicationRequestDaoImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirMedicationRequestDaoImpl.java index 29a2293d8..f8482cb78 100644 --- a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirMedicationRequestDaoImpl.java +++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirMedicationRequestDaoImpl.java @@ -156,8 +156,7 @@ private void handleCodedConcept(OpenmrsFhirCriteriaContext criteriaCo criteriaContext.getRoot().join("concept"); } - handleCodeableConcept(criteriaContext, code, "c", "cm", "crt") - .ifPresent(criteriaContext::addPredicate); + handleCodeableConcept(criteriaContext, code, "c", "cm", "crt").ifPresent(criteriaContext::addPredicate); criteriaContext.finalizeQuery(); } } diff --git a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirPractitionerDaoImpl.java b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirPractitionerDaoImpl.java index ac1ccfdb9..a69e914b4 100644 --- a/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirPractitionerDaoImpl.java +++ b/api/src/main/java/org/openmrs/module/fhir2/api/dao/impl/FhirPractitionerDaoImpl.java @@ -50,10 +50,12 @@ public List getActiveAttributesByPractitionerAndAttributeType OpenmrsFhirCriteriaContext criteriaContext = openmrsFhirCriteriaContext(); criteriaContext.getCriteriaQuery().select(criteriaContext.getRoot()); - criteriaContext.addPredicate(criteriaContext.getCriteriaBuilder().and(criteriaContext.getCriteriaBuilder() - .equal(criteriaContext.getRoot().join("provider").get("providerId"), provider.getId()),criteriaContext.getCriteriaBuilder() - .equal(criteriaContext.getRoot().join("attributeType").get("uuid"), providerAttributeTypeUuid),criteriaContext.getCriteriaBuilder() - .equal(criteriaContext.getRoot().get("voided"), false))); + criteriaContext.addPredicate(criteriaContext.getCriteriaBuilder().and( + criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().join("provider").get("providerId"), + provider.getId()), + criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().join("attributeType").get("uuid"), + providerAttributeTypeUuid), + criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("voided"), false))); return criteriaContext.getEntityManager().createQuery(criteriaContext.finalizeQuery()).getResultList(); } 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 7136ba5ca..7c7a6512b 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 @@ -47,7 +47,7 @@ public class FhirRelatedPersonDaoImpl extends BaseFhirDao implemen @Override protected void setupSearchParams(OpenmrsFhirCriteriaContext criteriaContext, SearchParameterMap theParams) { - criteriaContext.addJoin("personA","m").finalizeQuery(); + criteriaContext.addJoin("personA", "m").finalizeQuery(); theParams.getParameters().forEach(entry -> { switch (entry.getKey()) { case FhirConstants.NAME_SEARCH_HANDLER: @@ -86,53 +86,49 @@ protected Collection paramToProps(OpenmrsFhirCriteriaContext crite } if (param.startsWith("address") && lacksAlias(criteriaContext, "pad")) { - criteriaContext.addJoin("m.addresses","pad",javax.persistence.criteria.JoinType.LEFT).finalizeQuery(); + criteriaContext.addJoin("m.addresses", "pad", javax.persistence.criteria.JoinType.LEFT).finalizeQuery(); } else if (param.equals(SP_NAME) || param.equals(SP_GIVEN) || param.equals(SP_FAMILY)) { if (lacksAlias(criteriaContext, "pn")) { - criteriaContext.addJoin("m.names","pn",javax.persistence.criteria.JoinType.LEFT).finalizeQuery(); + criteriaContext.addJoin("m.names", "pn", javax.persistence.criteria.JoinType.LEFT).finalizeQuery(); } Root subRoot = criteriaContext.getCriteriaQuery().subquery(Integer.class).from(PersonName.class); Predicate predicate = criteriaContext.getCriteriaBuilder() - .and( - criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("pn.voided"), false), + .and(criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("pn.voided"), false), criteriaContext.getCriteriaBuilder().or( criteriaContext.getCriteriaBuilder().and( - criteriaContext.getCriteriaBuilder() - .equal(criteriaContext.getRoot().get("pn.preferred"), true), + criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("pn.preferred"), + true), criteriaContext.getCriteriaBuilder().equal(criteriaContext.getRoot().get("pn.personNameId"), - criteriaContext.getCriteriaQuery().subquery(Integer.class).select( - criteriaContext.getCriteriaBuilder().min(criteriaContext.getRoot().get("pn1.personNameId"))) + criteriaContext.getCriteriaQuery().subquery(Integer.class) + .select(criteriaContext.getCriteriaBuilder() + .min(criteriaContext.getRoot().get("pn1.personNameId"))) .where(criteriaContext.getCriteriaBuilder().and( criteriaContext.getCriteriaBuilder().equal(subRoot.get("preferred"), true), criteriaContext.getCriteriaBuilder().equal( subRoot.get("person_id"), criteriaContext.getRoot().get("person_id")))))), - criteriaContext.getCriteriaBuilder().and( - criteriaContext.getCriteriaBuilder() - .not(criteriaContext.getCriteriaBuilder() - .exists(criteriaContext.getCriteriaQuery().subquery(Integer.class) - .select(criteriaContext.getRoot().get("pn2.personNameId")) - .where(criteriaContext.getCriteriaBuilder().and( - criteriaContext.getCriteriaBuilder() - .equal(subRoot.get("pn2.preferred"), true), - criteriaContext.getCriteriaBuilder().equal( - subRoot.get("pn2").get("person").get("personId"), - criteriaContext.getRoot().get("personId")))))), + criteriaContext.getCriteriaBuilder().and(criteriaContext.getCriteriaBuilder().not( + criteriaContext.getCriteriaBuilder().exists(criteriaContext.getCriteriaQuery() + .subquery(Integer.class).select(criteriaContext.getRoot().get("pn2.personNameId")) + .where(criteriaContext.getCriteriaBuilder().and( + criteriaContext.getCriteriaBuilder().equal(subRoot.get("pn2.preferred"), true), + criteriaContext.getCriteriaBuilder().equal( + subRoot.get("pn2").get("person").get("personId"), + criteriaContext.getRoot().get("personId")))))), criteriaContext.getCriteriaBuilder().equal( criteriaContext.getRoot().get("pn.personNameId"), criteriaContext.getCriteriaQuery().subquery(Integer.class) .select(criteriaContext.getCriteriaBuilder() .min(criteriaContext.getRoot().get("pn3.personNameId"))) .where(criteriaContext.getCriteriaBuilder().and( - criteriaContext.getCriteriaBuilder() - .equal(subRoot.get("pn3.preferred"), false), + criteriaContext.getCriteriaBuilder().equal(subRoot.get("pn3.preferred"), + false), criteriaContext.getCriteriaBuilder().equal( subRoot.get("pn3").get("person").get("personId"), criteriaContext.getRoot().get("personId")))))), - criteriaContext.getCriteriaBuilder() - .isNull(criteriaContext.getRoot().get("pn.personNameId")))); + criteriaContext.getCriteriaBuilder().isNull(criteriaContext.getRoot().get("pn.personNameId")))); criteriaContext.getCriteriaQuery().where(predicate); String[] properties = 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 48642950d..14dc2aade 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 @@ -13,6 +13,8 @@ import javax.persistence.EntityManager; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.From; +import javax.persistence.criteria.Join; import javax.persistence.criteria.JoinType; import javax.persistence.criteria.Order; import javax.persistence.criteria.Predicate; @@ -22,6 +24,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import lombok.Getter; import lombok.NonNull; @@ -30,15 +33,6 @@ @RequiredArgsConstructor public class OpenmrsFhirCriteriaContext { - @Getter - @RequiredArgsConstructor - public static final class CriteriaJoin { - - private final String attributeName; - - private final JoinType joinType; - } - @Getter @NonNull private final EntityManager entityManager; @@ -55,7 +49,7 @@ public static final class CriteriaJoin { @NonNull private final Root root; - private final Map aliases = new LinkedHashMap<>(); + private final Map> aliases = new LinkedHashMap<>(); private final List predicates = new ArrayList<>(); @@ -64,17 +58,28 @@ public static final class CriteriaJoin { @Getter private final List results = new ArrayList<>(); - public OpenmrsFhirCriteriaContext addJoin(@Nonnull String attributeName, @Nonnull String alias) { + public Join addJoin(@Nonnull String attributeName, @Nonnull String alias) { return addJoin(attributeName, alias, JoinType.INNER); } - public OpenmrsFhirCriteriaContext addJoin(@Nonnull String attributeName, @Nonnull String alias, + public Join addJoin(@Nonnull String attributeName, @Nonnull String alias, @Nonnull JoinType joinType) { + return addJoin(getRoot(), attributeName, alias, joinType); + } + + public Join addJoin(From from, @Nonnull String attributeName, @Nonnull String alias) { + return addJoin(from, attributeName, alias, JoinType.INNER); + } + + public Join addJoin(From from, @Nonnull String attributeName, @Nonnull String alias, + @Nonnull JoinType joinType) { if (!aliases.containsKey(alias)) { - aliases.put(alias, new CriteriaJoin(attributeName, joinType)); + Join join = from.join(attributeName, joinType); + join.alias(alias); + aliases.put(alias, join); } - - return this; + + return aliases.get(alias); } public OpenmrsFhirCriteriaContext addPredicate(Predicate predicate) { @@ -91,16 +96,16 @@ public OpenmrsFhirCriteriaContext addResults(T result) { results.add(result); return this; } + + public Optional> getJoin(String alias) { + return Optional.ofNullable(aliases.get(alias)); + } public boolean hasAlias(String alias) { return aliases.containsKey(alias); } public CriteriaQuery finalizeQuery() { - for (Map.Entry alias : aliases.entrySet()) { - root.join(alias.getValue().getAttributeName(), alias.getValue().getJoinType()).alias(alias.getKey()); - } - return criteriaQuery.where(predicates.toArray(new Predicate[0])).orderBy(orders); } } diff --git a/api/src/test/java/org/openmrs/module/fhir2/api/search/LocationSearchQueryTest.java b/api/src/test/java/org/openmrs/module/fhir2/api/search/LocationSearchQueryTest.java index 055100431..137d79167 100644 --- a/api/src/test/java/org/openmrs/module/fhir2/api/search/LocationSearchQueryTest.java +++ b/api/src/test/java/org/openmrs/module/fhir2/api/search/LocationSearchQueryTest.java @@ -297,6 +297,7 @@ public void searchForLocations_shouldReturnEmptyCollectionWhenCalledWithUnknownS @Test public void searchForLocations_shouldReturnLocationsContainingGivenTag() { + org.apache.log4j.LogManager.getLogger("org.hibernate.SQL").setLevel(org.apache.log4j.Level.DEBUG); TokenAndListParam locationTag = new TokenAndListParam() .addAnd(new TokenOrListParam(FhirConstants.OPENMRS_FHIR_EXT_LOCATION_TAG, LOGIN_LOCATION_TAG_NAME)); SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.TAG_SEARCH_HANDLER, locationTag); @@ -305,7 +306,7 @@ public void searchForLocations_shouldReturnLocationsContainingGivenTag() { List resultList = get(locations); assertThat(locations, notNullValue()); - assertThat(resultList.size(), equalTo(2)); + assertThat(resultList, hasSize(equalTo(2))); assertThat(resultList.get(0).getMeta().getTag().iterator().next().getCode(), equalTo(LOGIN_LOCATION_TAG_NAME)); }