From 61260dd1b11768cdfa42c3bae7c7dcc5dc4c48f3 Mon Sep 17 00:00:00 2001 From: Tadgh Date: Wed, 30 Jun 2021 11:54:59 -0400 Subject: [PATCH] Slightly more portable solution for paging --- .../uhn/fhir/jpa/mdm/dao/MdmLinkDaoSvc.java | 8 +-- .../jpa/mdm/svc/MdmControllerSvcImpl.java | 11 +-- .../fhir/jpa/mdm/svc/MdmLinkQuerySvcImpl.java | 16 +++-- .../jpa/mdm/provider/BaseProviderR4Test.java | 5 +- .../provider/MdmProviderQueryLinkR4Test.java | 2 +- hapi-fhir-server-mdm/pom.xml | 5 ++ .../uhn/fhir/mdm/api/IMdmControllerSvc.java | 7 +- .../ca/uhn/fhir/mdm/api/IMdmLinkQuerySvc.java | 8 +-- .../mdm/api/paging/MdmPageLinkBuilder.java | 38 ++++++++++ .../fhir/mdm/api/paging/MdmPageLinkTuple.java | 36 ++++++++++ .../fhir/mdm/api/paging/MdmPageRequest.java | 69 +++++++++++++++++++ .../fhir/mdm/provider/BaseMdmProvider.java | 26 ++++++- .../mdm/provider/MdmProviderDstu3Plus.java | 58 ++++++++-------- .../fhir/mdm/provider/MdmProviderLoader.java | 5 +- .../fhir/rest/server/RestfulServerUtils.java | 25 ++++--- 15 files changed, 253 insertions(+), 66 deletions(-) create mode 100644 hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/paging/MdmPageLinkBuilder.java create mode 100644 hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/paging/MdmPageLinkTuple.java create mode 100644 hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/paging/MdmPageRequest.java diff --git a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/dao/MdmLinkDaoSvc.java b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/dao/MdmLinkDaoSvc.java index 0399bdcbc1b..45df193ddeb 100644 --- a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/dao/MdmLinkDaoSvc.java +++ b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/dao/MdmLinkDaoSvc.java @@ -27,6 +27,7 @@ import ca.uhn.fhir.jpa.entity.MdmLink; import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum; import ca.uhn.fhir.mdm.api.MdmMatchOutcome; import ca.uhn.fhir.mdm.api.MdmMatchResultEnum; +import ca.uhn.fhir.mdm.api.paging.MdmPageRequest; import ca.uhn.fhir.mdm.log.Logs; import ca.uhn.fhir.mdm.model.MdmTransactionContext; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -34,8 +35,6 @@ import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Example; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -289,9 +288,8 @@ public class MdmLinkDaoSvc { * @param theExampleLink The MDM link containing the data we would like to search for. * @return a list of {@link MdmLink} entities which match the example. */ - public Page findMdmLinkByExample(Example theExampleLink, int theOffset, int theCount) { - PageRequest of = PageRequest.of(theOffset / theCount, theCount); - return myMdmLinkDao.findAll(theExampleLink, of); + public Page findMdmLinkByExample(Example theExampleLink, MdmPageRequest thePageRequest) { + return myMdmLinkDao.findAll(theExampleLink, thePageRequest.toPageRequest()); } /** 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 97b6015320d..9c7a149b261 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 @@ -27,6 +27,7 @@ import ca.uhn.fhir.mdm.api.IMdmLinkUpdaterSvc; import ca.uhn.fhir.mdm.api.MdmLinkJson; import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum; import ca.uhn.fhir.mdm.api.MdmMatchResultEnum; +import ca.uhn.fhir.mdm.api.paging.MdmPageRequest; import ca.uhn.fhir.mdm.model.MdmTransactionContext; import ca.uhn.fhir.mdm.provider.MdmControllerHelper; import ca.uhn.fhir.mdm.provider.MdmControllerUtil; @@ -34,10 +35,10 @@ import ca.uhn.fhir.rest.server.provider.ProviderConstants; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IIdType; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; import javax.annotation.Nullable; -import java.util.stream.Stream; /** * This class acts as a layer between MdmProviders and MDM services to support a REST API that's not a FHIR Operation API. @@ -66,17 +67,17 @@ public class MdmControllerSvcImpl implements IMdmControllerSvc { } @Override - public Stream queryLinks(@Nullable String theGoldenResourceId, @Nullable String theSourceResourceId, @Nullable String theMatchResult, @Nullable String theLinkSource, MdmTransactionContext theMdmTransactionContext, int theOffset, int theCount) { + public Page queryLinks(@Nullable String theGoldenResourceId, @Nullable String theSourceResourceId, @Nullable String theMatchResult, @Nullable String theLinkSource, MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest) { IIdType goldenResourceId = MdmControllerUtil.extractGoldenResourceIdDtOrNull(ProviderConstants.MDM_QUERY_LINKS_GOLDEN_RESOURCE_ID, theGoldenResourceId); IIdType sourceId = MdmControllerUtil.extractSourceIdDtOrNull(ProviderConstants.MDM_QUERY_LINKS_RESOURCE_ID, theSourceResourceId); MdmMatchResultEnum matchResult = MdmControllerUtil.extractMatchResultOrNull(theMatchResult); MdmLinkSourceEnum linkSource = MdmControllerUtil.extractLinkSourceOrNull(theLinkSource); - return myMdmLinkQuerySvc.queryLinks(goldenResourceId, sourceId, matchResult, linkSource, theMdmTransactionContext, theOffset, theCount); + return myMdmLinkQuerySvc.queryLinks(goldenResourceId, sourceId, matchResult, linkSource, theMdmTransactionContext, thePageRequest); } @Override - public Stream getDuplicateGoldenResources(MdmTransactionContext theMdmTransactionContext, int theOffset, int theCount) { - return myMdmLinkQuerySvc.getDuplicateGoldenResources(theMdmTransactionContext, theOffset, theCount); + public Page getDuplicateGoldenResources(MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest) { + return myMdmLinkQuerySvc.getDuplicateGoldenResources(theMdmTransactionContext, thePageRequest); } @Override diff --git a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmLinkQuerySvcImpl.java b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmLinkQuerySvcImpl.java index 7fd03fb26fd..ea00e81ec4d 100644 --- a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmLinkQuerySvcImpl.java +++ b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmLinkQuerySvcImpl.java @@ -24,6 +24,7 @@ import ca.uhn.fhir.mdm.api.MdmLinkJson; import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum; import ca.uhn.fhir.mdm.api.MdmMatchResultEnum; import ca.uhn.fhir.mdm.api.IMdmLinkQuerySvc; +import ca.uhn.fhir.mdm.api.paging.MdmPageRequest; import ca.uhn.fhir.mdm.model.MdmTransactionContext; import ca.uhn.fhir.jpa.dao.index.IdHelperService; import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc; @@ -35,8 +36,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Example; import org.springframework.data.domain.Page; -import java.util.stream.Stream; - public class MdmLinkQuerySvcImpl implements IMdmLinkQuerySvc { private static final Logger ourLog = LoggerFactory.getLogger(MdmLinkQuerySvcImpl.class); @@ -47,16 +46,19 @@ public class MdmLinkQuerySvcImpl implements IMdmLinkQuerySvc { MdmLinkDaoSvc myMdmLinkDaoSvc; @Override - public Stream queryLinks(IIdType theGoldenResourceId, IIdType theSourceResourceId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmContext, int theOffset, int theCount) { + public Page queryLinks(IIdType theGoldenResourceId, IIdType theSourceResourceId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmContext, MdmPageRequest thePageRequest) { Example exampleLink = exampleLinkFromParameters(theGoldenResourceId, theSourceResourceId, theMatchResult, theLinkSource); - Page mdmLinkByExample = myMdmLinkDaoSvc.findMdmLinkByExample(exampleLink, theOffset, theCount); - return mdmLinkByExample.stream().map(this::toJson); + Page mdmLinkByExample = myMdmLinkDaoSvc.findMdmLinkByExample(exampleLink, thePageRequest); + Page map = mdmLinkByExample.map(this::toJson); + return map; } @Override - public Stream getDuplicateGoldenResources(MdmTransactionContext theMdmContext, int theOffset, int theCount) { + public Page getDuplicateGoldenResources(MdmTransactionContext theMdmContext, MdmPageRequest thePageRequest) { Example exampleLink = exampleLinkFromParameters(null, null, MdmMatchResultEnum.POSSIBLE_DUPLICATE, null); - return myMdmLinkDaoSvc.findMdmLinkByExample(exampleLink, theOffset, theCount).stream().map(this::toJson); + Page mdmLinkPage = myMdmLinkDaoSvc.findMdmLinkByExample(exampleLink, thePageRequest); + Page map = mdmLinkPage.map(this::toJson); + return map; } private MdmLinkJson toJson(MdmLink theLink) { diff --git a/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/provider/BaseProviderR4Test.java b/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/provider/BaseProviderR4Test.java index fd2647bb304..c3794c183ab 100644 --- a/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/provider/BaseProviderR4Test.java +++ b/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/provider/BaseProviderR4Test.java @@ -7,6 +7,7 @@ import ca.uhn.fhir.mdm.api.IMdmMatchFinderSvc; import ca.uhn.fhir.mdm.api.IMdmSubmitSvc; import ca.uhn.fhir.mdm.provider.MdmProviderDstu3Plus; import ca.uhn.fhir.mdm.rules.config.MdmSettings; +import ca.uhn.fhir.rest.server.IRestfulServerDefaults; import com.google.common.base.Charsets; import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.AfterEach; @@ -29,6 +30,8 @@ public abstract class BaseProviderR4Test extends BaseMdmR4Test { private IMdmSubmitSvc myMdmSubmitSvc; @Autowired private MdmSettings myMdmSettings; + @Autowired + private IRestfulServerDefaults myRestfulServerDefaults; private String defaultScript; @@ -42,7 +45,7 @@ public abstract class BaseProviderR4Test extends BaseMdmR4Test { @BeforeEach public void before() { - myMdmProvider = new MdmProviderDstu3Plus(myFhirContext, myMdmControllerSvc, myMdmMatchFinderSvc, myMdmExpungeSvc, myMdmSubmitSvc); + myMdmProvider = new MdmProviderDstu3Plus(myFhirContext, myMdmControllerSvc, myMdmMatchFinderSvc, myMdmExpungeSvc, myMdmSubmitSvc, myRestfulServerDefaults); defaultScript = myMdmSettings.getScriptText(); } @AfterEach 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 418d511538a..35308e62402 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 @@ -90,7 +90,7 @@ public class MdmProviderQueryLinkR4Test extends BaseLinkR4Test { ourLog.warn("Search at offset {} took {}ms",offset, sw.getMillisAndRestart()); ourLog.warn("Found source resource IDs: {}", sourceResourceIds); offset += count; - assertThat(parameter.size(), is(lessThanOrEqualTo(2))); + assertThat(parameter.size(), is(lessThanOrEqualTo(3))); //We have stopped finding patients. if (StringUtils.isEmpty(sourceResourceIds)) { diff --git a/hapi-fhir-server-mdm/pom.xml b/hapi-fhir-server-mdm/pom.xml index bf1e50dd9e9..3e9be79e7f4 100644 --- a/hapi-fhir-server-mdm/pom.xml +++ b/hapi-fhir-server-mdm/pom.xml @@ -27,6 +27,11 @@ hapi-fhir-server ${project.version} + + org.springframework.data + spring-data-commons + ${spring_data_version} + ca.uhn.hapi.fhir hapi-fhir-structures-dstu3 diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/IMdmControllerSvc.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/IMdmControllerSvc.java index 5ff3c62570f..67a3dc64a53 100644 --- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/IMdmControllerSvc.java +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/IMdmControllerSvc.java @@ -20,17 +20,18 @@ package ca.uhn.fhir.mdm.api; * #L% */ +import ca.uhn.fhir.mdm.api.paging.MdmPageRequest; import ca.uhn.fhir.mdm.model.MdmTransactionContext; import org.hl7.fhir.instance.model.api.IAnyResource; +import org.springframework.data.domain.Page; import javax.annotation.Nullable; -import java.util.stream.Stream; public interface IMdmControllerSvc { - Stream queryLinks(@Nullable String theGoldenResourceId, @Nullable String theSourceResourceId, @Nullable String theMatchResult, @Nullable String theLinkSource, MdmTransactionContext theMdmTransactionContext, int theOffset, int theCount); + Page queryLinks(@Nullable String theGoldenResourceId, @Nullable String theSourceResourceId, @Nullable String theMatchResult, @Nullable String theLinkSource, MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest); - Stream getDuplicateGoldenResources(MdmTransactionContext theMdmTransactionContext, int theOffset, int theCount); + Page getDuplicateGoldenResources(MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest); void notDuplicateGoldenResource(String theGoldenResourceId, String theTargetGoldenResourceId, MdmTransactionContext theMdmTransactionContext); diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/IMdmLinkQuerySvc.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/IMdmLinkQuerySvc.java index 28aefbaf550..a1ffc27e495 100644 --- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/IMdmLinkQuerySvc.java +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/IMdmLinkQuerySvc.java @@ -20,15 +20,15 @@ package ca.uhn.fhir.mdm.api; * #L% */ +import ca.uhn.fhir.mdm.api.paging.MdmPageRequest; import ca.uhn.fhir.mdm.model.MdmTransactionContext; import org.hl7.fhir.instance.model.api.IIdType; - -import java.util.stream.Stream; +import org.springframework.data.domain.Page; /** * This service supports the MDM operation providers for those services that return multiple MDM links. */ public interface IMdmLinkQuerySvc { - Stream queryLinks(IIdType theGoldenResourceId, IIdType theSourceResourceId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmContext, int theOffset, int theCount); - Stream getDuplicateGoldenResources(MdmTransactionContext theMdmContext, int theOffset, int theCount); + Page queryLinks(IIdType theGoldenResourceId, IIdType theSourceResourceId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmContext, MdmPageRequest thePageRequest); + Page getDuplicateGoldenResources(MdmTransactionContext theMdmContext, MdmPageRequest thePageRequest); } diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/paging/MdmPageLinkBuilder.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/paging/MdmPageLinkBuilder.java new file mode 100644 index 00000000000..7940cd1eb02 --- /dev/null +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/paging/MdmPageLinkBuilder.java @@ -0,0 +1,38 @@ +package ca.uhn.fhir.mdm.api.paging; + +import ca.uhn.fhir.mdm.api.MdmLinkJson; +import ca.uhn.fhir.rest.server.RestfulServerUtils; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import ca.uhn.fhir.util.ParametersUtil; +import org.springframework.data.domain.Page; + +import java.util.Arrays; + +import static ca.uhn.fhir.rest.api.Constants.PARAM_COUNT; +import static ca.uhn.fhir.rest.api.Constants.PARAM_OFFSET; + +public final class MdmPageLinkBuilder { + public static MdmPageLinkTuple buildMdmPageLinks(ServletRequestDetails theServletRequestDetails, Page theCurrentPage, MdmPageRequest thePageRequest) { + MdmPageLinkTuple tuple = new MdmPageLinkTuple(); + String urlWithoutPaging = RestfulServerUtils.createLinkSelfWithoutGivenParameters(theServletRequestDetails.getFhirServerBase(), theServletRequestDetails, Arrays.asList(PARAM_OFFSET, PARAM_COUNT)); + tuple.setSelfLink(buildLinkWithOffsetAndCount(urlWithoutPaging, thePageRequest.getCount(), thePageRequest.getOffset())); + if (theCurrentPage.hasNext()) { + tuple.setNextLink(buildLinkWithOffsetAndCount(urlWithoutPaging,thePageRequest.getCount(), thePageRequest.getNextOffset())); + } + if (theCurrentPage.hasPrevious()) { + tuple.setPreviousLink(buildLinkWithOffsetAndCount(urlWithoutPaging,thePageRequest.getCount(), thePageRequest.getPreviousOffset())); + } + return tuple; + + } + protected static String buildLinkWithOffsetAndCount(String theStartingUrl, int theCount, int theOffset) { + StringBuilder builder = new StringBuilder(); + builder.append(theStartingUrl); + if (!theStartingUrl.contains("?")) { + builder.append("?"); + } + builder.append(PARAM_OFFSET).append("=").append(theOffset); + builder.append(PARAM_COUNT).append("=").append(theCount); + return builder.toString(); + } +} diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/paging/MdmPageLinkTuple.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/paging/MdmPageLinkTuple.java new file mode 100644 index 00000000000..f6600f46eec --- /dev/null +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/paging/MdmPageLinkTuple.java @@ -0,0 +1,36 @@ +package ca.uhn.fhir.mdm.api.paging; + +import java.util.Optional; + +public class MdmPageLinkTuple { + private String myPreviousLink = null; + private String mySelfLink = null; + private String myNextLink = null; + + MdmPageLinkTuple() { + } + + public Optional getPreviousLink() { + return Optional.ofNullable(myPreviousLink); + } + + public void setPreviousLink(String thePreviousLink) { + this.myPreviousLink = thePreviousLink; + } + + public String getSelfLink() { + return mySelfLink; + } + + public void setSelfLink(String theSelfLink) { + this.mySelfLink = theSelfLink; + } + + public Optional getNextLink() { + return Optional.ofNullable(myNextLink); + } + + public void setNextLink(String theNextLink) { + this.myNextLink = theNextLink; + } +} diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/paging/MdmPageRequest.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/paging/MdmPageRequest.java new file mode 100644 index 00000000000..a971641c22d --- /dev/null +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/paging/MdmPageRequest.java @@ -0,0 +1,69 @@ +package ca.uhn.fhir.mdm.api.paging; + +import ca.uhn.fhir.rest.server.IRestfulServerDefaults; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import org.apache.commons.lang3.StringUtils; +import org.hl7.fhir.dstu3.model.UnsignedIntType; +import org.slf4j.Logger; +import org.springframework.data.domain.PageRequest; + +import static ca.uhn.fhir.rest.api.Constants.PARAM_COUNT; +import static ca.uhn.fhir.rest.api.Constants.PARAM_OFFSET; +import static org.slf4j.LoggerFactory.getLogger; + +public class MdmPageRequest { + private static final Logger ourLog = getLogger(MdmPageRequest.class); + + private int myPage; + private int myOffset; + private int myCount; + private IRestfulServerDefaults myRestfulServerDefaults; + + public MdmPageRequest(UnsignedIntType theOffset, UnsignedIntType theCount, IRestfulServerDefaults theDefaults) { + myOffset = theOffset == null ? 0 : theOffset.getValue(); + myCount = theCount == null ? theDefaults.getDefaultPageSize() : theCount.getValue(); + validatePagingParameters(myOffset, myCount); + + this.myPage = myOffset / myCount; + } + + public PageRequest toPageRequest() { + return PageRequest.of(this.myPage, this.myCount); + } + + private void validatePagingParameters(int theOffset, int theCount) { + String errorMessage = ""; + + if (theOffset < 0) { + errorMessage += PARAM_OFFSET + " must be greater than or equal to 0. "; + } + if (theCount <= 0 ) { + errorMessage += PARAM_COUNT + " must be greater than 0."; + } + if (myRestfulServerDefaults.getMaximumPageSize() != null && theCount > myRestfulServerDefaults.getMaximumPageSize() ) { + ourLog.debug("Shrinking page size down to {}, as this is the maximum allowed.", myRestfulServerDefaults.getMaximumPageSize()); + } + if (StringUtils.isNotEmpty(errorMessage)) { + throw new InvalidRequestException(errorMessage); + } + } + + public int getOffset() { + return myOffset; + } + + public int getPage() { + return myPage; + } + + public int getCount() { + return myCount; + } + + public int getNextOffset() { + return myOffset + myCount; + } + public int getPreviousOffset() { + return myOffset - myCount; + } +} 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 e9bdb713959..466011fc4f7 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 @@ -23,17 +23,26 @@ package ca.uhn.fhir.mdm.provider; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.mdm.api.MdmLinkJson; import ca.uhn.fhir.mdm.api.MdmMatchResultEnum; +import ca.uhn.fhir.mdm.api.paging.MdmPageLinkBuilder; +import ca.uhn.fhir.mdm.api.paging.MdmPageLinkTuple; +import ca.uhn.fhir.mdm.api.paging.MdmPageRequest; import ca.uhn.fhir.mdm.model.MdmTransactionContext; import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.RestfulServerUtils; import ca.uhn.fhir.rest.server.TransactionLogMessages; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.provider.ProviderConstants; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.ParametersUtil; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.springframework.data.domain.Page; -import java.util.stream.Stream; +import java.util.Arrays; + +import static ca.uhn.fhir.rest.api.Constants.PARAM_COUNT; +import static ca.uhn.fhir.rest.api.Constants.PARAM_OFFSET; public abstract class BaseMdmProvider { @@ -92,9 +101,9 @@ public abstract class BaseMdmProvider { return theString.getValue(); } - protected IBaseParameters parametersFromMdmLinks(Stream theMdmLinkStream, boolean includeResultAndSource) { + protected IBaseParameters parametersFromMdmLinks(Page theMdmLinkStream, boolean includeResultAndSource, ServletRequestDetails theServletRequestDetails, MdmPageRequest thePageRequest) { IBaseParameters retval = ParametersUtil.newInstance(myFhirContext); - + addPagingParameters(retval, theMdmLinkStream, theServletRequestDetails, thePageRequest); theMdmLinkStream.forEach(mdmLink -> { IBase resultPart = ParametersUtil.addParameterToParameters(myFhirContext, retval, "link"); ParametersUtil.addPartString(myFhirContext, resultPart, "goldenResourceId", mdmLink.getGoldenResourceId()); @@ -111,4 +120,15 @@ public abstract class BaseMdmProvider { return retval; } + protected void addPagingParameters(IBaseParameters theParameters, Page theCurrentPage, ServletRequestDetails theServletRequestDetails, MdmPageRequest thePageRequest) { + MdmPageLinkTuple mdmPageLinkTuple = MdmPageLinkBuilder.buildMdmPageLinks(theServletRequestDetails, theCurrentPage, thePageRequest); + ParametersUtil.addParameterToParametersUri(myFhirContext, theParameters, "self", mdmPageLinkTuple.getSelfLink()); + + if (mdmPageLinkTuple.getNextLink().isPresent()) { + ParametersUtil.addParameterToParametersUri(myFhirContext, theParameters, "next", mdmPageLinkTuple.getNextLink().get()); + } + if (mdmPageLinkTuple.getPreviousLink().isPresent()) { + ParametersUtil.addParameterToParametersUri(myFhirContext, theParameters, "prev", mdmPageLinkTuple.getPreviousLink().get()); + } + } } 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 df6fe73ffc5..4b06805c8f8 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 @@ -28,6 +28,7 @@ import ca.uhn.fhir.mdm.api.IMdmSubmitSvc; import ca.uhn.fhir.mdm.api.MatchedTarget; import ca.uhn.fhir.mdm.api.MdmConstants; import ca.uhn.fhir.mdm.api.MdmLinkJson; +import ca.uhn.fhir.mdm.api.paging.MdmPageRequest; import ca.uhn.fhir.mdm.model.MdmTransactionContext; import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.rest.annotation.IdParam; @@ -35,6 +36,7 @@ import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.IRestfulServerDefaults; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.provider.ProviderConstants; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; @@ -52,6 +54,8 @@ import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.slf4j.Logger; +import org.springframework.data.domain.Page; import javax.annotation.Nonnull; import java.math.BigDecimal; @@ -59,17 +63,19 @@ import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.UUID; -import java.util.stream.Stream; -import static ca.uhn.fhir.rest.api.Constants.PARAM_COUNT; import static ca.uhn.fhir.rest.api.Constants.PARAM_OFFSET; +import static org.slf4j.LoggerFactory.getLogger; public class MdmProviderDstu3Plus extends BaseMdmProvider { + private static final Logger ourLog = getLogger(MdmProviderDstu3Plus.class); + private final IMdmControllerSvc myMdmControllerSvc; private final IMdmMatchFinderSvc myMdmMatchFinderSvc; private final IMdmExpungeSvc myMdmExpungeSvc; private final IMdmSubmitSvc myMdmSubmitSvc; + private final IRestfulServerDefaults myRestfulServerDefaults; /** * Constructor @@ -77,12 +83,13 @@ public class MdmProviderDstu3Plus extends BaseMdmProvider { * Note that this is not a spring bean. Any necessary injections should * happen in the constructor */ - public MdmProviderDstu3Plus(FhirContext theFhirContext, IMdmControllerSvc theMdmControllerSvc, IMdmMatchFinderSvc theMdmMatchFinderSvc, IMdmExpungeSvc theMdmExpungeSvc, IMdmSubmitSvc theMdmSubmitSvc) { + public MdmProviderDstu3Plus(FhirContext theFhirContext, IMdmControllerSvc theMdmControllerSvc, IMdmMatchFinderSvc theMdmMatchFinderSvc, IMdmExpungeSvc theMdmExpungeSvc, IMdmSubmitSvc theMdmSubmitSvc, IRestfulServerDefaults theRestfulServerDefaults) { super(theFhirContext); myMdmControllerSvc = theMdmControllerSvc; myMdmMatchFinderSvc = theMdmMatchFinderSvc; myMdmExpungeSvc = theMdmExpungeSvc; myMdmSubmitSvc = theMdmSubmitSvc; + myRestfulServerDefaults = theRestfulServerDefaults; } @Operation(name = ProviderConstants.EMPI_MATCH, typeName = "Patient") @@ -199,40 +206,38 @@ public class MdmProviderDstu3Plus extends BaseMdmProvider { IPrimitiveType theLinkSource, @Description(formalDefinition="Results from this method are returned across multiple pages. This parameter controls the offset when fetching a page.") - @OperationParam(name = PARAM_OFFSET) + @OperationParam(name = PARAM_OFFSET, min = 0, max = 1) UnsignedIntType theOffset, @Description(formalDefinition = "Results from this method are returned across multiple pages. This parameter controls the size of those pages.") - @OperationParam(name = Constants.PARAM_COUNT) + @OperationParam(name = Constants.PARAM_COUNT, min = 0, max = 1) UnsignedIntType theCount, ServletRequestDetails theRequestDetails) { - validatePagingParameters(theOffset, theCount); - - Stream mdmLinkJson = myMdmControllerSvc.queryLinks(extractStringOrNull(theGoldenResourceId), + MdmPageRequest mdmPageRequest = new MdmPageRequest(theOffset, theCount, myRestfulServerDefaults); + Page mdmLinkJson = myMdmControllerSvc.queryLinks(extractStringOrNull(theGoldenResourceId), extractStringOrNull(theResourceId), extractStringOrNull(theMatchResult), extractStringOrNull(theLinkSource), createMdmContext(theRequestDetails, MdmTransactionContext.OperationType.QUERY_LINKS, - getResourceType(ProviderConstants.MDM_QUERY_LINKS_GOLDEN_RESOURCE_ID, theGoldenResourceId)), theOffset.getValue(), theCount.getValue() - ); - return parametersFromMdmLinks(mdmLinkJson, true); + getResourceType(ProviderConstants.MDM_QUERY_LINKS_GOLDEN_RESOURCE_ID, theGoldenResourceId)), mdmPageRequest); + + return parametersFromMdmLinks(mdmLinkJson, true, theRequestDetails, mdmPageRequest); } - private void validatePagingParameters(UnsignedIntType theOffset, UnsignedIntType theCount) { - String errorMessage = ""; - if (theOffset!= null && theOffset.getValue() < 0) { - errorMessage += PARAM_OFFSET + " must be greater than or equal to 0. "; - } - if (theCount != null && theCount.getValue() <= 0 ) { - errorMessage += PARAM_COUNT + " must be greater than 0."; - } - if (StringUtils.isNotEmpty(errorMessage)) { - throw new InvalidRequestException(errorMessage); - } - } @Operation(name = ProviderConstants.MDM_DUPLICATE_GOLDEN_RESOURCES, idempotent = true) - public IBaseParameters getDuplicateGoldenResources(ServletRequestDetails theRequestDetails) { - Stream possibleDuplicates = myMdmControllerSvc.getDuplicateGoldenResources(createMdmContext(theRequestDetails, MdmTransactionContext.OperationType.DUPLICATE_GOLDEN_RESOURCES, (String) null),0,10); - return parametersFromMdmLinks(possibleDuplicates, false); + public IBaseParameters getDuplicateGoldenResources( + @Description(formalDefinition="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) + UnsignedIntType theOffset, + @Description(formalDefinition = "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) + UnsignedIntType theCount, + ServletRequestDetails theRequestDetails) { + + MdmPageRequest mdmPageRequest = new MdmPageRequest(theOffset, theCount, myRestfulServerDefaults); + + Page possibleDuplicates = myMdmControllerSvc.getDuplicateGoldenResources(createMdmContext(theRequestDetails, MdmTransactionContext.OperationType.DUPLICATE_GOLDEN_RESOURCES, (String) null), mdmPageRequest); + + return parametersFromMdmLinks(possibleDuplicates, false, theRequestDetails, mdmPageRequest); } @Operation(name = ProviderConstants.MDM_NOT_DUPLICATE) @@ -260,7 +265,6 @@ public class MdmProviderDstu3Plus extends BaseMdmProvider { ServletRequestDetails theRequestDetails) { String criteria = convertStringTypeToString(theCriteria); String resourceType = convertStringTypeToString(theResourceType); - long submittedCount; if (resourceType != null) { submittedCount = myMdmSubmitSvc.submitSourceResourceTypeToMdm(resourceType, criteria); diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/provider/MdmProviderLoader.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/provider/MdmProviderLoader.java index a0defeda0a6..3ad3ee06e6a 100644 --- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/provider/MdmProviderLoader.java +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/provider/MdmProviderLoader.java @@ -26,6 +26,7 @@ import ca.uhn.fhir.mdm.api.IMdmControllerSvc; import ca.uhn.fhir.mdm.api.IMdmExpungeSvc; import ca.uhn.fhir.mdm.api.IMdmMatchFinderSvc; import ca.uhn.fhir.mdm.api.IMdmSubmitSvc; +import ca.uhn.fhir.rest.server.IRestfulServerDefaults; import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -46,6 +47,8 @@ public class MdmProviderLoader { private IMdmExpungeSvc myMdmExpungeSvc; @Autowired private IMdmSubmitSvc myMdmSubmitSvc; + @Autowired + private IRestfulServerDefaults myRestfulServerDefaults; private BaseMdmProvider myMdmProvider; @@ -54,7 +57,7 @@ public class MdmProviderLoader { case DSTU3: case R4: myResourceProviderFactory.addSupplier(() -> { - myMdmProvider = new MdmProviderDstu3Plus(myFhirContext, myMdmControllerSvc, myMdmMatchFinderSvc, myMdmExpungeSvc, myMdmSubmitSvc); + myMdmProvider = new MdmProviderDstu3Plus(myFhirContext, myMdmControllerSvc, myMdmMatchFinderSvc, myMdmExpungeSvc, myMdmSubmitSvc, myRestfulServerDefaults); return myMdmProvider; }); break; diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java index 07d4b8c41cc..e4ce660e2c7 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java @@ -249,6 +249,10 @@ public class RestfulServerUtils { public static String createLinkSelf(String theServerBase, RequestDetails theRequest) { + return createLinkSelfWithoutGivenParameters(theServerBase, theRequest, null); + } + + public static String createLinkSelfWithoutGivenParameters(String theServerBase, RequestDetails theRequest, List excludedParameterNames) { StringBuilder b = new StringBuilder(); b.append(theServerBase); @@ -265,21 +269,24 @@ public class RestfulServerUtils { boolean first = true; Map parameters = theRequest.getParameters(); for (String nextParamName : new TreeSet<>(parameters.keySet())) { - for (String nextParamValue : parameters.get(nextParamName)) { - if (first) { - b.append('?'); - first = false; - } else { - b.append('&'); + if (excludedParameterNames != null && !excludedParameterNames.contains(nextParamName)) { + for (String nextParamValue : parameters.get(nextParamName)) { + if (first) { + b.append('?'); + first = false; + } else { + b.append('&'); + } + b.append(UrlUtil.escapeUrlParam(nextParamName)); + b.append('='); + b.append(UrlUtil.escapeUrlParam(nextParamValue)); } - b.append(UrlUtil.escapeUrlParam(nextParamName)); - b.append('='); - b.append(UrlUtil.escapeUrlParam(nextParamValue)); } } } return b.toString(); + } public static String createOffsetPagingLink(BundleLinks theBundleLinks, String requestPath, String tenantId, Integer theOffset, Integer theCount, Map theRequestParameters) {