fixing tests

This commit is contained in:
leif stawnyczy 2023-08-17 15:57:31 -04:00
parent 8324f36ed8
commit 76587993e7
14 changed files with 210 additions and 155 deletions

View File

@ -64,7 +64,10 @@ public class PagingPatientProvider implements IResourceProvider {
@Nonnull
@Override
public List<IBaseResource> getResources(int theFromIndex, int theToIndex, @Nonnull ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
public List<IBaseResource> getResources(
int theFromIndex,
int theToIndex,
@Nonnull ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
int end = Math.max(theToIndex, matchingResourceIds.size() - 1);
List<Long> idsToReturn = matchingResourceIds.subList(theFromIndex, end);
return loadResourcesByIds(idsToReturn);

View File

@ -0,0 +1,7 @@
---
type: fix
issue: 5192
title: "Fixed a bug where searches with an _include filter would
break paging resulting in 'next' links to blank pages when
no more results are available.
"

View File

@ -65,15 +65,15 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Nonnull;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import javax.annotation.Nonnull;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
public class PersistedJpaBundleProvider implements IBundleProvider {
@ -173,9 +173,7 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
/**
* Perform a history search
*/
private List<IBaseResource> doHistoryInTransaction(
Integer theOffset, int theFromIndex, int theToIndex
) {
private List<IBaseResource> doHistoryInTransaction(Integer theOffset, int theFromIndex, int theToIndex) {
HistoryBuilder historyBuilder = myHistoryBuilderFactory.newHistoryBuilder(
mySearchEntity.getResourceType(),
@ -250,8 +248,9 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
}
protected List<IBaseResource> doSearchOrEverything(
final int theFromIndex, final int theToIndex, @Nonnull ResponsePage.ResponsePageBuilder theResponsePageBuilder
) {
final int theFromIndex,
final int theToIndex,
@Nonnull ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
if (mySearchEntity.getTotalCount() != null && mySearchEntity.getNumFound() <= 0) {
// No resources to fetch (e.g. we did a _summary=count search)
return Collections.emptyList();
@ -372,8 +371,7 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
@Override
public List<IBaseResource> getResources(
int theFromIndex, int theToIndex, @Nonnull ResponsePage.ResponsePageBuilder theResponsePageBuilder
) {
int theFromIndex, int theToIndex, @Nonnull ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
boolean entityLoaded = ensureSearchEntityLoaded();
assert entityLoaded;
assert mySearchEntity != null;
@ -382,9 +380,9 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
switch (mySearchEntity.getSearchType()) {
case HISTORY:
return myTxService
.withRequest(myRequest)
.withRequestPartitionId(getRequestPartitionId())
.execute(() -> doHistoryInTransaction(mySearchEntity.getOffset(), theFromIndex, theToIndex));
.withRequest(myRequest)
.withRequestPartitionId(getRequestPartitionId())
.execute(() -> doHistoryInTransaction(mySearchEntity.getOffset(), theFromIndex, theToIndex));
case SEARCH:
case EVERYTHING:
default:
@ -466,10 +464,9 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
// Note: Leave as protected, HSPC depends on this
@SuppressWarnings("WeakerAccess")
protected List<IBaseResource> toResourceList(
ISearchBuilder theSearchBuilder,
List<JpaPid> thePids,
ResponsePage.ResponsePageBuilder theResponsePageBuilder
) {
ISearchBuilder theSearchBuilder,
List<JpaPid> thePids,
ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
List<JpaPid> includedPidList = new ArrayList<>();
if (mySearchEntity.getSearchType() == SearchTypeEnum.SEARCH) {
Integer maxIncludes = myStorageSettings.getMaximumIncludesToLoadPerPage();
@ -546,7 +543,11 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
List<IBaseResource> resources = new ArrayList<>();
theSearchBuilder.loadResourcesByPid(thePids, includedPidList, resources, false, myRequest);
// we will send the resource list to our interceptors
// this can (potentially) change the results being returned.
int precount = resources.size();
resources = ServerInterceptorUtil.fireStoragePreshowResource(resources, myRequest, myInterceptorBroadcaster);
theResponsePageBuilder.setOmittedResourceCount(precount - resources.size());
theResponsePageBuilder.setResources(resources);
theResponsePageBuilder.setIncludedResourceCount(includedPidList.size());

View File

@ -35,14 +35,15 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
public class PersistedJpaSearchFirstPageBundleProvider extends PersistedJpaBundleProvider {
private static final Logger ourLog = LoggerFactory.getLogger(PersistedJpaSearchFirstPageBundleProvider.class);
private final SearchTask mySearchTask;
@SuppressWarnings("rawtypes")
private final ISearchBuilder mySearchBuilder;
@ -68,7 +69,8 @@ public class PersistedJpaSearchFirstPageBundleProvider extends PersistedJpaBundl
@Nonnull
@Override
public List<IBaseResource> getResources(int theFromIndex, int theToIndex, @Nonnull ResponsePage.ResponsePageBuilder thePageBuilder) {
public List<IBaseResource> getResources(
int theFromIndex, int theToIndex, @Nonnull ResponsePage.ResponsePageBuilder thePageBuilder) {
ensureSearchEntityLoaded();
QueryParameterUtils.verifySearchHasntFailedOrThrowInternalErrorException(getSearchEntity());

View File

@ -108,15 +108,6 @@ import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.SingleColumnRowMapper;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.Query;
import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -128,6 +119,15 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.Query;
import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import static ca.uhn.fhir.jpa.search.builder.QueryStack.LOCATION_POSITION;
import static org.apache.commons.lang3.StringUtils.defaultString;

View File

@ -595,7 +595,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
@Test
public void testSearchLinksWorkWithIncludes() {
for (int i = 0; i < 5; i++) {
Organization o = new Organization();
o.setId("O" + i);
o.setName("O" + i);
@ -605,7 +604,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
p.setId("P" + i);
p.getManagingOrganization().setReference(oid.getValue());
myClient.update().resource(p).execute();
}
Bundle output = myClient

View File

@ -166,7 +166,8 @@ public interface IBundleProvider {
* @param theResponsePageBuilder The ResponsePageBuilder. The builder will add values needed for the response page.
* @return A list of resources. The size of this list must be at least <code>theToIndex - theFromIndex</code>.
*/
default List<IBaseResource> getResources(int theFromIndex, int theToIndex, @Nonnull ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
default List<IBaseResource> getResources(
int theFromIndex, int theToIndex, @Nonnull ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
// TODO - override and implement
return getResources(theFromIndex, theToIndex);
}

View File

@ -89,7 +89,8 @@ public class BundleProviderWithNamedPages extends SimpleBundleProvider {
@SuppressWarnings("unchecked")
@Nonnull
@Override
public List<IBaseResource> getResources(int theFromIndex, int theToIndex, @Nonnull ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
public List<IBaseResource> getResources(
int theFromIndex, int theToIndex, @Nonnull ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
return (List<IBaseResource>) getList(); // indexes are ignored for this provider type
}

View File

@ -48,7 +48,8 @@ public class BundleProviders {
return new IBundleProvider() {
@Nonnull
@Override
public List<IBaseResource> getResources(int theFromIndex, int theToIndex, ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
public List<IBaseResource> getResources(
int theFromIndex, int theToIndex, ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
return Collections.emptyList();
}

View File

@ -26,11 +26,11 @@ import org.apache.commons.lang3.builder.ToStringBuilder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import javax.annotation.Nonnull;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import javax.annotation.Nonnull;
public class SimpleBundleProvider implements IBundleProvider {
@ -142,7 +142,8 @@ public class SimpleBundleProvider implements IBundleProvider {
@SuppressWarnings("unchecked")
@Nonnull
@Override
public List<IBaseResource> getResources(int theFromIndex, int theToIndex, @Nonnull ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
public List<IBaseResource> getResources(
int theFromIndex, int theToIndex, @Nonnull ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
return (List<IBaseResource>)
myList.subList(Math.min(theFromIndex, myList.size()), Math.min(theToIndex, myList.size()));
}

View File

@ -181,7 +181,8 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
@Nonnull
@Override
public List<IBaseResource> getResources(int theFromIndex, int theToIndex, ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
public List<IBaseResource> getResources(
int theFromIndex, int theToIndex, ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
List<IBaseResource> retVal = resources.getResources(theFromIndex, theToIndex, theResponsePageBuilder);
int index = theFromIndex;
for (IBaseResource nextResource : retVal) {

View File

@ -111,7 +111,8 @@ public class ResponseBundleBuilder {
numToReturn = Math.min(pageSize, size.intValue() - theResponseBundleRequest.offset);
}
resourceList = pagingBuildResourceList(theResponseBundleRequest, bundleProvider, numToReturn, responsePageBuilder);
resourceList =
pagingBuildResourceList(theResponseBundleRequest, bundleProvider, numToReturn, responsePageBuilder);
RestfulServerUtils.validateResourceListNotNull(resourceList);
searchId = pagingBuildSearchId(theResponseBundleRequest, numToReturn, bundleProvider.size());
@ -121,11 +122,11 @@ public class ResponseBundleBuilder {
// But since we haven't updated all such providers, we will
// build it here (this is at best 'duplicating' work).
responsePageBuilder
.setSearchId(searchId)
.setPageSize(pageSize)
.setNumToReturn(numToReturn)
.setNumTotalResults(bundleProvider.size())
.setResources(resourceList);
.setSearchId(searchId)
.setPageSize(pageSize)
.setNumToReturn(numToReturn)
.setNumTotalResults(bundleProvider.size())
.setResources(resourceList);
return responsePageBuilder.build();
}
@ -156,15 +157,13 @@ public class ResponseBundleBuilder {
ResponseBundleRequest theResponseBundleRequest,
IBundleProvider theBundleProvider,
int theNumToReturn,
ResponsePage.ResponsePageBuilder theResponsePageBuilder
) {
ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
final List<IBaseResource> retval;
if (theNumToReturn > 0 || theBundleProvider.getCurrentPageId() != null) {
retval = theBundleProvider.getResources(
theResponseBundleRequest.offset,
theNumToReturn + theResponseBundleRequest.offset,
theResponsePageBuilder
);
theResponseBundleRequest.offset,
theNumToReturn + theResponseBundleRequest.offset,
theResponsePageBuilder);
} else {
retval = Collections.emptyList();
}
@ -180,11 +179,10 @@ public class ResponseBundleBuilder {
}
private List<IBaseResource> offsetBuildResourceList(
IBundleProvider theBundleProvider,
RequestedPage theRequestedPage,
int theNumToReturn,
ResponsePage.ResponsePageBuilder theResponsePageBuilder
) {
IBundleProvider theBundleProvider,
RequestedPage theRequestedPage,
int theNumToReturn,
ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
final List<IBaseResource> retval;
if ((theRequestedPage.offset != null && !myIsOffsetModeHistory)
|| theBundleProvider.getCurrentPageOffset() != null) {
@ -200,10 +198,7 @@ public class ResponseBundleBuilder {
}
private static int offsetCalculatePageSize(
IRestfulServer<?> server,
RequestedPage theRequestedPage,
Integer theNumTotalResults
) {
IRestfulServer<?> server, RequestedPage theRequestedPage, Integer theNumTotalResults) {
final int retval;
if (theRequestedPage.limit != null) {
retval = theRequestedPage.limit;
@ -266,8 +261,8 @@ public class ResponseBundleBuilder {
// determine if we are using offset / uncached pages
theResponsePage.setUseOffsetPaging(pageRequest.offset != null
|| (!server.canStoreSearchResults() && !isEverythingOperation(theResponseBundleRequest.requestDetails))
|| myIsOffsetModeHistory);
|| (!server.canStoreSearchResults() && !isEverythingOperation(theResponseBundleRequest.requestDetails))
|| myIsOffsetModeHistory);
theResponsePage.setResponseBundleRequest(theResponseBundleRequest);
theResponsePage.setRequestedPage(pageRequest);
theResponsePage.setBundleProvider(bundleProvider);

View File

@ -25,6 +25,8 @@ import ca.uhn.fhir.rest.server.RestfulServerUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
@ -32,6 +34,9 @@ import java.util.List;
* This is an intermediate record object that holds all the fields required to make the final bundle that will be returned to the client.
*/
public class ResponsePage {
private static final Logger ourLog = LoggerFactory.getLogger(ResponsePage.class);
/**
* The id of the search used to page through search results
*/
@ -59,6 +64,13 @@ public class ResponsePage {
* These _include resources are otherwise included in the resourceList.
*/
private final int myIncludedResourceCount;
/**
* This is the count of resources that have been omitted from results
* (typically because of consent interceptors).
* We track these because they shouldn't change paging results,
* even though it will change number of resources returned.
*/
private final int myOmittedResourceCount;
// Properties below here are set for calculation of pages;
// not part of the response pages in and of themselves
@ -91,19 +103,20 @@ public class ResponsePage {
private PagingStyle myPagingStyle;
ResponsePage(
String theSearchId,
List<IBaseResource> theResourceList,
int thePageSize,
int theNumToReturn,
Integer theNumTotalResults,
int theIncludedResourceCount
) {
String theSearchId,
List<IBaseResource> theResourceList,
int thePageSize,
int theNumToReturn,
Integer theNumTotalResults,
int theIncludedResourceCount,
int theOmittedResourceCount) {
mySearchId = theSearchId;
myResourceList = theResourceList;
myPageSize = thePageSize;
myNumToReturn = theNumToReturn;
myNumTotalResults = theNumTotalResults;
myIncludedResourceCount = theIncludedResourceCount;
myOmittedResourceCount = theOmittedResourceCount;
}
public int size() {
@ -120,7 +133,9 @@ public class ResponsePage {
return;
}
if (myBundleProvider != null && myBundleProvider.getCurrentPageOffset() != null) {
if (myBundleProvider != null && (myBundleProvider.getCurrentPageOffset() != null
|| (StringUtils.isNotBlank(myBundleProvider.getNextPageId()) || StringUtils.isNotBlank(myBundleProvider.getPreviousPageId())))
) {
myPagingStyle = PagingStyle.BUNDLE_PROVIDER_OFFSETS;
} else if (myIsUsingOffsetPages) {
myPagingStyle = PagingStyle.NONCACHED_OFFSET;
@ -128,9 +143,12 @@ public class ResponsePage {
myPagingStyle = PagingStyle.BUNDLE_PROVIDER_PAGE_IDS;
} else if (StringUtils.isNotBlank(mySearchId)) {
myPagingStyle = PagingStyle.SAVED_SEARCH;
} else {
myPagingStyle = PagingStyle.UNKNOWN;
// only our unit tests end up here
ourLog.warn("Response page requires more information to determine paging style. " +
"Did you remember to mock out all proper values?");
}
assert myPagingStyle != null : "Response page requires more information to determine paging style";
}
public void setRequestedPage(RequestedPage theRequestedPage) {
@ -170,22 +188,18 @@ public class ResponsePage {
* we'll also have a myNumTotalResults value.
*/
return true;
} else if (myNumTotalResults > myNumToReturn + myRequestedPage.offset) {
} else if (myNumTotalResults > myNumToReturn + ObjectUtils.defaultIfNull(myRequestedPage.offset, 0)) {
return true;
}
break;
case SAVED_SEARCH:
if (myNumTotalResults == null) {
if (myPageSize == myResourceList.size() - myIncludedResourceCount) {
// if the size of the resource list - included resources == pagesize
if (myPageSize == myResourceList.size() + myOmittedResourceCount - myIncludedResourceCount) {
// if the size of the resource list - included resources + omitted resources == pagesize
// we have more pages
return true;
}
} else if (myResponseBundleRequest.offset + myNumToReturn - myIncludedResourceCount < myNumTotalResults) {
/*
* if we have an accurate total (myNumTotalResults),
* this value is supposed to exclude _include's resource count
*/
} else if (myResponseBundleRequest.offset + myNumToReturn < myNumTotalResults) {
return true;
}
break;
@ -201,42 +215,38 @@ public class ResponsePage {
switch (myPagingStyle) {
case BUNDLE_PROVIDER_OFFSETS:
next = RestfulServerUtils.createOffsetPagingLink(
theLinks,
myResponseBundleRequest.requestDetails.getRequestPath(),
myResponseBundleRequest.requestDetails.getTenantId(),
myRequestedPage.offset + myRequestedPage.limit,
myRequestedPage.limit,
myResponseBundleRequest.getRequestParameters()
);
theLinks,
myResponseBundleRequest.requestDetails.getRequestPath(),
myResponseBundleRequest.requestDetails.getTenantId(),
myRequestedPage.offset + myRequestedPage.limit,
myRequestedPage.limit,
myResponseBundleRequest.getRequestParameters());
break;
case NONCACHED_OFFSET:
next = RestfulServerUtils.createOffsetPagingLink(
theLinks,
myResponseBundleRequest.requestDetails.getRequestPath(),
myResponseBundleRequest.requestDetails.getTenantId(),
ObjectUtils.defaultIfNull(myRequestedPage.offset, 0) + myNumToReturn,
myNumToReturn,
myResponseBundleRequest.getRequestParameters()
);
theLinks,
myResponseBundleRequest.requestDetails.getRequestPath(),
myResponseBundleRequest.requestDetails.getTenantId(),
ObjectUtils.defaultIfNull(myRequestedPage.offset, 0) + myNumToReturn,
myNumToReturn,
myResponseBundleRequest.getRequestParameters());
break;
case BUNDLE_PROVIDER_PAGE_IDS:
next = RestfulServerUtils.createPagingLink(
theLinks,
myResponseBundleRequest.requestDetails,
myBundleProvider.getUuid(),
myBundleProvider.getNextPageId(),
myResponseBundleRequest.getRequestParameters()
);
theLinks,
myResponseBundleRequest.requestDetails,
myBundleProvider.getUuid(),
myBundleProvider.getNextPageId(),
myResponseBundleRequest.getRequestParameters());
break;
case SAVED_SEARCH:
next = RestfulServerUtils.createPagingLink(
theLinks,
myResponseBundleRequest.requestDetails,
mySearchId,
myResponseBundleRequest.offset + myNumToReturn,
myNumToReturn,
myResponseBundleRequest.getRequestParameters()
);
theLinks,
myResponseBundleRequest.requestDetails,
mySearchId,
myResponseBundleRequest.offset + myNumToReturn,
myNumToReturn,
myResponseBundleRequest.getRequestParameters());
break;
default:
next = null;
@ -274,47 +284,45 @@ public class ResponsePage {
switch (myPagingStyle) {
case BUNDLE_PROVIDER_OFFSETS:
prev = RestfulServerUtils.createOffsetPagingLink(
theLinks,
myResponseBundleRequest.requestDetails.getRequestPath(),
myResponseBundleRequest.requestDetails.getTenantId(),
Math.max(ObjectUtils.defaultIfNull(myRequestedPage.offset, 0) - myRequestedPage.limit, 0),
myRequestedPage.limit,
myResponseBundleRequest.getRequestParameters()
);
theLinks,
myResponseBundleRequest.requestDetails.getRequestPath(),
myResponseBundleRequest.requestDetails.getTenantId(),
Math.max(ObjectUtils.defaultIfNull(myRequestedPage.offset, 0) - myRequestedPage.limit, 0),
myRequestedPage.limit,
myResponseBundleRequest.getRequestParameters());
break;
case NONCACHED_OFFSET:
{
int start = Math.max(0, ObjectUtils.defaultIfNull(myRequestedPage.offset, 0) - myPageSize);
prev = RestfulServerUtils.createOffsetPagingLink(
theLinks,
myResponseBundleRequest.requestDetails.getRequestPath(),
myResponseBundleRequest.requestDetails.getTenantId(),
start,
myPageSize,
myResponseBundleRequest.getRequestParameters());
}
break;
case NONCACHED_OFFSET: {
int start = Math.max(0, ObjectUtils.defaultIfNull(myRequestedPage.offset, 0) - myPageSize);
prev = RestfulServerUtils.createOffsetPagingLink(
theLinks,
myResponseBundleRequest.requestDetails.getRequestPath(),
myResponseBundleRequest.requestDetails.getTenantId(),
start,
myPageSize,
myResponseBundleRequest.getRequestParameters()
);
}
break;
case BUNDLE_PROVIDER_PAGE_IDS:
prev = RestfulServerUtils.createPagingLink(
theLinks,
myResponseBundleRequest.requestDetails,
myBundleProvider.getUuid(),
myBundleProvider.getPreviousPageId(),
myResponseBundleRequest.getRequestParameters()
);
theLinks,
myResponseBundleRequest.requestDetails,
myBundleProvider.getUuid(),
myBundleProvider.getPreviousPageId(),
myResponseBundleRequest.getRequestParameters());
break;
case SAVED_SEARCH:
{
int start = Math.max(0, myResponseBundleRequest.offset - myPageSize);
prev = RestfulServerUtils.createPagingLink(
theLinks,
myResponseBundleRequest.requestDetails,
mySearchId,
start,
myPageSize,
myResponseBundleRequest.getRequestParameters());
}
break;
case SAVED_SEARCH: {
int start = Math.max(0, myResponseBundleRequest.offset - myPageSize);
prev = RestfulServerUtils.createPagingLink(
theLinks,
myResponseBundleRequest.requestDetails,
mySearchId,
start,
myPageSize,
myResponseBundleRequest.getRequestParameters()
);
}
break;
default:
prev = null;
}
@ -336,6 +344,12 @@ public class ResponsePage {
private int myPageSize;
private int myNumToReturn;
private int myIncludedResourceCount;
private int myOmittedResourceCount;
public ResponsePageBuilder setOmittedResourceCount(int theOmittedResourceCount) {
myOmittedResourceCount = theOmittedResourceCount;
return this;
}
public ResponsePageBuilder setIncludedResourceCount(int theIncludedResourceCount) {
myIncludedResourceCount = theIncludedResourceCount;
@ -369,33 +383,60 @@ public class ResponsePage {
public ResponsePage build() {
return new ResponsePage(
mySearchId, // search id
myResources, // resource list
myPageSize, // page size
myNumToReturn, // num to return
myNumTotalResults, // total results
myIncludedResourceCount // included count
mySearchId, // search id
myResources, // resource list
myPageSize, // page size
myNumToReturn, // num to return
myNumTotalResults, // total results
myIncludedResourceCount, // included count
myOmittedResourceCount // omitted resources
);
}
}
/**
* First we determine what kind of paging we use:
* * Bundle Provider Offsets - the bundle provider has offset counts that it uses
* to determine the page. For legacy reasons, it's not enough
* that the bundle provider has a currentOffsetPage. Sometimes
* this value is provided (often as a 0), but no nextPageId nor previousPageId
* is available. Typically this is the case in UnitTests.
* * non-cached offsets - if the server is not storing the search results (and it's not
* an everything operator) OR the Requested Page has an initial offset
* OR it is explicitly set to use non-cached offset
* (ResponseBundleBuilder.myIsOffsetModeHistory)
* * Bundle Provider Page Ids - the bundle provider knows the page ids and will
* provide them. bundle provider will have a currentPageId
* * Saved Search - the server has a saved search object with an id that it
* uses to page through results.
*/
private enum PagingStyle {
/**
* Paging is done by offsets; pages are not cached
*/
NONCACHED_OFFSET,
/**
* The bundle provider provides the offsets
* Paging is done by offsets, but
* the bundle provider provides the offsets
*/
BUNDLE_PROVIDER_OFFSETS,
/**
* Paging by page ids, but provided by the bundle provider
* Paging is done by page ids,
* but bundle provider provides the page ids
*/
BUNDLE_PROVIDER_PAGE_IDS,
/**
* A saved search (search exists in the database).
* The server has a saved search object with an id
* that is used to page through results.
*/
SAVED_SEARCH
SAVED_SEARCH,
/**
* Our unit tests have a tendency to not include all the mocked out
* values, resulting in invalid search pages being returned.
*
* These tests also do not check links, so correctness was never verified.
* This search status should never appear in production.
*/
UNKNOWN;
}
}

View File

@ -65,7 +65,6 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -78,6 +77,7 @@ import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import static java.lang.Math.max;
import static java.lang.Math.min;
@ -320,7 +320,10 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
@SuppressWarnings("unchecked")
@Nonnull
@Override
public List<IBaseResource> getResources(int theFromIndex, int theToIndex, @Nonnull ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
public List<IBaseResource> getResources(
int theFromIndex,
int theToIndex,
@Nonnull ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
// Make sure that "from" isn't less than 0, "to" isn't more than the number available,
// and "from" <= "to"