From 278d5cc4816b574141be6651814b517021da551e Mon Sep 17 00:00:00 2001 From: jmarchionatto <60409882+jmarchionatto@users.noreply.github.com> Date: Fri, 20 Jan 2023 12:25:57 -0500 Subject: [PATCH] Issue 4426 add sorting to mdm queries (#4439) * Add sorting capability to $mdm-query-links operation * Add exception code * Improve code * Add test combining pagination with sort Co-authored-by: juan.marchionatto --- ...pability-to-mdm-query-links-operation.yaml | 4 + .../docs/server_jpa_mdm/mdm_operations.md | 14 ++ .../fhir/jpa/dao/mdm/MdmLinkDaoJpaImpl.java | 114 +++++++---- .../jpa/mdm/svc/MdmControllerSvcImpl.java | 29 ++- .../jpa/mdm/svc/MdmLinkQuerySvcImplSvc.java | 29 ++- .../provider/MdmProviderQueryLinkR4Test.java | 184 +++++++++++++++++- .../jpa/mdm/svc/MdmControllerSvcImplTest.java | 5 +- .../mdm/api/MdmQuerySearchParameters.java | 108 +++++++--- .../fhir/mdm/provider/BaseMdmProvider.java | 7 +- .../mdm/provider/MdmProviderDstu3Plus.java | 23 ++- .../mdm/api/MdmQuerySearchParametersTest.java | 3 +- 11 files changed, 427 insertions(+), 93 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_4_0/4426-add-sort-capability-to-mdm-query-links-operation.yaml diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_4_0/4426-add-sort-capability-to-mdm-query-links-operation.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_4_0/4426-add-sort-capability-to-mdm-query-links-operation.yaml new file mode 100644 index 00000000000..741f914e7d6 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_4_0/4426-add-sort-capability-to-mdm-query-links-operation.yaml @@ -0,0 +1,4 @@ +--- +type: add +issue: 4426 +title: "Added sorting capability to $mdm-query-links operation." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_mdm/mdm_operations.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_mdm/mdm_operations.md index 76b55a1d97e..bcfc3853788 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_mdm/mdm_operations.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_mdm/mdm_operations.md @@ -113,6 +113,14 @@ Use the `$mdm-query-links` operation to view MDM links. The results returned are The number of links to be returned in a page. + + _sort + String + 0..1 + + The sort specification (see sort note below). + + resourceType String @@ -124,6 +132,12 @@ Use the `$mdm-query-links` operation to view MDM links. The results returned are +Sort note: sort is specified by adding one or more comma-separated MdmLink property names prefixed by '-' (minus sign) to indicate descending order. + +#### Sort specification example +```url +http://example.com/$mdm-query-links?_sort=-myScore,myCreated +``` ### Example Use an HTTP GET like `http://example.com/$mdm-query-links?matchResult=POSSIBLE_MATCH` or an HTTP POST to the following URL to invoke this operation: diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/mdm/MdmLinkDaoJpaImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/mdm/MdmLinkDaoJpaImpl.java index 335809dcd7c..0e122b87981 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/mdm/MdmLinkDaoJpaImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/mdm/MdmLinkDaoJpaImpl.java @@ -33,6 +33,7 @@ import ca.uhn.fhir.mdm.api.MdmQuerySearchParameters; import ca.uhn.fhir.mdm.api.paging.MdmPageRequest; import ca.uhn.fhir.mdm.dao.IMdmLinkDao; import ca.uhn.fhir.mdm.model.MdmPidTuple; +import ca.uhn.fhir.rest.api.SortOrderEnum; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.ListUtils; @@ -49,14 +50,26 @@ import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Expression; +import javax.persistence.criteria.Order; +import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; +import javax.validation.constraints.NotNull; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; +import static ca.uhn.fhir.mdm.api.MdmQuerySearchParameters.GOLDEN_RESOURCE_NAME; +import static ca.uhn.fhir.mdm.api.MdmQuerySearchParameters.GOLDEN_RESOURCE_PID_NAME; +import static ca.uhn.fhir.mdm.api.MdmQuerySearchParameters.LINK_SOURCE_NAME; +import static ca.uhn.fhir.mdm.api.MdmQuerySearchParameters.MATCH_RESULT_NAME; +import static ca.uhn.fhir.mdm.api.MdmQuerySearchParameters.PARTITION_ID_NAME; +import static ca.uhn.fhir.mdm.api.MdmQuerySearchParameters.RESOURCE_TYPE_NAME; +import static ca.uhn.fhir.mdm.api.MdmQuerySearchParameters.SOURCE_PID_NAME; + public class MdmLinkDaoJpaImpl implements IMdmLinkDao { @Autowired IMdmLinkJpaRepository myMdmLinkDao; @@ -107,7 +120,7 @@ public class MdmLinkDaoJpaImpl implements IMdmLinkDao { public List findPidByResourceNameAndThreshold(String theResourceName, Date theHighThreshold, Pageable thePageable) { return myMdmLinkDao.findPidByResourceNameAndThreshold(theResourceName,theHighThreshold, thePageable) .stream() - .map( theResourcePids -> JpaPid.fromId(theResourcePids)) + .map(JpaPid::fromId) .collect(Collectors.toList()); } @@ -115,7 +128,7 @@ public class MdmLinkDaoJpaImpl implements IMdmLinkDao { public List findPidByResourceNameAndThresholdAndPartitionId(String theResourceName, Date theHighThreshold, List thePartitionIds, Pageable thePageable) { return myMdmLinkDao.findPidByResourceNameAndThresholdAndPartitionId(theResourceName,theHighThreshold, thePartitionIds, thePageable) .stream() - .map( theResourcePids -> JpaPid.fromId(theResourcePids)) + .map(JpaPid::fromId) .collect(Collectors.toList()); } @@ -183,8 +196,14 @@ public class MdmLinkDaoJpaImpl implements IMdmLinkDao { @Override @Deprecated - public Page search(IIdType theGoldenResourceId, IIdType theSourceId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource, MdmPageRequest thePageRequest, List thePartitionId) { - MdmQuerySearchParameters mdmQuerySearchParameters = new MdmQuerySearchParameters(theGoldenResourceId, theSourceId, theMatchResult, theLinkSource, thePageRequest, thePartitionId, null); + public Page search(IIdType theGoldenResourceId, IIdType theSourceId, MdmMatchResultEnum theMatchResult, + MdmLinkSourceEnum theLinkSource, MdmPageRequest thePageRequest, List thePartitionIds) { + MdmQuerySearchParameters mdmQuerySearchParameters = new MdmQuerySearchParameters(thePageRequest) + .setGoldenResourceId(theGoldenResourceId) + .setSourceId(theSourceId) + .setMatchResult(theMatchResult) + .setLinkSource(theLinkSource) + .setPartitionIds(thePartitionIds); return search(mdmQuerySearchParameters); } @@ -193,37 +212,14 @@ public class MdmLinkDaoJpaImpl implements IMdmLinkDao { CriteriaBuilder criteriaBuilder = myEntityManager.getCriteriaBuilder(); CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(MdmLink.class); Root from = criteriaQuery.from(MdmLink.class); + List orderList = getOrderList(theParams, criteriaBuilder, from); - List andPredicates = new ArrayList<>(); - - if (theParams.getGoldenResourceId() != null) { - Predicate goldenResourcePredicate = criteriaBuilder.equal(from.get("myGoldenResourcePid").as(Long.class), (myIdHelperService.getPidOrThrowException(RequestPartitionId.allPartitions(), theParams.getGoldenResourceId())).getId()); - andPredicates.add(goldenResourcePredicate); - } - if (theParams.getSourceId() != null) { - Predicate sourceIdPredicate = criteriaBuilder.equal(from.get("mySourcePid").as(Long.class), (myIdHelperService.getPidOrThrowException(RequestPartitionId.allPartitions(), theParams.getSourceId())).getId()); - andPredicates.add(sourceIdPredicate); - } - if (theParams.getMatchResult() != null) { - Predicate matchResultPredicate = criteriaBuilder.equal(from.get("myMatchResult").as(MdmMatchResultEnum.class), theParams.getMatchResult()); - andPredicates.add(matchResultPredicate); - } - if (theParams.getLinkSource() != null) { - Predicate linkSourcePredicate = criteriaBuilder.equal(from.get("myLinkSource").as(MdmLinkSourceEnum.class), theParams.getLinkSource()); - andPredicates.add(linkSourcePredicate); - } - if (!CollectionUtils.isEmpty(theParams.getPartitionIds())) { - Expression exp = from.get("myPartitionId").get("myPartitionId").as(Integer.class); - Predicate linkSourcePredicate = exp.in(theParams.getPartitionIds()); - andPredicates.add(linkSourcePredicate); - } - - if (theParams.getResourceType() != null) { - Predicate resourceTypePredicate = criteriaBuilder.equal(from.get("myGoldenResource").get("myResourceType").as(String.class), theParams.getResourceType()); - andPredicates.add(resourceTypePredicate); - } + List andPredicates = buildPredicates(theParams, criteriaBuilder, from); Predicate finalQuery = criteriaBuilder.and(andPredicates.toArray(new Predicate[0])); + if ( ! orderList.isEmpty()) { + criteriaQuery.orderBy(orderList); + } TypedQuery typedQuery = myEntityManager.createQuery(criteriaQuery.where(finalQuery)); CriteriaQuery countQuery = criteriaBuilder.createQuery(Long.class); @@ -232,11 +228,63 @@ public class MdmLinkDaoJpaImpl implements IMdmLinkDao { Long totalResults = myEntityManager.createQuery(countQuery).getSingleResult(); MdmPageRequest pageRequest = theParams.getPageRequest(); - return new PageImpl<>(typedQuery.setFirstResult(pageRequest.getOffset()).setMaxResults(pageRequest.getCount()).getResultList(), + + List result = typedQuery + .setFirstResult(pageRequest.getOffset()) + .setMaxResults(pageRequest.getCount()) + .getResultList(); + + return new PageImpl<>(result, PageRequest.of(pageRequest.getPage(), pageRequest.getCount()), totalResults); } + @NotNull + private List buildPredicates(MdmQuerySearchParameters theParams, CriteriaBuilder criteriaBuilder, Root from) { + List andPredicates = new ArrayList<>(); + if (theParams.getGoldenResourceId() != null) { + Predicate goldenResourcePredicate = criteriaBuilder.equal(from.get(GOLDEN_RESOURCE_PID_NAME).as(Long.class), (myIdHelperService.getPidOrThrowException(RequestPartitionId.allPartitions(), theParams.getGoldenResourceId())).getId()); + andPredicates.add(goldenResourcePredicate); + } + if (theParams.getSourceId() != null) { + Predicate sourceIdPredicate = criteriaBuilder.equal(from.get(SOURCE_PID_NAME).as(Long.class), (myIdHelperService.getPidOrThrowException(RequestPartitionId.allPartitions(), theParams.getSourceId())).getId()); + andPredicates.add(sourceIdPredicate); + } + if (theParams.getMatchResult() != null) { + Predicate matchResultPredicate = criteriaBuilder.equal(from.get(MATCH_RESULT_NAME).as(MdmMatchResultEnum.class), theParams.getMatchResult()); + andPredicates.add(matchResultPredicate); + } + if (theParams.getLinkSource() != null) { + Predicate linkSourcePredicate = criteriaBuilder.equal(from.get(LINK_SOURCE_NAME).as(MdmLinkSourceEnum.class), theParams.getLinkSource()); + andPredicates.add(linkSourcePredicate); + } + if (!CollectionUtils.isEmpty(theParams.getPartitionIds())) { + Expression exp = from.get(PARTITION_ID_NAME).get(PARTITION_ID_NAME).as(Integer.class); + Predicate linkSourcePredicate = exp.in(theParams.getPartitionIds()); + andPredicates.add(linkSourcePredicate); + } + + if (theParams.getResourceType() != null) { + Predicate resourceTypePredicate = criteriaBuilder.equal(from.get(GOLDEN_RESOURCE_NAME).get(RESOURCE_TYPE_NAME).as(String.class), theParams.getResourceType()); + andPredicates.add(resourceTypePredicate); + } + + return andPredicates; + } + + + private List getOrderList(MdmQuerySearchParameters theParams, CriteriaBuilder criteriaBuilder, Root from) { + if (CollectionUtils.isEmpty(theParams.getSort())) { + return Collections.emptyList(); + } + + return theParams.getSort().stream().map(sortSpec -> { + Path path = from.get(sortSpec.getParamName()); + return sortSpec.getOrder() == SortOrderEnum.DESC ? criteriaBuilder.desc(path) : criteriaBuilder.asc(path); + }) + .collect(Collectors.toList()); + } + @Override public Optional findBySourcePidAndMatchResult(JpaPid theSourcePid, MdmMatchResultEnum theMatch) { return myMdmLinkDao.findBySourcePidAndMatchResult((theSourcePid).getId(), theMatch); diff --git a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmControllerSvcImpl.java b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmControllerSvcImpl.java index 0fec83d773a..57093c633d3 100644 --- a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmControllerSvcImpl.java +++ b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmControllerSvcImpl.java @@ -103,14 +103,24 @@ public class MdmControllerSvcImpl implements IMdmControllerSvc { @Override @Deprecated public Page queryLinks(@Nullable String theGoldenResourceId, @Nullable String theSourceResourceId, @Nullable String theMatchResult, @Nullable String theLinkSource, MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest) { - MdmQuerySearchParameters mdmQuerySearchParameters = new MdmQuerySearchParameters(theGoldenResourceId, theSourceResourceId, theMatchResult, theLinkSource, thePageRequest, null, null); + MdmQuerySearchParameters mdmQuerySearchParameters = new MdmQuerySearchParameters(thePageRequest) + .setGoldenResourceId(theGoldenResourceId) + .setSourceId(theSourceResourceId) + .setMatchResult(theMatchResult) + .setLinkSource(theLinkSource); return queryLinksFromPartitionList(mdmQuerySearchParameters, theMdmTransactionContext); } @Override @Deprecated - public Page queryLinks(@Nullable String theGoldenResourceId, @Nullable String theSourceResourceId, @Nullable String theMatchResult, @Nullable String theLinkSource, MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest, @Nullable RequestDetails theRequestDetails) { - MdmQuerySearchParameters mdmQuerySearchParameters = new MdmQuerySearchParameters(theGoldenResourceId, theSourceResourceId, theMatchResult, theLinkSource, thePageRequest, null, null); + public Page queryLinks(@Nullable String theGoldenResourceId, @Nullable String theSourceResourceId, + @Nullable String theMatchResult, @Nullable String theLinkSource, MdmTransactionContext theMdmTransactionContext, + MdmPageRequest thePageRequest, @Nullable RequestDetails theRequestDetails) { + MdmQuerySearchParameters mdmQuerySearchParameters = new MdmQuerySearchParameters(thePageRequest) + .setGoldenResourceId(theGoldenResourceId) + .setSourceId(theSourceResourceId) + .setMatchResult(theMatchResult) + .setLinkSource(theLinkSource); return queryLinks(mdmQuerySearchParameters, theMdmTransactionContext, theRequestDetails); } @@ -130,8 +140,16 @@ public class MdmControllerSvcImpl implements IMdmControllerSvc { @Override @Deprecated - public Page queryLinksFromPartitionList(@Nullable String theGoldenResourceId, @Nullable String theSourceResourceId, @Nullable String theMatchResult, @Nullable String theLinkSource, MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest, @Nullable List thePartitionIds) { - MdmQuerySearchParameters mdmQuerySearchParameters = new MdmQuerySearchParameters(theGoldenResourceId, theSourceResourceId, theMatchResult, theLinkSource, thePageRequest, thePartitionIds, null); + public Page queryLinksFromPartitionList(@Nullable String theGoldenResourceId, @Nullable String theSourceResourceId, + @Nullable String theMatchResult, @Nullable String theLinkSource, + MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest, + @Nullable List thePartitionIds) { + MdmQuerySearchParameters mdmQuerySearchParameters = new MdmQuerySearchParameters(thePageRequest) + .setGoldenResourceId(theGoldenResourceId) + .setSourceId(theSourceResourceId) + .setMatchResult(theMatchResult) + .setLinkSource(theLinkSource) + .setPartitionIds(thePartitionIds); return queryLinksFromPartitionList(mdmQuerySearchParameters, theMdmTransactionContext); } @@ -214,7 +232,6 @@ public class MdmControllerSvcImpl implements IMdmControllerSvc { if (theBatchSize != null && theBatchSize.getValue() !=null && theBatchSize.getValue().longValue() > 0) { params.setBatchSize(theBatchSize.getValue().intValue()); } - ReadPartitionIdRequestDetails details= new ReadPartitionIdRequestDetails(null, RestOperationTypeEnum.EXTENDED_OPERATION_SERVER, null, null, null); params.setRequestPartitionId(RequestPartitionId.allPartitions()); theUrls.forEach(params::addUrl); diff --git a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmLinkQuerySvcImplSvc.java b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmLinkQuerySvcImplSvc.java index 20783cfed73..144a43c3953 100644 --- a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmLinkQuerySvcImplSvc.java +++ b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmLinkQuerySvcImplSvc.java @@ -52,21 +52,33 @@ public class MdmLinkQuerySvcImplSvc implements IMdmLinkQuerySvc { @Deprecated @Transactional public Page queryLinks(IIdType theGoldenResourceId, IIdType theSourceResourceId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmContext, MdmPageRequest thePageRequest) { - MdmQuerySearchParameters mdmQuerySearchParameters = new MdmQuerySearchParameters(theGoldenResourceId, theSourceResourceId, theMatchResult, theLinkSource, thePageRequest, null, null); + MdmQuerySearchParameters mdmQuerySearchParameters = new MdmQuerySearchParameters(thePageRequest) + .setGoldenResourceId(theGoldenResourceId) + .setSourceId(theSourceResourceId) + .setMatchResult(theMatchResult) + .setLinkSource(theLinkSource); + return queryLinks(mdmQuerySearchParameters, theMdmContext); } @Override @Deprecated @Transactional - public Page queryLinks(IIdType theGoldenResourceId, IIdType theSourceResourceId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmContext, MdmPageRequest thePageRequest, List thePartitionId) { - MdmQuerySearchParameters mdmQuerySearchParameters = new MdmQuerySearchParameters(theGoldenResourceId, theSourceResourceId, theMatchResult, theLinkSource, thePageRequest, thePartitionId, null); + public Page queryLinks(IIdType theGoldenResourceId, IIdType theSourceResourceId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmContext, MdmPageRequest thePageRequest, List thePartitionIds) { + MdmQuerySearchParameters mdmQuerySearchParameters = new MdmQuerySearchParameters(thePageRequest) + .setGoldenResourceId(theGoldenResourceId) + .setSourceId(theSourceResourceId) + .setMatchResult(theMatchResult) + .setLinkSource(theLinkSource) + .setPartitionIds(thePartitionIds); + return queryLinks(mdmQuerySearchParameters, theMdmContext); } @Override @Transactional public Page queryLinks(MdmQuerySearchParameters theMdmQuerySearchParameters, MdmTransactionContext theMdmContext) { + @SuppressWarnings("unchecked") Page mdmLinks = myMdmLinkDaoSvc.executeTypedQuery(theMdmQuerySearchParameters); return mdmLinks.map(myMdmModelConverterSvc::toJson); } @@ -80,9 +92,14 @@ public class MdmLinkQuerySvcImplSvc implements IMdmLinkQuerySvc { @Override @Transactional - public Page getDuplicateGoldenResources(MdmTransactionContext theMdmContext, MdmPageRequest thePageRequest, List thePartitionId, String theRequestResourceType) { - MdmQuerySearchParameters mdmQuerySearchParameters = - new MdmQuerySearchParameters(null, null, MdmMatchResultEnum.POSSIBLE_DUPLICATE, null, thePageRequest, thePartitionId, theRequestResourceType); + public Page getDuplicateGoldenResources(MdmTransactionContext theMdmContext, MdmPageRequest thePageRequest, + List thePartitionIds, String theRequestResourceType) { + MdmQuerySearchParameters mdmQuerySearchParameters = new MdmQuerySearchParameters(thePageRequest) + .setMatchResult(MdmMatchResultEnum.POSSIBLE_DUPLICATE) + .setPartitionIds(thePartitionIds) + .setResourceType(theRequestResourceType); + + @SuppressWarnings("unchecked") Page mdmLinkPage = myMdmLinkDaoSvc.executeTypedQuery(mdmQuerySearchParameters); return mdmLinkPage.map(myMdmModelConverterSvc::toJson); } diff --git a/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/provider/MdmProviderQueryLinkR4Test.java b/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/provider/MdmProviderQueryLinkR4Test.java index ecb3ac04695..a8afac6393c 100644 --- a/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/provider/MdmProviderQueryLinkR4Test.java +++ b/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/provider/MdmProviderQueryLinkR4Test.java @@ -10,23 +10,30 @@ import ca.uhn.fhir.mdm.api.MdmMatchResultEnum; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.util.ParametersUtil; import ca.uhn.fhir.util.StopWatch; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; import org.hl7.fhir.dstu3.model.UnsignedIntType; import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.DecimalType; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.StringType; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import java.util.Comparator; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import static org.hamcrest.MatcherAssert.assertThat; @@ -35,13 +42,16 @@ import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.lessThanOrEqualTo; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; public class MdmProviderQueryLinkR4Test extends BaseLinkR4Test { private static final Logger ourLog = LoggerFactory.getLogger(MdmProviderQueryLinkR4Test.class); + private static final int MDM_LINK_PROPERTY_COUNT = 9; private static final StringType RESOURCE_TYPE_PATIENT = new StringType("Patient"); private static final StringType RESOURCE_TYPE_OBSERVATION = new StringType("Observation"); + private StringType myLinkSource; private StringType myGoldenResource1Id; private StringType myGoldenResource2Id; @@ -74,27 +84,180 @@ public class MdmProviderQueryLinkR4Test extends BaseLinkR4Test { @Test public void testQueryLinkOneMatch() { - Parameters result = (Parameters) myMdmProvider.queryLinks(mySourcePatientId, myPatientId, null, null, new UnsignedIntType(0), new UnsignedIntType(10), myRequestDetails, null); + Parameters result = (Parameters) myMdmProvider.queryLinks(mySourcePatientId, myPatientId, null, null, new UnsignedIntType(0), new UnsignedIntType(10), new StringType(), myRequestDetails, null); ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(result)); List list = getParametersByName(result, "link"); assertThat(list, hasSize(1)); List part = list.get(0).getPart(); - assertMdmLink(7, part, mySourcePatientId.getValue(), myPatientId.getValue(), MdmMatchResultEnum.POSSIBLE_MATCH, "false", "true", null); + assertMdmLink(MDM_LINK_PROPERTY_COUNT, part, mySourcePatientId.getValue(), myPatientId.getValue(), MdmMatchResultEnum.POSSIBLE_MATCH, "false", "true", null); } @Test public void testQueryLinkWithResourceType() { - Parameters result = (Parameters) myMdmProvider.queryLinks(null, null, null, null, new UnsignedIntType(0), new UnsignedIntType(10), myRequestDetails, RESOURCE_TYPE_PATIENT); + Parameters result = (Parameters) myMdmProvider.queryLinks(null, null, null, null, new UnsignedIntType(0), new UnsignedIntType(10), new StringType(), myRequestDetails, RESOURCE_TYPE_PATIENT); ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(result)); List list = getParametersByName(result, "link"); assertThat("All resources with Patient type found", list, hasSize(3)); List part = list.get(0).getPart(); - assertMdmLink(7, part, mySourcePatientId.getValue(), myPatientId.getValue(), MdmMatchResultEnum.POSSIBLE_MATCH, "false", "true", null); + assertMdmLink(MDM_LINK_PROPERTY_COUNT, part, mySourcePatientId.getValue(), myPatientId.getValue(), MdmMatchResultEnum.POSSIBLE_MATCH, "false", "true", null); } + + @Nested + public class QueryLinkWithSort { + @Test + public void byCreatedDescending() { + Parameters result = (Parameters) myMdmProvider.queryLinks(null, null, null, null, + new UnsignedIntType(0), new UnsignedIntType(10), new StringType("-myCreated"), + myRequestDetails, new StringType("Patient")); + + List linkList = getParametersByName(result, "link"); + assertThat(linkList, hasSize(3)); + + List createdDates = linkList.stream().map(this::extractCreated).collect(Collectors.toList()); + + // by created descending + Comparator comp = Comparator.comparingLong( (Long e) -> e).reversed(); + List expected = createdDates.stream().sorted(comp).collect(Collectors.toList()); + + assertEquals(expected, createdDates); + } + + @Test + public void byScoreDescending() { + addScoresToLinksInCreationOrder(List.of(.5d, .1d, .3d)); + + Parameters result = (Parameters) myMdmProvider.queryLinks(null, null, null, null, + new UnsignedIntType(0), new UnsignedIntType(10), new StringType("-myScore"), + myRequestDetails, new StringType("Patient")); + + List linkList = getParametersByName(result, "link"); + assertThat(linkList, hasSize(3)); + + List scores = linkList.stream().map(this::extractScore).collect(Collectors.toList()); + + // by score descending + Comparator comp = Comparator.comparingDouble( (Double e) -> e).reversed(); + List expected = scores.stream().sorted(comp).collect(Collectors.toList()); + + assertEquals(expected, scores); + } + + + @Test + public void byScoreDescendingAndThenCreated() { + addScoresToLinksInCreationOrder(List.of(.4d, .4d, .3d)); + + Parameters result = (Parameters) myMdmProvider.queryLinks(null, null, null, null, + new UnsignedIntType(0), new UnsignedIntType(10), new StringType("-myScore,-myCreated"), + myRequestDetails, new StringType("Patient")); + + List linkList = getParametersByName(result, "link"); + assertThat(linkList, hasSize(3)); + + List> resultUpdatedScorePairs = linkList.stream() + .map(l -> Pair.of(extractCreated(l), extractScore(l))).collect(Collectors.toList()); + + // by desc score then desc created + Comparator> comp = Comparator + .comparing( (Pair p) -> p.getRight(), Comparator.reverseOrder() ) + .thenComparing(Pair::getLeft, Comparator.reverseOrder() ); + + List> expected = resultUpdatedScorePairs.stream().sorted(comp).collect(Collectors.toList()); + + assertEquals(expected, resultUpdatedScorePairs); + } + + @Test + public void testPaginationWhenSorting() { + addTwoMoreMdmLinks(); + addScoresToLinksInCreationOrder(List.of(.5d, .1d, .3d, .8d, .6d)); + List expectedScoresPage1 = List.of(.8d, .6d, .5d); + List expectedScoresPage2 = List.of(.3d, .1d); + + int pageSize = 3; + + // first page + + Parameters page1 = (Parameters) myMdmProvider.queryLinks(null, null, null, null, + new UnsignedIntType(0), new UnsignedIntType(pageSize), new StringType("-myScore"), + myRequestDetails, new StringType("Patient")); + + List linkListPage1 = getParametersByName(page1, "link"); + assertThat(linkListPage1, hasSize(pageSize)); + + List scoresPage1 = linkListPage1.stream().map(this::extractScore).collect(Collectors.toList()); + assertEquals(expectedScoresPage1, scoresPage1); + + // second page + + Parameters page2 = (Parameters) myMdmProvider.queryLinks(null, null, null, null, + new UnsignedIntType(pageSize), new UnsignedIntType(pageSize), new StringType("-myScore"), + myRequestDetails, new StringType("Patient")); + + List linkListPage2 = getParametersByName(page2, "link"); + assertThat(linkListPage2, hasSize(2)); + + List scoresPage2 = linkListPage2.stream().map(this::extractScore).collect(Collectors.toList()); + assertEquals(expectedScoresPage2, scoresPage2); + } + + + private Long extractCreated(Parameters.ParametersParameterComponent theParamComponent) { + Optional opt = ParametersUtil.getParameterPartValue(myFhirContext, theParamComponent, "linkUpdated"); + assertTrue(opt.isPresent()); + DecimalType createdDateDt = (DecimalType) opt.get(); + return createdDateDt.getValue().longValue(); + } + + + private Double extractScore(Parameters.ParametersParameterComponent theParamComponent) { + Optional opt = ParametersUtil.getParameterPartValue(myFhirContext, theParamComponent, "score"); + assertTrue(opt.isPresent()); + DecimalType scoreIntegerDt = (DecimalType) opt.get(); + assertNotNull(scoreIntegerDt.getValue()); + return scoreIntegerDt.getValue().doubleValue(); + } + + } + + private void addTwoMoreMdmLinks() { + createPatientAndUpdateLinks(buildFrankPatient()); + + // Add a possible duplicate + myLinkSource = new StringType(MdmLinkSourceEnum.AUTO.name()); + Patient sourcePatient1 = createGoldenPatient(); + myGoldenResource1Id = new StringType(sourcePatient1.getIdElement().toVersionless().getValue()); + JpaPid sourcePatient1Pid = runInTransaction(()->myIdHelperService.getPidOrNull(RequestPartitionId.allPartitions(), sourcePatient1)); + Patient sourcePatient2 = createGoldenPatient(); + myGoldenResource2Id = new StringType(sourcePatient2.getIdElement().toVersionless().getValue()); + JpaPid sourcePatient2Pid = runInTransaction(()->myIdHelperService.getPidOrNull(RequestPartitionId.allPartitions(), sourcePatient2)); + + MdmLink possibleDuplicateMdmLink = (MdmLink) myMdmLinkDaoSvc.newMdmLink(); + possibleDuplicateMdmLink.setGoldenResourcePersistenceId(sourcePatient1Pid); + possibleDuplicateMdmLink.setSourcePersistenceId(sourcePatient2Pid); + possibleDuplicateMdmLink.setMatchResult(MdmMatchResultEnum.POSSIBLE_DUPLICATE).setLinkSource(MdmLinkSourceEnum.AUTO); + saveLink(possibleDuplicateMdmLink); + } + + + private void addScoresToLinksInCreationOrder(List theScores) { + List links = myMdmLinkDao.findAll(); + assertThat(links, hasSize(theScores.size())); + + links.sort( Comparator.comparing(MdmLink::getCreated) ); + + for (int i = 0; i < links.size(); i++) { + MdmLink link = links.get(i); + link.setScore( theScores.get(i) ); + myMdmLinkDao.save(link); + } + } + + @Test public void testQueryLinkWithResourceTypeNoMatch() { - Parameters result = (Parameters) myMdmProvider.queryLinks(null, null, null, null, new UnsignedIntType(0), new UnsignedIntType(10), myRequestDetails, RESOURCE_TYPE_OBSERVATION); + Parameters result = (Parameters) myMdmProvider.queryLinks(null, null, null, null, new UnsignedIntType(0), new UnsignedIntType(10), new StringType(), myRequestDetails, RESOURCE_TYPE_OBSERVATION); ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(result)); List list = getParametersByName(result, "link"); assertThat(list, hasSize(0)); @@ -110,7 +273,7 @@ public class MdmProviderQueryLinkR4Test extends BaseLinkR4Test { int count = 2; StopWatch sw = new StopWatch(); while (true) { - Parameters result = (Parameters) myMdmProvider.queryLinks(null, null, null, myLinkSource, new UnsignedIntType(offset), new UnsignedIntType(count), myRequestDetails, null); + Parameters result = (Parameters) myMdmProvider.queryLinks(null, null, null, myLinkSource, new UnsignedIntType(offset), new UnsignedIntType(count), new StringType(), myRequestDetails, null); List parameter = result.getParameter(); @@ -156,7 +319,8 @@ public class MdmProviderQueryLinkR4Test extends BaseLinkR4Test { null, myLinkSource, new UnsignedIntType(offset), new UnsignedIntType(count), - myRequestDetails, + new StringType(), + myRequestDetails, null); } catch (InvalidRequestException e) { //Then @@ -173,6 +337,7 @@ public class MdmProviderQueryLinkR4Test extends BaseLinkR4Test { null, myLinkSource, new UnsignedIntType(offset), new UnsignedIntType(count), + new StringType(), myRequestDetails, null); } catch (InvalidRequestException e) { @@ -190,6 +355,7 @@ public class MdmProviderQueryLinkR4Test extends BaseLinkR4Test { null, myLinkSource, new UnsignedIntType(offset), new UnsignedIntType(count), + new StringType(), myRequestDetails, null); } catch (InvalidRequestException e) { @@ -206,12 +372,12 @@ public class MdmProviderQueryLinkR4Test extends BaseLinkR4Test { IAnyResource goldenResource = getGoldenResourceFromTargetResource(patient); IIdType goldenResourceId = goldenResource.getIdElement().toVersionless(); - Parameters result = (Parameters) myMdmProvider.queryLinks(null, null, null, myLinkSource, new UnsignedIntType(0), new UnsignedIntType(10), myRequestDetails, null); + Parameters result = (Parameters) myMdmProvider.queryLinks(null, null, null, myLinkSource, new UnsignedIntType(0), new UnsignedIntType(10), new StringType(), myRequestDetails, null); ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(result)); List list = getParametersByName(result, "link"); assertThat(list, hasSize(4)); List part = list.get(3).getPart(); - assertMdmLink(7, part, goldenResourceId.getValue(), patientId.getValue(), MdmMatchResultEnum.MATCH, "false", "false", "2"); + assertMdmLink(MDM_LINK_PROPERTY_COUNT, part, goldenResourceId.getValue(), patientId.getValue(), MdmMatchResultEnum.MATCH, "false", "false", "2"); } @Test diff --git a/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/svc/MdmControllerSvcImplTest.java b/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/svc/MdmControllerSvcImplTest.java index 2c0f722b11f..52f5a216fe6 100644 --- a/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/svc/MdmControllerSvcImplTest.java +++ b/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/svc/MdmControllerSvcImplTest.java @@ -106,8 +106,9 @@ public class MdmControllerSvcImplTest extends BaseLinkR4Test { assertEquals(MdmLinkSourceEnum.AUTO, link.getLinkSource()); assertLinkCount(2); - MdmQuerySearchParameters params = new MdmQuerySearchParameters(null, myPatientId.getIdElement().getValue(), null, null, - new MdmPageRequest((Integer) null, null, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE), null, null); + MdmPageRequest pageRequest = new MdmPageRequest((Integer) null, null, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE); + MdmQuerySearchParameters params = new MdmQuerySearchParameters(pageRequest) + .setSourceId(myPatientId.getIdElement().getValue()); Page resultPage = myMdmControllerSvc.queryLinks(params, new MdmTransactionContext(MdmTransactionContext.OperationType.QUERY_LINKS), diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/MdmQuerySearchParameters.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/MdmQuerySearchParameters.java index 0cf1601d588..ec747c84979 100644 --- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/MdmQuerySearchParameters.java +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/MdmQuerySearchParameters.java @@ -20,16 +20,41 @@ package ca.uhn.fhir.mdm.api; * #L% */ +import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.mdm.api.paging.MdmPageRequest; import ca.uhn.fhir.mdm.provider.MdmControllerUtil; +import ca.uhn.fhir.rest.api.SortOrderEnum; +import ca.uhn.fhir.rest.api.SortSpec; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.provider.ProviderConstants; import org.apache.commons.lang3.builder.ToStringBuilder; import org.hl7.fhir.instance.model.api.IIdType; import javax.annotation.Nullable; +import java.util.ArrayList; import java.util.List; +import java.util.Set; + +import static org.hibernate.internal.util.StringHelper.isBlank; public class MdmQuerySearchParameters { + + public static final String GOLDEN_RESOURCE_PID_NAME = "myGoldenResourcePid"; + public static final String SOURCE_PID_NAME = "mySourcePid"; + public static final String MATCH_RESULT_NAME = "myMatchResult"; + public static final String LINK_SOURCE_NAME = "myLinkSource"; + public static final String PARTITION_ID_NAME = "myPartitionId"; + public static final String GOLDEN_RESOURCE_NAME = "myGoldenResource"; + public static final String RESOURCE_TYPE_NAME = "myResourceType"; + public static final String CREATED_NAME = "myCreated"; + public static final String UPDATED_NAME = "myUpdated"; + public static final String SCORE_NAME = "myScore"; + + public static final Set ourValidSortParameters = Set.of( + GOLDEN_RESOURCE_PID_NAME, SOURCE_PID_NAME, MATCH_RESULT_NAME, LINK_SOURCE_NAME, PARTITION_ID_NAME, GOLDEN_RESOURCE_NAME, + RESOURCE_TYPE_NAME, CREATED_NAME, UPDATED_NAME, SCORE_NAME + ); + private IIdType myGoldenResourceId; private IIdType mySourceId; private MdmMatchResultEnum myMatchResult; @@ -37,9 +62,16 @@ public class MdmQuerySearchParameters { private MdmPageRequest myPageRequest; private List myPartitionIds; private String myResourceType; + private final List mySort = new ArrayList<>(); + @Deprecated(since = "2023.02.R01", forRemoval = true) public MdmQuerySearchParameters() {} + public MdmQuerySearchParameters(MdmPageRequest thePageRequest) { + setPageRequest(thePageRequest); + } + + @Deprecated(since = "2023.02.R01", forRemoval = true) public MdmQuerySearchParameters(@Nullable String theGoldenResourceId, @Nullable String theSourceId, @Nullable String theMatchResult, @Nullable String theLinkSource, MdmPageRequest thePageRequest, @Nullable List thePartitionIds, @Nullable String theResourceType) { setGoldenResourceId(theGoldenResourceId); setSourceId(theSourceId); @@ -50,6 +82,7 @@ public class MdmQuerySearchParameters { setResourceType(theResourceType); } + @Deprecated(since = "2023.02.R01", forRemoval = true) public MdmQuerySearchParameters(@Nullable IIdType theGoldenResourceId, @Nullable IIdType theSourceId, @Nullable MdmMatchResultEnum theMatchResult, @Nullable MdmLinkSourceEnum theLinkSource, MdmPageRequest thePageRequest, @Nullable List thePartitionIds, @Nullable String theResourceType) { setGoldenResourceId(theGoldenResourceId); setSourceId(theSourceId); @@ -64,52 +97,56 @@ public class MdmQuerySearchParameters { return myGoldenResourceId; } - public void setGoldenResourceId(IIdType theGoldenResourceId) { + public MdmQuerySearchParameters setGoldenResourceId(IIdType theGoldenResourceId) { myGoldenResourceId = theGoldenResourceId; + return this; } - public void setGoldenResourceId(String theGoldenResourceId) { - IIdType goldenResourceId = MdmControllerUtil.extractGoldenResourceIdDtOrNull(ProviderConstants.MDM_QUERY_LINKS_GOLDEN_RESOURCE_ID, theGoldenResourceId); - myGoldenResourceId = goldenResourceId; + public MdmQuerySearchParameters setGoldenResourceId(String theGoldenResourceId) { + myGoldenResourceId = MdmControllerUtil.extractGoldenResourceIdDtOrNull(ProviderConstants.MDM_QUERY_LINKS_GOLDEN_RESOURCE_ID, theGoldenResourceId); + return this; } public IIdType getSourceId() { return mySourceId; } - public void setSourceId(IIdType theSourceId) { + public MdmQuerySearchParameters setSourceId(IIdType theSourceId) { mySourceId = theSourceId; + return this; } - public void setSourceId(String theSourceId) { - IIdType sourceId = MdmControllerUtil.extractSourceIdDtOrNull(ProviderConstants.MDM_QUERY_LINKS_RESOURCE_ID, theSourceId); - mySourceId = sourceId; + public MdmQuerySearchParameters setSourceId(String theSourceId) { + mySourceId = MdmControllerUtil.extractSourceIdDtOrNull(ProviderConstants.MDM_QUERY_LINKS_RESOURCE_ID, theSourceId); + return this; } public MdmMatchResultEnum getMatchResult() { return myMatchResult; } - public void setMatchResult(MdmMatchResultEnum theMatchResult) { + public MdmQuerySearchParameters setMatchResult(MdmMatchResultEnum theMatchResult) { myMatchResult = theMatchResult; + return this; } - public void setMatchResult(String theMatchResult) { - MdmMatchResultEnum matchResult = MdmControllerUtil.extractMatchResultOrNull(theMatchResult); - myMatchResult = matchResult; + public MdmQuerySearchParameters setMatchResult(String theMatchResult) { + myMatchResult = MdmControllerUtil.extractMatchResultOrNull(theMatchResult); + return this; } public MdmLinkSourceEnum getLinkSource() { return myLinkSource; } - public void setLinkSource(MdmLinkSourceEnum theLinkSource) { + public MdmQuerySearchParameters setLinkSource(MdmLinkSourceEnum theLinkSource) { myLinkSource = theLinkSource; + return this; } - public void setLinkSource(String theLinkSource) { - MdmLinkSourceEnum linkSource = MdmControllerUtil.extractLinkSourceOrNull(theLinkSource); - myLinkSource = linkSource; + public MdmQuerySearchParameters setLinkSource(String theLinkSource) { + myLinkSource = MdmControllerUtil.extractLinkSourceOrNull(theLinkSource); + return this; } public MdmPageRequest getPageRequest() { @@ -124,28 +161,51 @@ public class MdmQuerySearchParameters { return myPartitionIds; } - public void setPartitionIds(List thePartitionIds) { + public MdmQuerySearchParameters setPartitionIds(List thePartitionIds) { myPartitionIds = thePartitionIds; + return this; } public String getResourceType() { return myResourceType; } - public void setResourceType(String theResourceType) { + public MdmQuerySearchParameters setResourceType(String theResourceType) { myResourceType = theResourceType; + return this; + } + + public List getSort() { + return mySort; + } + + public MdmQuerySearchParameters setSort(String theSortString) { + if (isBlank(theSortString)) { + return this; + } + + for (String param : theSortString.split(",")) { + String p = (param.startsWith("-") ? param.substring(1) : param).trim(); + if ( ! MdmQuerySearchParameters.ourValidSortParameters.contains(p)) { + throw new InvalidRequestException(Msg.code(2233) + "Unrecognized sort parameter: " + p + ". Valid parameters are: " + + String.join(", ", MdmQuerySearchParameters.ourValidSortParameters)); + } + SortOrderEnum order = param.startsWith("-") ? SortOrderEnum.DESC : SortOrderEnum.ASC; + mySort.add( new SortSpec(p, order) ); + } + return this; } @Override public String toString() { return new ToStringBuilder(this) - .append("myGoldenResourceId", myGoldenResourceId) - .append("mySourceId", mySourceId) - .append("myMatchResult", myMatchResult) - .append("myLinkSource", myLinkSource) - .append("myPartitionId", myPartitionIds) + .append(GOLDEN_RESOURCE_PID_NAME, myGoldenResourceId) + .append(SOURCE_PID_NAME, mySourceId) + .append(MATCH_RESULT_NAME, myMatchResult) + .append(LINK_SOURCE_NAME, myLinkSource) + .append(PARTITION_ID_NAME, myPartitionIds) + .append(RESOURCE_TYPE_NAME, myResourceType) .append("myPageRequest", myPageRequest) - .append("myResourceType", myResourceType) .toString(); } diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/provider/BaseMdmProvider.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/provider/BaseMdmProvider.java index 51c2eb6b44c..926ebbd5773 100644 --- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/provider/BaseMdmProvider.java +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/provider/BaseMdmProvider.java @@ -20,8 +20,8 @@ package ca.uhn.fhir.mdm.provider; * #L% */ -import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.mdm.api.MdmLinkJson; import ca.uhn.fhir.mdm.api.MdmMatchResultEnum; import ca.uhn.fhir.mdm.api.paging.MdmPageLinkBuilder; @@ -115,7 +115,8 @@ public abstract class BaseMdmProvider { return theString.getValue(); } - protected IBaseParameters parametersFromMdmLinks(Page theMdmLinkStream, boolean includeResultAndSource, ServletRequestDetails theServletRequestDetails, MdmPageRequest thePageRequest) { + protected IBaseParameters parametersFromMdmLinks(Page theMdmLinkStream, boolean includeResultAndSource, + ServletRequestDetails theServletRequestDetails, MdmPageRequest thePageRequest) { IBaseParameters retval = ParametersUtil.newInstance(myFhirContext); addPagingParameters(retval, theMdmLinkStream, theServletRequestDetails, thePageRequest); theMdmLinkStream.getContent().forEach(mdmLink -> { @@ -129,6 +130,8 @@ public abstract class BaseMdmProvider { ParametersUtil.addPartBoolean(myFhirContext, resultPart, "eidMatch", mdmLink.getEidMatch()); ParametersUtil.addPartBoolean(myFhirContext, resultPart, "hadToCreateNewResource", mdmLink.getLinkCreatedNewResource()); ParametersUtil.addPartDecimal(myFhirContext, resultPart, "score", mdmLink.getScore()); + ParametersUtil.addPartDecimal(myFhirContext, resultPart, "linkCreated", (double) mdmLink.getCreated().getTime()); + ParametersUtil.addPartDecimal(myFhirContext, resultPart, "linkUpdated", (double) mdmLink.getUpdated().getTime()); } }); return retval; diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/provider/MdmProviderDstu3Plus.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/provider/MdmProviderDstu3Plus.java index 44fa42db52f..eb4a5e70a1d 100644 --- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/provider/MdmProviderDstu3Plus.java +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/provider/MdmProviderDstu3Plus.java @@ -182,25 +182,30 @@ public class MdmProviderDstu3Plus extends BaseMdmProvider { @OperationParam(name = ProviderConstants.MDM_QUERY_LINKS_LINK_SOURCE, min = 0, max = 1, typeName = "string") IPrimitiveType theLinkSource, - @Description(formalDefinition = "Results from this method are returned across multiple pages. This parameter controls the offset when fetching a page.") + @Description(value = "Results from this method are returned across multiple pages. This parameter controls the offset when fetching a page.") @OperationParam(name = PARAM_OFFSET, min = 0, max = 1, typeName = "integer") IPrimitiveType theOffset, - @Description(formalDefinition = "Results from this method are returned across multiple pages. This parameter controls the size of those pages.") + + @Description(value = "Results from this method are returned across multiple pages. This parameter controls the size of those pages.") @OperationParam(name = Constants.PARAM_COUNT, min = 0, max = 1, typeName = "integer") IPrimitiveType theCount, + + @OperationParam(name = Constants.PARAM_SORT, min = 0, max = 1, typeName = "string") + IPrimitiveType theSort, + ServletRequestDetails theRequestDetails, @OperationParam(name = ProviderConstants.MDM_RESOURCE_TYPE, min = 0, max = 1, typeName = "string") IPrimitiveType theResourceType ) { MdmPageRequest mdmPageRequest = new MdmPageRequest(theOffset, theCount, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE); MdmTransactionContext mdmContext = createMdmContext(theRequestDetails, MdmTransactionContext.OperationType.QUERY_LINKS, getResourceType(ProviderConstants.MDM_QUERY_LINKS_GOLDEN_RESOURCE_ID, theGoldenResourceId, theResourceType)); - MdmQuerySearchParameters mdmQuerySearchParameters = new MdmQuerySearchParameters(); - mdmQuerySearchParameters.setGoldenResourceId(extractStringOrNull(theGoldenResourceId)); - mdmQuerySearchParameters.setSourceId(extractStringOrNull(theResourceId)); - mdmQuerySearchParameters.setLinkSource(extractStringOrNull(theLinkSource)); - mdmQuerySearchParameters.setMatchResult(extractStringOrNull(theMatchResult)); - mdmQuerySearchParameters.setPageRequest(mdmPageRequest); - mdmQuerySearchParameters.setResourceType(extractStringOrNull(theResourceType)); + MdmQuerySearchParameters mdmQuerySearchParameters = new MdmQuerySearchParameters(mdmPageRequest) + .setGoldenResourceId(extractStringOrNull(theGoldenResourceId)) + .setSourceId(extractStringOrNull(theResourceId)) + .setLinkSource(extractStringOrNull(theLinkSource)) + .setMatchResult(extractStringOrNull(theMatchResult)) + .setResourceType(extractStringOrNull(theResourceType)) + .setSort(extractStringOrNull(theSort)); Page mdmLinkJson = myMdmControllerSvc.queryLinks(mdmQuerySearchParameters, mdmContext, theRequestDetails); return parametersFromMdmLinks(mdmLinkJson, true, theRequestDetails, mdmPageRequest); diff --git a/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/api/MdmQuerySearchParametersTest.java b/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/api/MdmQuerySearchParametersTest.java index 6260c262927..cd9083a96f5 100644 --- a/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/api/MdmQuerySearchParametersTest.java +++ b/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/api/MdmQuerySearchParametersTest.java @@ -20,13 +20,12 @@ class MdmQuerySearchParametersTest { @Test public void testMdmQuerySearchParameters() { - MdmQuerySearchParameters params = new MdmQuerySearchParameters(); + MdmQuerySearchParameters params = new MdmQuerySearchParameters(PAGE_REQUEST); params.setGoldenResourceId(GOLDEN_RESOURCE_ID); params.setSourceId(SOURCE_ID); params.setMatchResult(MATCH_RESULT); params.setLinkSource(LINK_SOURCE); params.setPartitionIds(PARTITION_ID); - params.setPageRequest(PAGE_REQUEST); params.setResourceType(RESOURCE_TYPE); assertEquals(GOLDEN_RESOURCE_ID, params.getGoldenResourceId().getValueAsString()); assertEquals(SOURCE_ID, params.getSourceId().getValueAsString());