adding a test
This commit is contained in:
parent
667421db77
commit
2c0f157ce9
|
@ -24,6 +24,7 @@ import ca.uhn.fhir.rest.annotation.Search;
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
import ca.uhn.fhir.rest.param.StringParam;
|
import ca.uhn.fhir.rest.param.StringParam;
|
||||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||||
|
import ca.uhn.fhir.rest.server.method.ResponsePage;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.r4.model.InstantType;
|
import org.hl7.fhir.r4.model.InstantType;
|
||||||
import org.hl7.fhir.r4.model.Patient;
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
|
@ -63,7 +64,7 @@ public class PagingPatientProvider implements IResourceProvider {
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
|
public List<IBaseResource> getResources(int theFromIndex, int theToIndex, @Nonnull ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
|
||||||
int end = Math.max(theToIndex, matchingResourceIds.size() - 1);
|
int end = Math.max(theToIndex, matchingResourceIds.size() - 1);
|
||||||
List<Long> idsToReturn = matchingResourceIds.subList(theFromIndex, end);
|
List<Long> idsToReturn = matchingResourceIds.subList(theFromIndex, end);
|
||||||
return loadResourcesByIds(idsToReturn);
|
return loadResourcesByIds(idsToReturn);
|
||||||
|
|
|
@ -55,23 +55,25 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.api.server.SimplePreResourceAccessDetails;
|
import ca.uhn.fhir.rest.api.server.SimplePreResourceAccessDetails;
|
||||||
import ca.uhn.fhir.rest.api.server.SimplePreResourceShowDetails;
|
import ca.uhn.fhir.rest.api.server.SimplePreResourceShowDetails;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.ServerInterceptorUtil;
|
import ca.uhn.fhir.rest.server.interceptor.ServerInterceptorUtil;
|
||||||
|
import ca.uhn.fhir.rest.server.method.ResponsePage;
|
||||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||||
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
|
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
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.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import javax.persistence.EntityManager;
|
|
||||||
import javax.persistence.PersistenceContext;
|
|
||||||
|
|
||||||
public class PersistedJpaBundleProvider implements IBundleProvider {
|
public class PersistedJpaBundleProvider implements IBundleProvider {
|
||||||
|
|
||||||
|
@ -128,6 +130,7 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
||||||
private String myUuid;
|
private String myUuid;
|
||||||
private SearchCacheStatusEnum myCacheStatus;
|
private SearchCacheStatusEnum myCacheStatus;
|
||||||
private RequestPartitionId myRequestPartitionId;
|
private RequestPartitionId myRequestPartitionId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
|
@ -150,6 +153,13 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
||||||
myRequestPartitionHelperSvc = theRequestPartitionHelperSvc;
|
myRequestPartitionHelperSvc = theRequestPartitionHelperSvc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponsePage.ResponsePageBuilder getResponsePageBuilder() {
|
||||||
|
ResponsePage.ResponsePageBuilder builder = new ResponsePage.ResponsePageBuilder();
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
protected Search getSearchEntity() {
|
protected Search getSearchEntity() {
|
||||||
return mySearchEntity;
|
return mySearchEntity;
|
||||||
}
|
}
|
||||||
|
@ -163,7 +173,9 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
||||||
/**
|
/**
|
||||||
* Perform a history search
|
* 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(
|
HistoryBuilder historyBuilder = myHistoryBuilderFactory.newHistoryBuilder(
|
||||||
mySearchEntity.getResourceType(),
|
mySearchEntity.getResourceType(),
|
||||||
|
@ -180,7 +192,6 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
||||||
BaseHasResource resource;
|
BaseHasResource resource;
|
||||||
resource = next;
|
resource = next;
|
||||||
|
|
||||||
IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(next.getResourceType());
|
|
||||||
retVal.add(myJpaStorageResourceParser.toResource(resource, true));
|
retVal.add(myJpaStorageResourceParser.toResource(resource, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,7 +249,9 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
||||||
myRequestPartitionId = theRequestPartitionId;
|
myRequestPartitionId = theRequestPartitionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected List<IBaseResource> doSearchOrEverything(final int theFromIndex, final int theToIndex) {
|
protected List<IBaseResource> doSearchOrEverything(
|
||||||
|
final int theFromIndex, final int theToIndex, ResponsePage.ResponsePageBuilder theResponsePageBuilder
|
||||||
|
) {
|
||||||
if (mySearchEntity.getTotalCount() != null && mySearchEntity.getNumFound() <= 0) {
|
if (mySearchEntity.getTotalCount() != null && mySearchEntity.getNumFound() <= 0) {
|
||||||
// No resources to fetch (e.g. we did a _summary=count search)
|
// No resources to fetch (e.g. we did a _summary=count search)
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
|
@ -253,12 +266,14 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
||||||
RequestPartitionId requestPartitionId = getRequestPartitionId();
|
RequestPartitionId requestPartitionId = getRequestPartitionId();
|
||||||
final List<JpaPid> pidsSubList =
|
final List<JpaPid> pidsSubList =
|
||||||
mySearchCoordinatorSvc.getResources(myUuid, theFromIndex, theToIndex, myRequest, requestPartitionId);
|
mySearchCoordinatorSvc.getResources(myUuid, theFromIndex, theToIndex, myRequest, requestPartitionId);
|
||||||
return myTxService
|
List<IBaseResource> resources = myTxService
|
||||||
.withRequest(myRequest)
|
.withRequest(myRequest)
|
||||||
.withRequestPartitionId(requestPartitionId)
|
.withRequestPartitionId(requestPartitionId)
|
||||||
.execute(() -> {
|
.execute(() -> {
|
||||||
return toResourceList(sb, pidsSubList);
|
return toResourceList(sb, pidsSubList, theResponsePageBuilder);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return resources;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -349,9 +364,16 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
||||||
return new InstantDt(mySearchEntity.getCreated());
|
return new InstantDt(mySearchEntity.getCreated());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public List<IBaseResource> getResources(final int theFromIndex, final int theToIndex) {
|
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
|
||||||
|
return getResources(theFromIndex, theToIndex, getResponsePageBuilder());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<IBaseResource> getResources(
|
||||||
|
int theFromIndex, int theToIndex, @Nonnull ResponsePage.ResponsePageBuilder theResponsePageBuilder
|
||||||
|
) {
|
||||||
boolean entityLoaded = ensureSearchEntityLoaded();
|
boolean entityLoaded = ensureSearchEntityLoaded();
|
||||||
assert entityLoaded;
|
assert entityLoaded;
|
||||||
assert mySearchEntity != null;
|
assert mySearchEntity != null;
|
||||||
|
@ -366,7 +388,7 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
||||||
case SEARCH:
|
case SEARCH:
|
||||||
case EVERYTHING:
|
case EVERYTHING:
|
||||||
default:
|
default:
|
||||||
List<IBaseResource> retVal = doSearchOrEverything(theFromIndex, theToIndex);
|
List<IBaseResource> retVal = doSearchOrEverything(theFromIndex, theToIndex, theResponsePageBuilder);
|
||||||
/*
|
/*
|
||||||
* If we got fewer resources back than we asked for, it's possible that the search
|
* If we got fewer resources back than we asked for, it's possible that the search
|
||||||
* completed. If that's the case, the cached version of the search entity is probably
|
* completed. If that's the case, the cached version of the search entity is probably
|
||||||
|
@ -443,8 +465,11 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
||||||
|
|
||||||
// Note: Leave as protected, HSPC depends on this
|
// Note: Leave as protected, HSPC depends on this
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
protected List<IBaseResource> toResourceList(ISearchBuilder theSearchBuilder, List<JpaPid> thePids) {
|
protected List<IBaseResource> toResourceList(
|
||||||
|
ISearchBuilder theSearchBuilder,
|
||||||
|
List<JpaPid> thePids,
|
||||||
|
ResponsePage.ResponsePageBuilder theResponsePageBuilder
|
||||||
|
) {
|
||||||
List<JpaPid> includedPidList = new ArrayList<>();
|
List<JpaPid> includedPidList = new ArrayList<>();
|
||||||
if (mySearchEntity.getSearchType() == SearchTypeEnum.SEARCH) {
|
if (mySearchEntity.getSearchType() == SearchTypeEnum.SEARCH) {
|
||||||
Integer maxIncludes = myStorageSettings.getMaximumIncludesToLoadPerPage();
|
Integer maxIncludes = myStorageSettings.getMaximumIncludesToLoadPerPage();
|
||||||
|
@ -522,6 +547,8 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
||||||
theSearchBuilder.loadResourcesByPid(thePids, includedPidList, resources, false, myRequest);
|
theSearchBuilder.loadResourcesByPid(thePids, includedPidList, resources, false, myRequest);
|
||||||
|
|
||||||
resources = ServerInterceptorUtil.fireStoragePreshowResource(resources, myRequest, myInterceptorBroadcaster);
|
resources = ServerInterceptorUtil.fireStoragePreshowResource(resources, myRequest, myInterceptorBroadcaster);
|
||||||
|
theResponsePageBuilder.setResources(resources);
|
||||||
|
theResponsePageBuilder.setIncludedResourceCount(includedPidList.size());
|
||||||
|
|
||||||
return resources;
|
return resources;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,23 +30,26 @@ import ca.uhn.fhir.jpa.util.QueryParameterUtils;
|
||||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||||
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
|
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.server.method.ResponsePage;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
|
|
||||||
public class PersistedJpaSearchFirstPageBundleProvider extends PersistedJpaBundleProvider {
|
public class PersistedJpaSearchFirstPageBundleProvider extends PersistedJpaBundleProvider {
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(PersistedJpaSearchFirstPageBundleProvider.class);
|
private static final Logger ourLog = LoggerFactory.getLogger(PersistedJpaSearchFirstPageBundleProvider.class);
|
||||||
private final SearchTask mySearchTask;
|
private final SearchTask mySearchTask;
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
private final ISearchBuilder mySearchBuilder;
|
private final ISearchBuilder mySearchBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
public PersistedJpaSearchFirstPageBundleProvider(
|
public PersistedJpaSearchFirstPageBundleProvider(
|
||||||
Search theSearch,
|
Search theSearch,
|
||||||
SearchTask theSearchTask,
|
SearchTask theSearchTask,
|
||||||
|
@ -65,7 +68,7 @@ public class PersistedJpaSearchFirstPageBundleProvider extends PersistedJpaBundl
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
|
public List<IBaseResource> getResources(int theFromIndex, int theToIndex, @Nonnull ResponsePage.ResponsePageBuilder thePageBuilder) {
|
||||||
ensureSearchEntityLoaded();
|
ensureSearchEntityLoaded();
|
||||||
QueryParameterUtils.verifySearchHasntFailedOrThrowInternalErrorException(getSearchEntity());
|
QueryParameterUtils.verifySearchHasntFailedOrThrowInternalErrorException(getSearchEntity());
|
||||||
|
|
||||||
|
@ -80,7 +83,7 @@ public class PersistedJpaSearchFirstPageBundleProvider extends PersistedJpaBundl
|
||||||
List<IBaseResource> retVal = myTxService
|
List<IBaseResource> retVal = myTxService
|
||||||
.withRequest(myRequest)
|
.withRequest(myRequest)
|
||||||
.withRequestPartitionId(requestPartitionId)
|
.withRequestPartitionId(requestPartitionId)
|
||||||
.execute(() -> toResourceList(mySearchBuilder, pids));
|
.execute(() -> toResourceList(mySearchBuilder, pids, thePageBuilder));
|
||||||
|
|
||||||
long totalCountWanted = theToIndex - theFromIndex;
|
long totalCountWanted = theToIndex - theFromIndex;
|
||||||
long totalCountMatch = (int) retVal.stream().filter(t -> !isInclude(t)).count();
|
long totalCountMatch = (int) retVal.stream().filter(t -> !isInclude(t)).count();
|
||||||
|
@ -101,7 +104,7 @@ public class PersistedJpaSearchFirstPageBundleProvider extends PersistedJpaBundl
|
||||||
|
|
||||||
long remainingWanted = totalCountWanted - totalCountMatch;
|
long remainingWanted = totalCountWanted - totalCountMatch;
|
||||||
long fromIndex = theToIndex - remainingWanted;
|
long fromIndex = theToIndex - remainingWanted;
|
||||||
List<IBaseResource> remaining = super.getResources((int) fromIndex, theToIndex);
|
List<IBaseResource> remaining = super.getResources((int) fromIndex, theToIndex, thePageBuilder);
|
||||||
remaining.forEach(t -> {
|
remaining.forEach(t -> {
|
||||||
if (!existingIds.contains(t.getIdElement().getValue())) {
|
if (!existingIds.contains(t.getIdElement().getValue())) {
|
||||||
retVal.add(t);
|
retVal.add(t);
|
||||||
|
|
|
@ -146,6 +146,7 @@ import org.hl7.fhir.r4.model.StructureDefinition;
|
||||||
import org.hl7.fhir.r4.model.Subscription;
|
import org.hl7.fhir.r4.model.Subscription;
|
||||||
import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType;
|
import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType;
|
||||||
import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus;
|
import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus;
|
||||||
|
import org.hl7.fhir.r4.model.Task;
|
||||||
import org.hl7.fhir.r4.model.UriType;
|
import org.hl7.fhir.r4.model.UriType;
|
||||||
import org.hl7.fhir.r4.model.ValueSet;
|
import org.hl7.fhir.r4.model.ValueSet;
|
||||||
import org.hl7.fhir.utilities.xhtml.NodeType;
|
import org.hl7.fhir.utilities.xhtml.NodeType;
|
||||||
|
@ -2654,6 +2655,92 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private void printResources(Bundle theBundle) {
|
||||||
|
IParser parser = myFhirContext.newJsonParser();
|
||||||
|
for (BundleEntryComponent entry : theBundle.getEntry()) {
|
||||||
|
ourLog.info(entry.getResource().getId());
|
||||||
|
// ourLog.info(
|
||||||
|
// parser.encodeResourceToString(entry.getResource())
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO - we should not be including the
|
||||||
|
// _include count in the _count
|
||||||
|
// so current behaviour is correct
|
||||||
|
// but we should also not be including a next
|
||||||
|
// list if there are no more resources
|
||||||
|
@Test
|
||||||
|
public void testPagingWithIncludesReturnsConsistentValues() {
|
||||||
|
// setup
|
||||||
|
int total = 19;
|
||||||
|
int orgs = 10;
|
||||||
|
// create resources
|
||||||
|
{
|
||||||
|
Coding tagCode = new Coding();
|
||||||
|
tagCode.setCode("test");
|
||||||
|
tagCode.setSystem("http://example.com");
|
||||||
|
int orgCount = orgs;
|
||||||
|
for (int i = 0; i < total; i++) {
|
||||||
|
Task t = new Task();
|
||||||
|
t.getMeta()
|
||||||
|
.addTag(tagCode);
|
||||||
|
t.setStatus(Task.TaskStatus.REQUESTED);
|
||||||
|
if (orgCount > 0) {
|
||||||
|
Organization org = new Organization();
|
||||||
|
org.setName("ORG");
|
||||||
|
IIdType orgId = myOrganizationDao.create(org).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
orgCount--;
|
||||||
|
t.getOwner().setReference(orgId.getValue());
|
||||||
|
}
|
||||||
|
myTaskDao.create(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int requestedAmount = 10;
|
||||||
|
Bundle bundle = myClient
|
||||||
|
.search()
|
||||||
|
.byUrl("Task?_count=10&_tag=test&status=requested&_include=Task%3Aowner&_sort=status")
|
||||||
|
.returnBundle(Bundle.class)
|
||||||
|
.execute();
|
||||||
|
int count = bundle.getEntry().size();
|
||||||
|
assertTrue(bundle.getEntry().size() > 0);
|
||||||
|
System.out.println("Received " + bundle.getEntry().size());
|
||||||
|
// assertEquals(requestedAmount, count);
|
||||||
|
printResources(bundle);
|
||||||
|
String nextUrl = null;
|
||||||
|
do {
|
||||||
|
Bundle.BundleLinkComponent nextLink = bundle.getLink("next");
|
||||||
|
if (nextLink != null) {
|
||||||
|
nextUrl = nextLink.getUrl();
|
||||||
|
|
||||||
|
// make sure we're always requesting 10
|
||||||
|
assertTrue(nextUrl.contains(String.format("_count=%d", requestedAmount)));
|
||||||
|
|
||||||
|
// get next batch
|
||||||
|
bundle = myClient.fetchResourceFromUrl(Bundle.class, nextUrl);
|
||||||
|
int received = bundle.getEntry().size();
|
||||||
|
|
||||||
|
// verify it's the same amount as we requested
|
||||||
|
System.out.println("Recieved " + received);
|
||||||
|
printResources(bundle);
|
||||||
|
|
||||||
|
assertTrue(bundle.getEntry().size() > 0);
|
||||||
|
// assertEquals(requestedAmount, received);
|
||||||
|
count += received;
|
||||||
|
} else {
|
||||||
|
nextUrl = null;
|
||||||
|
}
|
||||||
|
} while (nextUrl != null);
|
||||||
|
|
||||||
|
// verify
|
||||||
|
assertEquals(total + orgs, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See #793
|
* See #793
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.api.server;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.ConfigurationException;
|
import ca.uhn.fhir.context.ConfigurationException;
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
import ca.uhn.fhir.i18n.Msg;
|
||||||
|
import ca.uhn.fhir.rest.server.method.ResponsePage;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
|
@ -34,6 +35,14 @@ import javax.annotation.Nullable;
|
||||||
|
|
||||||
public interface IBundleProvider {
|
public interface IBundleProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a ResponsePageBuilder for constructing
|
||||||
|
* pages that return results.
|
||||||
|
*/
|
||||||
|
default ResponsePage.ResponsePageBuilder getResponsePageBuilder() {
|
||||||
|
return new ResponsePage.ResponsePageBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If this method is implemented, provides an ID for the current
|
* If this method is implemented, provides an ID for the current
|
||||||
* page of results. This ID should be unique (at least within
|
* page of results. This ID should be unique (at least within
|
||||||
|
@ -113,6 +122,32 @@ public interface IBundleProvider {
|
||||||
*/
|
*/
|
||||||
IPrimitiveType<Date> getPublished();
|
IPrimitiveType<Date> getPublished();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deprecated: Use the getResources(int from, int to, ResourcePageBuilder builder) instead.
|
||||||
|
* <p>
|
||||||
|
* Load the given collection of resources by index, plus any additional resources per the
|
||||||
|
* server's processing rules (e.g. _include'd resources, OperationOutcome, etc.). For example,
|
||||||
|
* if the method is invoked with index 0,10 the method might return 10 search results, plus an
|
||||||
|
* additional 20 resources which matched a client's _include specification.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Note that if this bundle provider was loaded using a
|
||||||
|
* page ID (i.e. via {@link ca.uhn.fhir.rest.server.IPagingProvider#retrieveResultList(RequestDetails, String, String)}
|
||||||
|
* because {@link #getNextPageId()} provided a value on the
|
||||||
|
* previous page, then the indexes should be ignored and the
|
||||||
|
* whole page returned.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param theFromIndex The low index (inclusive) to return
|
||||||
|
* @param theToIndex The high index (exclusive) to return
|
||||||
|
* @return A list of resources. The size of this list must be at least <code>theToIndex - theFromIndex</code>.
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
@Deprecated(forRemoval = true, since = "7.0.0")
|
||||||
|
default List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
|
||||||
|
return getResources(theFromIndex, theToIndex, getResponsePageBuilder());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the given collection of resources by index, plus any additional resources per the
|
* Load the given collection of resources by index, plus any additional resources per the
|
||||||
* server's processing rules (e.g. _include'd resources, OperationOutcome, etc.). For example,
|
* server's processing rules (e.g. _include'd resources, OperationOutcome, etc.). For example,
|
||||||
|
@ -128,10 +163,13 @@ public interface IBundleProvider {
|
||||||
*
|
*
|
||||||
* @param theFromIndex The low index (inclusive) to return
|
* @param theFromIndex The low index (inclusive) to return
|
||||||
* @param theToIndex The high index (exclusive) to return
|
* @param theToIndex The high index (exclusive) to return
|
||||||
|
* @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>.
|
* @return A list of resources. The size of this list must be at least <code>theToIndex - theFromIndex</code>.
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
default List<IBaseResource> getResources(int theFromIndex, int theToIndex, @Nonnull ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
|
||||||
List<IBaseResource> getResources(int theFromIndex, int theToIndex);
|
// TODO - override and implement
|
||||||
|
return getResources(theFromIndex, theToIndex);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all resources
|
* Get all resources
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
*/
|
*/
|
||||||
package ca.uhn.fhir.rest.server;
|
package ca.uhn.fhir.rest.server;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.rest.server.method.ResponsePage;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
|
@ -85,9 +86,10 @@ public class BundleProviderWithNamedPages extends SimpleBundleProvider {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
|
public List<IBaseResource> getResources(int theFromIndex, int theToIndex, @Nonnull ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
|
||||||
return (List<IBaseResource>) getList(); // indexes are ignored for this provider type
|
return (List<IBaseResource>) getList(); // indexes are ignored for this provider type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.server;
|
||||||
|
|
||||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
|
import ca.uhn.fhir.rest.server.method.ResponsePage;
|
||||||
import ca.uhn.fhir.util.CoverageIgnore;
|
import ca.uhn.fhir.util.CoverageIgnore;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
|
@ -47,7 +48,7 @@ public class BundleProviders {
|
||||||
return new IBundleProvider() {
|
return new IBundleProvider() {
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
|
public List<IBaseResource> getResources(int theFromIndex, int theToIndex, ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,15 +21,16 @@ package ca.uhn.fhir.rest.server;
|
||||||
|
|
||||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
|
import ca.uhn.fhir.rest.server.method.ResponsePage;
|
||||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
|
|
||||||
public class SimpleBundleProvider implements IBundleProvider {
|
public class SimpleBundleProvider implements IBundleProvider {
|
||||||
|
|
||||||
|
@ -40,6 +41,7 @@ public class SimpleBundleProvider implements IBundleProvider {
|
||||||
private IPrimitiveType<Date> myPublished = InstantDt.withCurrentTime();
|
private IPrimitiveType<Date> myPublished = InstantDt.withCurrentTime();
|
||||||
private Integer myCurrentPageOffset;
|
private Integer myCurrentPageOffset;
|
||||||
private Integer myCurrentPageSize;
|
private Integer myCurrentPageSize;
|
||||||
|
private ResponsePage.ResponsePageBuilder myPageBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
|
@ -137,9 +139,10 @@ public class SimpleBundleProvider implements IBundleProvider {
|
||||||
myPublished = thePublished;
|
myPublished = thePublished;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
|
public List<IBaseResource> getResources(int theFromIndex, int theToIndex, @Nonnull ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
|
||||||
return (List<IBaseResource>)
|
return (List<IBaseResource>)
|
||||||
myList.subList(Math.min(theFromIndex, myList.size()), Math.min(theToIndex, myList.size()));
|
myList.subList(Math.min(theFromIndex, myList.size()), Math.min(theToIndex, myList.size()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,8 +181,8 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
|
public List<IBaseResource> getResources(int theFromIndex, int theToIndex, ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
|
||||||
List<IBaseResource> retVal = resources.getResources(theFromIndex, theToIndex);
|
List<IBaseResource> retVal = resources.getResources(theFromIndex, theToIndex, theResponsePageBuilder);
|
||||||
int index = theFromIndex;
|
int index = theFromIndex;
|
||||||
for (IBaseResource nextResource : retVal) {
|
for (IBaseResource nextResource : retVal) {
|
||||||
if (nextResource.getIdElement() == null
|
if (nextResource.getIdElement() == null
|
||||||
|
|
|
@ -91,6 +91,8 @@ public class ResponseBundleBuilder {
|
||||||
final List<IBaseResource> resourceList;
|
final List<IBaseResource> resourceList;
|
||||||
final int pageSize;
|
final int pageSize;
|
||||||
|
|
||||||
|
ResponsePage.ResponsePageBuilder responsePageBuilder = bundleProvider.getResponsePageBuilder();
|
||||||
|
|
||||||
int numToReturn;
|
int numToReturn;
|
||||||
String searchId = null;
|
String searchId = null;
|
||||||
|
|
||||||
|
@ -98,24 +100,35 @@ public class ResponseBundleBuilder {
|
||||||
pageSize = offsetCalculatePageSize(server, requestedPage, bundleProvider.size());
|
pageSize = offsetCalculatePageSize(server, requestedPage, bundleProvider.size());
|
||||||
numToReturn = pageSize;
|
numToReturn = pageSize;
|
||||||
|
|
||||||
resourceList = offsetBuildResourceList(bundleProvider, requestedPage, numToReturn);
|
resourceList = offsetBuildResourceList(bundleProvider, requestedPage, numToReturn, responsePageBuilder);
|
||||||
RestfulServerUtils.validateResourceListNotNull(resourceList);
|
RestfulServerUtils.validateResourceListNotNull(resourceList);
|
||||||
} else {
|
} else {
|
||||||
pageSize = pagingCalculatePageSize(requestedPage, server.getPagingProvider());
|
pageSize = pagingCalculatePageSize(requestedPage, server.getPagingProvider());
|
||||||
|
|
||||||
if (bundleProvider.size() == null) {
|
Integer size = bundleProvider.size();
|
||||||
|
if (size == null) {
|
||||||
numToReturn = pageSize;
|
numToReturn = pageSize;
|
||||||
} else {
|
} else {
|
||||||
numToReturn = Math.min(pageSize, bundleProvider.size() - theResponseBundleRequest.offset);
|
numToReturn = Math.min(pageSize, size.intValue() - theResponseBundleRequest.offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceList = pagingBuildResourceList(theResponseBundleRequest, bundleProvider, numToReturn);
|
resourceList = pagingBuildResourceList(theResponseBundleRequest, bundleProvider, numToReturn, responsePageBuilder);
|
||||||
RestfulServerUtils.validateResourceListNotNull(resourceList);
|
RestfulServerUtils.validateResourceListNotNull(resourceList);
|
||||||
|
|
||||||
searchId = pagingBuildSearchId(theResponseBundleRequest, numToReturn, bundleProvider.size());
|
searchId = pagingBuildSearchId(theResponseBundleRequest, numToReturn, bundleProvider.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ResponsePage(searchId, resourceList, pageSize, numToReturn, bundleProvider.size());
|
// We should leave the IBundleProvider to populate these values (specifically resourceList).
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
return responsePageBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String pagingBuildSearchId(
|
private static String pagingBuildSearchId(
|
||||||
|
@ -141,11 +154,18 @@ public class ResponseBundleBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<IBaseResource> pagingBuildResourceList(
|
private static List<IBaseResource> pagingBuildResourceList(
|
||||||
ResponseBundleRequest theResponseBundleRequest, IBundleProvider theBundleProvider, int theNumToReturn) {
|
ResponseBundleRequest theResponseBundleRequest,
|
||||||
|
IBundleProvider theBundleProvider,
|
||||||
|
int theNumToReturn,
|
||||||
|
ResponsePage.ResponsePageBuilder theResponsePageBuilder
|
||||||
|
) {
|
||||||
final List<IBaseResource> retval;
|
final List<IBaseResource> retval;
|
||||||
if (theNumToReturn > 0 || theBundleProvider.getCurrentPageId() != null) {
|
if (theNumToReturn > 0 || theBundleProvider.getCurrentPageId() != null) {
|
||||||
retval = theBundleProvider.getResources(
|
retval = theBundleProvider.getResources(
|
||||||
theResponseBundleRequest.offset, theNumToReturn + theResponseBundleRequest.offset);
|
theResponseBundleRequest.offset,
|
||||||
|
theNumToReturn + theResponseBundleRequest.offset,
|
||||||
|
theResponsePageBuilder
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
retval = Collections.emptyList();
|
retval = Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
@ -161,15 +181,19 @@ public class ResponseBundleBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<IBaseResource> offsetBuildResourceList(
|
private List<IBaseResource> offsetBuildResourceList(
|
||||||
IBundleProvider theBundleProvider, RequestedPage theRequestedPage, int theNumToReturn) {
|
IBundleProvider theBundleProvider,
|
||||||
|
RequestedPage theRequestedPage,
|
||||||
|
int theNumToReturn,
|
||||||
|
ResponsePage.ResponsePageBuilder theResponsePageBuilder
|
||||||
|
) {
|
||||||
final List<IBaseResource> retval;
|
final List<IBaseResource> retval;
|
||||||
if ((theRequestedPage.offset != null && !myIsOffsetModeHistory)
|
if ((theRequestedPage.offset != null && !myIsOffsetModeHistory)
|
||||||
|| theBundleProvider.getCurrentPageOffset() != null) {
|
|| theBundleProvider.getCurrentPageOffset() != null) {
|
||||||
// When offset query is done theResult already contains correct amount (+ their includes etc.) so return
|
// When offset query is done theResult already contains correct amount (+ their includes etc.) so return
|
||||||
// everything
|
// everything
|
||||||
retval = theBundleProvider.getResources(0, Integer.MAX_VALUE);
|
retval = theBundleProvider.getResources(0, Integer.MAX_VALUE, theResponsePageBuilder);
|
||||||
} else if (theNumToReturn > 0) {
|
} else if (theNumToReturn > 0) {
|
||||||
retval = theBundleProvider.getResources(0, theNumToReturn);
|
retval = theBundleProvider.getResources(0, theNumToReturn, theResponsePageBuilder);
|
||||||
} else {
|
} else {
|
||||||
retval = Collections.emptyList();
|
retval = Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
@ -177,7 +201,10 @@ public class ResponseBundleBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int offsetCalculatePageSize(
|
private static int offsetCalculatePageSize(
|
||||||
IRestfulServer<?> server, RequestedPage theRequestedPage, Integer theNumTotalResults) {
|
IRestfulServer<?> server,
|
||||||
|
RequestedPage theRequestedPage,
|
||||||
|
Integer theNumTotalResults
|
||||||
|
) {
|
||||||
final int retval;
|
final int retval;
|
||||||
if (theRequestedPage.limit != null) {
|
if (theRequestedPage.limit != null) {
|
||||||
retval = theRequestedPage.limit;
|
retval = theRequestedPage.limit;
|
||||||
|
@ -271,7 +298,6 @@ public class ResponseBundleBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (theResponsePage.numTotalResults == null || requestedToReturn < theResponsePage.numTotalResults) {
|
if (theResponsePage.numTotalResults == null || requestedToReturn < theResponsePage.numTotalResults) {
|
||||||
|
|
||||||
retval.setNext(RestfulServerUtils.createOffsetPagingLink(
|
retval.setNext(RestfulServerUtils.createOffsetPagingLink(
|
||||||
retval,
|
retval,
|
||||||
theResponseBundleRequest.requestDetails.getRequestPath(),
|
theResponseBundleRequest.requestDetails.getRequestPath(),
|
||||||
|
|
|
@ -49,20 +49,83 @@ public class ResponsePage {
|
||||||
*/
|
*/
|
||||||
public final int numToReturn;
|
public final int numToReturn;
|
||||||
|
|
||||||
public ResponsePage(
|
/**
|
||||||
|
* The count of resources included from the _include filter.
|
||||||
|
* These _include resources are otherwise included in the resourceList.
|
||||||
|
*/
|
||||||
|
private final int includedResourceCount;
|
||||||
|
|
||||||
|
ResponsePage(
|
||||||
String theSearchId,
|
String theSearchId,
|
||||||
List<IBaseResource> theResourceList,
|
List<IBaseResource> theResourceList,
|
||||||
int thePageSize,
|
int thePageSize,
|
||||||
int theNumToReturn,
|
int theNumToReturn,
|
||||||
Integer theNumTotalResults) {
|
Integer theNumTotalResults,
|
||||||
|
int theIncludedResourceCount
|
||||||
|
) {
|
||||||
searchId = theSearchId;
|
searchId = theSearchId;
|
||||||
resourceList = theResourceList;
|
resourceList = theResourceList;
|
||||||
pageSize = thePageSize;
|
pageSize = thePageSize;
|
||||||
numToReturn = theNumToReturn;
|
numToReturn = theNumToReturn;
|
||||||
numTotalResults = theNumTotalResults;
|
numTotalResults = theNumTotalResults;
|
||||||
|
includedResourceCount = theIncludedResourceCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int size() {
|
public int size() {
|
||||||
return resourceList.size();
|
return resourceList.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A builder for constructing ResponsePage objects.
|
||||||
|
*/
|
||||||
|
public static class ResponsePageBuilder {
|
||||||
|
|
||||||
|
private String mySearchId;
|
||||||
|
private List<IBaseResource> myResources;
|
||||||
|
private Integer myNumTotalResults;
|
||||||
|
private int myPageSize;
|
||||||
|
private int myNumToReturn;
|
||||||
|
private int myIncludedResourceCount;
|
||||||
|
|
||||||
|
public ResponsePageBuilder setIncludedResourceCount(int theIncludedResourceCount) {
|
||||||
|
myIncludedResourceCount = theIncludedResourceCount;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponsePageBuilder setNumToReturn(int theNumToReturn) {
|
||||||
|
myNumToReturn = theNumToReturn;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponsePageBuilder setPageSize(int thePageSize) {
|
||||||
|
myPageSize = thePageSize;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponsePageBuilder setNumTotalResults(Integer theNumTotalResults) {
|
||||||
|
myNumTotalResults = theNumTotalResults;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponsePageBuilder setResources(List<IBaseResource> theResources) {
|
||||||
|
myResources = theResources;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponsePageBuilder setSearchId(String theSearchId) {
|
||||||
|
mySearchId = theSearchId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,7 @@ import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||||
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
|
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
|
import ca.uhn.fhir.rest.server.method.ResponsePage;
|
||||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||||
import ca.uhn.fhir.util.ValidateUtil;
|
import ca.uhn.fhir.util.ValidateUtil;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
@ -64,6 +65,7 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -76,7 +78,6 @@ import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
|
|
||||||
import static java.lang.Math.max;
|
import static java.lang.Math.max;
|
||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
|
@ -319,7 +320,7 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
|
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,
|
// Make sure that "from" isn't less than 0, "to" isn't more than the number available,
|
||||||
// and "from" <= "to"
|
// and "from" <= "to"
|
||||||
|
|
|
@ -13,6 +13,7 @@ import ca.uhn.fhir.rest.server.SimpleBundleProvider;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.r4.model.Bundle;
|
import org.hl7.fhir.r4.model.Bundle;
|
||||||
|
import org.hl7.fhir.r4.model.Organization;
|
||||||
import org.hl7.fhir.r4.model.Patient;
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
@ -150,6 +151,55 @@ class ResponseBundleBuilderTest {
|
||||||
assertNextLink(bundle, DEFAULT_PAGE_SIZE);
|
assertNextLink(bundle, DEFAULT_PAGE_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void buildResponseBundle_withIncludeFilterAndFewerResultsThanPageSize_doesNotReturnNextLink() {
|
||||||
|
// setup
|
||||||
|
int includeResources = 4;
|
||||||
|
// we want the number of resources returned to be equal to the pagesize
|
||||||
|
List<IBaseResource> list = buildXPatientList(DEFAULT_PAGE_SIZE + 1 - includeResources);
|
||||||
|
|
||||||
|
ResponsePage.ResponsePageBuilder builder = new ResponsePage.ResponsePageBuilder();
|
||||||
|
builder.setIncludedResourceCount(includeResources);
|
||||||
|
|
||||||
|
ResponseBundleBuilder svc = new ResponseBundleBuilder(true);
|
||||||
|
|
||||||
|
SimpleBundleProvider provider = new SimpleBundleProvider() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponsePage.ResponsePageBuilder getResponsePageBuilder() {
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public List<IBaseResource> getResources(int theFrom, int theTo, @Nonnull ResponsePage.ResponsePageBuilder theResponsePageBuilder) {
|
||||||
|
List<IBaseResource> retList = list.subList(theFrom, Math.min(theTo, list.size()-1));
|
||||||
|
// our fake includes
|
||||||
|
for (int i = 0; i < includeResources; i++) {
|
||||||
|
retList.add(new Organization().setId("Organization/" + i));
|
||||||
|
}
|
||||||
|
return retList;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO - if null, it adds a next link
|
||||||
|
provider.setSize(DEFAULT_PAGE_SIZE);
|
||||||
|
|
||||||
|
// mocking
|
||||||
|
when(myServer.canStoreSearchResults()).thenReturn(true);
|
||||||
|
when(myServer.getPagingProvider()).thenReturn(myPagingProvider);
|
||||||
|
when(myPagingProvider.getDefaultPageSize()).thenReturn(DEFAULT_PAGE_SIZE);
|
||||||
|
|
||||||
|
ResponseBundleRequest req = buildResponseBundleRequest(provider);
|
||||||
|
|
||||||
|
// test
|
||||||
|
Bundle bundle = (Bundle) svc.buildResponseBundle(req);
|
||||||
|
|
||||||
|
// verify
|
||||||
|
assertEquals(1, bundle.getLink().size());
|
||||||
|
verifyBundle(bundle, RESOURCE_COUNT, DEFAULT_PAGE_SIZE -1, "A0", "A14");
|
||||||
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@ValueSource(booleans = {true, false})
|
@ValueSource(booleans = {true, false})
|
||||||
void testFilterNulls(boolean theCanStoreSearchResults) {
|
void testFilterNulls(boolean theCanStoreSearchResults) {
|
||||||
|
@ -423,8 +473,12 @@ class ResponseBundleBuilderTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<IBaseResource> buildPatientList() {
|
private List<IBaseResource> buildPatientList() {
|
||||||
|
return buildXPatientList(ResponseBundleBuilderTest.RESOURCE_COUNT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<IBaseResource> buildXPatientList(int theCount) {
|
||||||
List<IBaseResource> retval = new ArrayList<>();
|
List<IBaseResource> retval = new ArrayList<>();
|
||||||
for (int i = 0; i < ResponseBundleBuilderTest.RESOURCE_COUNT; ++i) {
|
for (int i = 0; i < theCount; ++i) {
|
||||||
Patient p = new Patient();
|
Patient p = new Patient();
|
||||||
p.setId("A" + i);
|
p.setId("A" + i);
|
||||||
p.setActive(true);
|
p.setActive(true);
|
||||||
|
@ -499,10 +553,9 @@ class ResponseBundleBuilderTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
public List<IBaseResource> getResources(int theFromIndex, int theToIndex, @Nonnull ResponsePage.ResponsePageBuilder theResponseBundleBuilder) {
|
||||||
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
|
|
||||||
getResourcesCalled = true;
|
getResourcesCalled = true;
|
||||||
return super.getResources(theFromIndex, theToIndex);
|
return super.getResources(theFromIndex, theToIndex, theResponseBundleBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emulate the behaviour of PersistedJpaBundleProvider where size() is only set after getResources() has been called
|
// Emulate the behaviour of PersistedJpaBundleProvider where size() is only set after getResources() has been called
|
||||||
|
|
Loading…
Reference in New Issue