More perf work

This commit is contained in:
James Agnew 2017-04-16 14:46:15 -04:00
parent 62ece72e6f
commit 217d9f8176
11 changed files with 830 additions and 451 deletions

View File

@ -696,7 +696,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
search.setResourceType(resourceName); search.setResourceType(resourceName);
search.setResourceId(theId); search.setResourceId(theId);
search.setSearchType(SearchTypeEnum.HISTORY); search.setSearchType(SearchTypeEnum.HISTORY);
search.setStatus(SearchStatusEnum.FINISHED);
if (theSince != null) { if (theSince != null) {
if (resourceName == null) { if (resourceName == null) {
search.setTotalCount(myResourceHistoryTableDao.countForAllResourceTypes(theSince)); search.setTotalCount(myResourceHistoryTableDao.countForAllResourceTypes(theSince));

View File

@ -593,87 +593,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return retVal; return retVal;
} }
// @Override
// public IBundleProvider everything(IIdType theId) {
// Search search = new Search();
// search.setUuid(UUID.randomUUID().toString());
// search.setCreated(new Date());
// myEntityManager.persist(search);
//
// List<SearchResult> results = new ArrayList<SearchResult>();
// if (theId != null) {
// Long pid = translateForcedIdToPid(theId);
// ResourceTable entity = myEntityManager.find(ResourceTable.class, pid);
// validateGivenIdIsAppropriateToRetrieveResource(theId, entity);
// SearchResult res = new SearchResult(search);
// res.setResourcePid(pid);
// results.add(res);
// } else {
// TypedQuery<Tuple> query = createSearchAllByTypeQuery();
// for (Tuple next : query.getResultList()) {
// SearchResult res = new SearchResult(search);
// res.setResourcePid(next.get(0, Long.class));
// results.add(res);
// }
// }
//
// int totalCount = results.size();
// mySearchResultDao.save(results);
// mySearchResultDao.flush();
//
// CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
//
// // Load _revincludes
// CriteriaQuery<Long> cq = builder.createQuery(Long.class);
// Root<ResourceLink> from = cq.from(ResourceLink.class);
// cq.select(from.get("mySourceResourcePid").as(Long.class));
//
// Subquery<Long> pidsSubquery = cq.subquery(Long.class);
// Root<SearchResult> pidsSubqueryFrom = pidsSubquery.from(SearchResult.class);
// pidsSubquery.select(pidsSubqueryFrom.get("myResourcePid").as(Long.class));
// pidsSubquery.where(pidsSubqueryFrom.get("mySearch").in(search));
//
// cq.where(from.get("myTargetResourceId").in(pidsSubquery));
// TypedQuery<Long> query = myEntityManager.createQuery(cq);
//
// results = new ArrayList<SearchResult>();
// for (Long next : query.getResultList()) {
// SearchResult res = new SearchResult(search);
// res.setResourcePid(next);
// results.add(res);
// }
//
// // Save _revincludes
// totalCount += results.size();
// mySearchResultDao.save(results);
// mySearchResultDao.flush();
//
// final int finalTotalCount = totalCount;
// return new IBundleProvider() {
//
// @Override
// public int size() {
// return finalTotalCount;
// }
//
// @Override
// public Integer preferredPageSize() {
// return null;
// }
//
// @Override
// public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
// // TODO Auto-generated method stub
// return null;
// }
//
// @Override
// public InstantDt getPublished() {
// // TODO Auto-generated method stub
// return null;
// }
// };
// }
@Override @Override
public <MT extends IBaseMetaType> MT metaGetOperation(Class<MT> theType, IIdType theId, RequestDetails theRequestDetails) { public <MT extends IBaseMetaType> MT metaGetOperation(Class<MT> theType, IIdType theId, RequestDetails theRequestDetails) {

View File

@ -1315,8 +1315,10 @@ public class SearchBuilder implements ISearchBuilder {
@Override @Override
public Long next() { public Long next() {
fetchNext(); fetchNext();
Validate.isTrue(myNext != NO_MORE, "No more elements"); Long retVal = myNext;
return myNext; myNext = null;
Validate.isTrue(retVal != NO_MORE, "No more elements");
return retVal;
} }
}; };

View File

@ -268,6 +268,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
@Override @Override
public Void call() throws Exception { public Void call() throws Exception {
StopWatch sw = new StopWatch();
try { try {
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager); TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW); txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW);
@ -277,14 +279,18 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
doSearch(); doSearch();
} }
}); });
ourLog.info("Completed search for {} resources in {}ms", mySyncedPids.size(), sw.getMillis());
} catch (Throwable t) { } catch (Throwable t) {
ourLog.error("Failed during search loading", t); ourLog.error("Failed during search loading after {}ms", t, sw.getMillis());
myUnsyncedPids.clear(); myUnsyncedPids.clear();
mySearch.setStatus(SearchStatusEnum.FAILED); mySearch.setStatus(SearchStatusEnum.FAILED);
String failureMessage = ExceptionUtils.getRootCauseMessage(t); String failureMessage = ExceptionUtils.getRootCauseMessage(t);
mySearch.setFailureMessage(failureMessage); mySearch.setFailureMessage(failureMessage);
saveSearch(); saveSearch();
} }
myIdToSearchTask.remove(mySearch.getUuid()); myIdToSearchTask.remove(mySearch.getUuid());

View File

@ -308,9 +308,6 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
@Test @Test
public void testCountParam() throws Exception { public void testCountParam() throws Exception {
// NB this does not get used- The paging provider has its own limits built in
myDaoConfig.setHardSearchLimit(100);
List<IBaseResource> resources = new ArrayList<IBaseResource>(); List<IBaseResource> resources = new ArrayList<IBaseResource>();
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
Organization org = new Organization(); Organization org = new Organization();
@ -1611,6 +1608,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
assertEquals(77, ids.size()); assertEquals(77, ids.size());
} }
@SuppressWarnings("unused")
@Test @Test
public void testFullTextSearch() throws RuntimeException, Exception { public void testFullTextSearch() throws RuntimeException, Exception {
Observation obs1 = new Observation(); Observation obs1 = new Observation();
@ -1920,6 +1918,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
} }
} }
@SuppressWarnings("unused")
@Test @Test
public void testMetadataSuperParamsAreIncluded() throws IOException { public void testMetadataSuperParamsAreIncluded() throws IOException {
StructureDefinition p = new StructureDefinition(); StructureDefinition p = new StructureDefinition();
@ -2622,6 +2621,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
} }
@SuppressWarnings("unused")
@Test @Test
public void testSearchPagingKeepsOldSearches() throws Exception { public void testSearchPagingKeepsOldSearches() throws Exception {
String methodName = "testSearchPagingKeepsOldSearches"; String methodName = "testSearchPagingKeepsOldSearches";

View File

@ -60,6 +60,23 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
myContext = theContext; myContext = theContext;
} }
private void addProfileIfNeeded(IRestfulServer<?> theServer, String theServerBase, IBaseResource nextRes) {
RuntimeResourceDefinition def = theServer.getFhirContext().getResourceDefinition(nextRes);
if (theServer.getAddProfileTag() == AddProfileTagEnum.ALWAYS || !def.isStandardType()) {
TagList tl = ResourceMetadataKeyEnum.TAG_LIST.get((IResource) nextRes);
if (tl == null) {
tl = new TagList();
ResourceMetadataKeyEnum.TAG_LIST.put((IResource) nextRes, tl);
}
RuntimeResourceDefinition nextDef = myContext.getResourceDefinition(nextRes);
String profile = nextDef.getResourceProfile(theServerBase);
if (isNotBlank(profile)) {
tl.add(new Tag(Tag.HL7_ORG_PROFILE_TAG, profile, null));
}
}
}
@Override @Override
public void addResourcesToBundle(List<IBaseResource> theResult, BundleTypeEnum theBundleType, String theServerBase, BundleInclusionRule theBundleInclusionRule, Set<Include> theIncludes) { public void addResourcesToBundle(List<IBaseResource> theResult, BundleTypeEnum theBundleType, String theServerBase, BundleInclusionRule theBundleInclusionRule, Set<Include> theIncludes) {
if (myBundle == null) { if (myBundle == null) {
@ -149,7 +166,7 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
myBundle.getAuthorName().setValue(theAuthor); myBundle.getAuthorName().setValue(theAuthor);
} }
if (myBundle.getUpdated().isEmpty() && isNotBlank(theLastUpdated.getValueAsString())) { if (theLastUpdated != null && myBundle.getUpdated().isEmpty() && isNotBlank(theLastUpdated.getValueAsString())) {
myBundle.getUpdated().setValueAsString(theLastUpdated.getValueAsString()); myBundle.getUpdated().setValueAsString(theLastUpdated.getValueAsString());
} }
@ -190,27 +207,31 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
int numToReturn; int numToReturn;
String searchId = null; String searchId = null;
List<IBaseResource> resourceList; List<IBaseResource> resourceList;
Integer numTotalResults = theResult.size();
if (theServer.getPagingProvider() == null) { if (theServer.getPagingProvider() == null) {
numToReturn = theResult.size(); numToReturn = numTotalResults;
resourceList = theResult.getResources(0, numToReturn); resourceList = theResult.getResources(0, numToReturn);
RestfulServerUtils.validateResourceListNotNull(resourceList); RestfulServerUtils.validateResourceListNotNull(resourceList);
} else { } else {
IPagingProvider pagingProvider = theServer.getPagingProvider(); IPagingProvider pagingProvider = theServer.getPagingProvider();
if (theLimit == null) { if (theLimit == null || theLimit.equals(Integer.valueOf(0))) {
numToReturn = pagingProvider.getDefaultPageSize(); numToReturn = pagingProvider.getDefaultPageSize();
} else { } else {
numToReturn = Math.min(pagingProvider.getMaximumPageSize(), theLimit); numToReturn = Math.min(pagingProvider.getMaximumPageSize(), theLimit);
} }
numToReturn = Math.min(numToReturn, theResult.size() - theOffset); if (numTotalResults != null) {
numToReturn = Math.min(numToReturn, numTotalResults - theOffset);
}
resourceList = theResult.getResources(theOffset, numToReturn + theOffset); resourceList = theResult.getResources(theOffset, numToReturn + theOffset);
RestfulServerUtils.validateResourceListNotNull(resourceList); RestfulServerUtils.validateResourceListNotNull(resourceList);
if (theSearchId != null) { if (theSearchId != null) {
searchId = theSearchId; searchId = theSearchId;
} else { } else {
if (theResult.size() > numToReturn) { if (numTotalResults == null || numTotalResults > numToReturn) {
searchId = pagingProvider.storeResultList(theResult); searchId = pagingProvider.storeResultList(theResult);
Validate.notNull(searchId, "Paging provider returned null searchId"); Validate.notNull(searchId, "Paging provider returned null searchId");
} }
@ -230,7 +251,7 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
addResourcesToBundle(new ArrayList<IBaseResource>(resourceList), theBundleType, theServerBase, theServer.getBundleInclusionRule(), theIncludes); addResourcesToBundle(new ArrayList<IBaseResource>(resourceList), theBundleType, theServerBase, theServer.getBundleInclusionRule(), theIncludes);
addRootPropertiesToBundle(null, theServerBase, theCompleteUrl, theResult.size(), theBundleType, theResult.getPublished()); addRootPropertiesToBundle(null, theServerBase, theCompleteUrl, numTotalResults, theBundleType, theResult.getPublished());
if (theServer.getPagingProvider() != null) { if (theServer.getPagingProvider() != null) {
int limit; int limit;
@ -238,7 +259,7 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
limit = Math.min(limit, theServer.getPagingProvider().getMaximumPageSize()); limit = Math.min(limit, theServer.getPagingProvider().getMaximumPageSize());
if (searchId != null) { if (searchId != null) {
if (theOffset + numToReturn < theResult.size()) { if (numTotalResults == null || theOffset + numToReturn < numTotalResults) {
myBundle.getLinkNext() myBundle.getLinkNext()
.setValue(RestfulServerUtils.createPagingLink(theIncludes, theServerBase, searchId, theOffset + numToReturn, numToReturn, theResponseEncoding, thePrettyPrint, theBundleType)); .setValue(RestfulServerUtils.createPagingLink(theIncludes, theServerBase, searchId, theOffset + numToReturn, numToReturn, theResponseEncoding, thePrettyPrint, theBundleType));
} }
@ -250,23 +271,6 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
} }
} }
private void addProfileIfNeeded(IRestfulServer<?> theServer, String theServerBase, IBaseResource nextRes) {
RuntimeResourceDefinition def = theServer.getFhirContext().getResourceDefinition(nextRes);
if (theServer.getAddProfileTag() == AddProfileTagEnum.ALWAYS || !def.isStandardType()) {
TagList tl = ResourceMetadataKeyEnum.TAG_LIST.get((IResource) nextRes);
if (tl == null) {
tl = new TagList();
ResourceMetadataKeyEnum.TAG_LIST.put((IResource) nextRes, tl);
}
RuntimeResourceDefinition nextDef = myContext.getResourceDefinition(nextRes);
String profile = nextDef.getResourceProfile(theServerBase);
if (isNotBlank(profile)) {
tl.add(new Tag(Tag.HL7_ORG_PROFILE_TAG, profile, null));
}
}
}
@Override @Override
public void initializeBundleFromResourceList(String theAuthor, List<? extends IBaseResource> theResult, String theServerBase, String theCompleteUrl, int theTotalResults, public void initializeBundleFromResourceList(String theAuthor, List<? extends IBaseResource> theResult, String theServerBase, String theCompleteUrl, int theTotalResults,
BundleTypeEnum theBundleType) { BundleTypeEnum theBundleType) {

View File

@ -0,0 +1,186 @@
package ca.uhn.fhir.rest.server;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import com.google.common.collect.Lists;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
public class SearchBundleProviderWithNoSizeDstu1Test {
private static CloseableHttpClient ourClient;
private static FhirContext ourCtx = FhirContext.forDstu1();
private static TokenAndListParam ourIdentifiers;
private static IBundleProvider ourLastBundleProvider;
private static String ourLastMethod;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchBundleProviderWithNoSizeDstu1Test.class);
private static int ourPort;
private static Server ourServer;
@Before
public void before() {
ourLastMethod = null;
ourIdentifiers = null;
}
@Test
public void testBundleProviderReturnsNoSize() throws Exception {
Bundle respBundle;
ourLastBundleProvider = mock(IBundleProvider.class);
when(ourLastBundleProvider.size()).thenReturn(null);
when(ourLastBundleProvider.getResources(any(int.class), any(int.class))).then(new Answer<List<IBaseResource>>() {
@Override
public List<IBaseResource> answer(InvocationOnMock theInvocation) throws Throwable {
int from =(Integer)theInvocation.getArguments()[0];
int to =(Integer)theInvocation.getArguments()[1];
ArrayList<IBaseResource> retVal = Lists.newArrayList();
for (int i = from; i < to; i++) {
Patient p = new Patient();
p.setId(Integer.toString(i));
retVal.add(p);
}
return retVal;
}});
HttpGet httpGet;
CloseableHttpResponse status = null;
StringDt linkNext;
try {
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_format=json");
status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("searchAll", ourLastMethod);
respBundle = ourCtx.newJsonParser().parseBundle(responseContent);
assertEquals(10, respBundle.getEntries().size());
assertEquals("Patient/0", respBundle.getEntries().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
linkNext = respBundle.getLinkNext();
assertNotNull(linkNext.getValue());
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
}
when(ourLastBundleProvider.size()).thenReturn(25);
try {
httpGet = new HttpGet(linkNext.getValue());
status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("searchAll", ourLastMethod);
respBundle = ourCtx.newJsonParser().parseBundle(responseContent);
assertEquals(10, respBundle.getEntries().size());
assertEquals("Patient/10", respBundle.getEntries().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
linkNext = respBundle.getLinkNext();
assertNotNull(linkNext.getValue());
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
}
try {
httpGet = new HttpGet(linkNext.getValue());
status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("searchAll", ourLastMethod);
respBundle = ourCtx.newJsonParser().parseBundle(responseContent);
assertEquals(5, respBundle.getEntries().size());
assertEquals("Patient/20", respBundle.getEntries().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
linkNext = respBundle.getLinkNext();
assertNull(linkNext.getValue());
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
}
}
@AfterClass
public static void afterClassClearContext() throws Exception {
ourServer.stop();
TestUtil.clearAllStaticFieldsForUnitTest();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(ourCtx);
servlet.setPagingProvider(new FifoMemoryPagingProvider(10));
servlet.setResourceProviders(patientProvider);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
public static class DummyPatientResourceProvider implements IResourceProvider {
@Override
public Class<? extends IBaseResource> getResourceType() {
return Patient.class;
}
@Search()
public IBundleProvider searchAll() {
ourLastMethod = "searchAll";
return ourLastBundleProvider;
}
}
}

View File

@ -306,8 +306,9 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
int numToReturn; int numToReturn;
String searchId = null; String searchId = null;
List<IBaseResource> resourceList; List<IBaseResource> resourceList;
Integer numTotalResults = theResult.size();
if (theServer.getPagingProvider() == null) { if (theServer.getPagingProvider() == null) {
numToReturn = theResult.size(); numToReturn = numTotalResults;
if (numToReturn > 0) { if (numToReturn > 0) {
resourceList = theResult.getResources(0, numToReturn); resourceList = theResult.getResources(0, numToReturn);
} else { } else {
@ -317,13 +318,16 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
} else { } else {
IPagingProvider pagingProvider = theServer.getPagingProvider(); IPagingProvider pagingProvider = theServer.getPagingProvider();
if (theLimit == null) { if (theLimit == null || theLimit.equals(Integer.valueOf(0))) {
numToReturn = pagingProvider.getDefaultPageSize(); numToReturn = pagingProvider.getDefaultPageSize();
} else { } else {
numToReturn = Math.min(pagingProvider.getMaximumPageSize(), theLimit); numToReturn = Math.min(pagingProvider.getMaximumPageSize(), theLimit);
} }
numToReturn = Math.min(numToReturn, theResult.size() - theOffset); if (numTotalResults != null) {
numToReturn = Math.min(numToReturn, numTotalResults - theOffset);
}
if (numToReturn > 0) { if (numToReturn > 0) {
resourceList = theResult.getResources(theOffset, numToReturn + theOffset); resourceList = theResult.getResources(theOffset, numToReturn + theOffset);
} else { } else {
@ -334,7 +338,7 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
if (theSearchId != null) { if (theSearchId != null) {
searchId = theSearchId; searchId = theSearchId;
} else { } else {
if (theResult.size() > numToReturn) { if (numTotalResults == null || numTotalResults > numToReturn) {
searchId = pagingProvider.storeResultList(theResult); searchId = pagingProvider.storeResultList(theResult);
Validate.notNull(searchId, "Paging provider returned null searchId"); Validate.notNull(searchId, "Paging provider returned null searchId");
} }
@ -350,7 +354,7 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
} }
addResourcesToBundle(new ArrayList<IBaseResource>(resourceList), theBundleType, theServerBase, theServer.getBundleInclusionRule(), theIncludes); addResourcesToBundle(new ArrayList<IBaseResource>(resourceList), theBundleType, theServerBase, theServer.getBundleInclusionRule(), theIncludes);
addRootPropertiesToBundle(null, theServerBase, theCompleteUrl, theResult.size(), theBundleType, theResult.getPublished()); addRootPropertiesToBundle(null, theServerBase, theCompleteUrl, numTotalResults, theBundleType, theResult.getPublished());
if (theServer.getPagingProvider() != null) { if (theServer.getPagingProvider() != null) {
int limit; int limit;
@ -358,7 +362,7 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
limit = Math.min(limit, theServer.getPagingProvider().getMaximumPageSize()); limit = Math.min(limit, theServer.getPagingProvider().getMaximumPageSize());
if (searchId != null) { if (searchId != null) {
if (theOffset + numToReturn < theResult.size()) { if (numTotalResults == null || theOffset + numToReturn < numTotalResults) {
myBundle.addLink().setRelation(Constants.LINK_NEXT) myBundle.addLink().setRelation(Constants.LINK_NEXT)
.setUrl(RestfulServerUtils.createPagingLink(theIncludes, theServerBase, searchId, theOffset + numToReturn, numToReturn, theResponseEncoding, thePrettyPrint, theBundleType)); .setUrl(RestfulServerUtils.createPagingLink(theIncludes, theServerBase, searchId, theOffset + numToReturn, numToReturn, theResponseEncoding, thePrettyPrint, theBundleType));
} }

View File

@ -0,0 +1,186 @@
package ca.uhn.fhir.rest.server;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import com.google.common.collect.Lists;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Bundle.Link;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
public class SearchBundleProviderWithNoSizeDstu2Test {
private static CloseableHttpClient ourClient;
private static FhirContext ourCtx = FhirContext.forDstu2();
private static TokenAndListParam ourIdentifiers;
private static IBundleProvider ourLastBundleProvider;
private static String ourLastMethod;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchBundleProviderWithNoSizeDstu2Test.class);
private static int ourPort;
private static Server ourServer;
@Before
public void before() {
ourLastMethod = null;
ourIdentifiers = null;
}
@Test
public void testBundleProviderReturnsNoSize() throws Exception {
Bundle respBundle;
ourLastBundleProvider = mock(IBundleProvider.class);
when(ourLastBundleProvider.size()).thenReturn(null);
when(ourLastBundleProvider.getResources(any(int.class), any(int.class))).then(new Answer<List<IBaseResource>>() {
@Override
public List<IBaseResource> answer(InvocationOnMock theInvocation) throws Throwable {
int from =(Integer)theInvocation.getArguments()[0];
int to =(Integer)theInvocation.getArguments()[1];
ArrayList<IBaseResource> retVal = Lists.newArrayList();
for (int i = from; i < to; i++) {
Patient p = new Patient();
p.setId(Integer.toString(i));
retVal.add(p);
}
return retVal;
}});
HttpGet httpGet;
CloseableHttpResponse status = null;
Link linkNext;
try {
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_format=json");
status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("searchAll", ourLastMethod);
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, responseContent);
assertEquals(10, respBundle.getEntry().size());
assertEquals("Patient/0", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
linkNext = respBundle.getLink("next");
assertNotNull(linkNext);
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
}
when(ourLastBundleProvider.size()).thenReturn(25);
try {
httpGet = new HttpGet(linkNext.getUrl());
status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("searchAll", ourLastMethod);
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, responseContent);
assertEquals(10, respBundle.getEntry().size());
assertEquals("Patient/10", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
linkNext = respBundle.getLink("next");
assertNotNull(linkNext);
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
}
try {
httpGet = new HttpGet(linkNext.getUrl());
status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("searchAll", ourLastMethod);
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, responseContent);
assertEquals(5, respBundle.getEntry().size());
assertEquals("Patient/20", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
linkNext = respBundle.getLink("next");
assertNull(linkNext);
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
}
}
@AfterClass
public static void afterClassClearContext() throws Exception {
ourServer.stop();
TestUtil.clearAllStaticFieldsForUnitTest();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(ourCtx);
servlet.setPagingProvider(new FifoMemoryPagingProvider(10));
servlet.setResourceProviders(patientProvider);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
public static class DummyPatientResourceProvider implements IResourceProvider {
@Override
public Class<? extends IBaseResource> getResourceType() {
return Patient.class;
}
@Search()
public IBundleProvider searchAll() {
ourLastMethod = "searchAll";
return ourLastBundleProvider;
}
}
}

View File

@ -10,7 +10,7 @@ package org.hl7.fhir.dstu3.hapi.rest.server;
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -45,388 +45,392 @@ import ca.uhn.fhir.util.ResourceReferenceInfo;
public class Dstu3BundleFactory implements IVersionSpecificBundleFactory { public class Dstu3BundleFactory implements IVersionSpecificBundleFactory {
private Bundle myBundle; private Bundle myBundle;
private FhirContext myContext; private FhirContext myContext;
private String myBase; private String myBase;
public Dstu3BundleFactory(FhirContext theContext) { public Dstu3BundleFactory(FhirContext theContext) {
myContext = theContext; myContext = theContext;
} }
private void addResourcesForSearch(List<? extends IBaseResource> theResult) { private void addResourcesForSearch(List<? extends IBaseResource> theResult) {
List<IBaseResource> includedResources = new ArrayList<IBaseResource>(); List<IBaseResource> includedResources = new ArrayList<IBaseResource>();
Set<IIdType> addedResourceIds = new HashSet<IIdType>(); Set<IIdType> addedResourceIds = new HashSet<IIdType>();
for (IBaseResource next : theResult) { for (IBaseResource next : theResult) {
if (next.getIdElement().isEmpty() == false) { if (next.getIdElement().isEmpty() == false) {
addedResourceIds.add(next.getIdElement()); addedResourceIds.add(next.getIdElement());
} }
} }
for (IBaseResource nextBaseRes : theResult) { for (IBaseResource nextBaseRes : theResult) {
Resource next = (Resource) nextBaseRes; Resource next = (Resource) nextBaseRes;
Set<String> containedIds = new HashSet<String>(); Set<String> containedIds = new HashSet<String>();
if (next instanceof DomainResource) { if (next instanceof DomainResource) {
for (Resource nextContained : ((DomainResource)next).getContained()) { for (Resource nextContained : ((DomainResource) next).getContained()) {
if (nextContained.getIdElement().isEmpty() == false) { if (nextContained.getIdElement().isEmpty() == false) {
containedIds.add(nextContained.getIdElement().getValue()); containedIds.add(nextContained.getIdElement().getValue());
} }
} }
} }
List<IBaseReference> references = myContext.newTerser().getAllPopulatedChildElementsOfType(next, IBaseReference.class);
do {
List<IAnyResource> addedResourcesThisPass = new ArrayList<IAnyResource>();
for (IBaseReference nextRef : references) { List<IBaseReference> references = myContext.newTerser().getAllPopulatedChildElementsOfType(next, IBaseReference.class);
IAnyResource nextRes = (IAnyResource) nextRef.getResource(); do {
if (nextRes != null) { List<IAnyResource> addedResourcesThisPass = new ArrayList<IAnyResource>();
if (nextRes.getIdElement().hasIdPart()) {
if (containedIds.contains(nextRes.getIdElement().getValue())) {
// Don't add contained IDs as top level resources
continue;
}
IIdType id = nextRes.getIdElement(); for (IBaseReference nextRef : references) {
if (id.hasResourceType() == false) { IAnyResource nextRes = (IAnyResource) nextRef.getResource();
String resName = myContext.getResourceDefinition(nextRes).getName(); if (nextRes != null) {
id = id.withResourceType(resName); if (nextRes.getIdElement().hasIdPart()) {
} if (containedIds.contains(nextRes.getIdElement().getValue())) {
// Don't add contained IDs as top level resources
continue;
}
if (!addedResourceIds.contains(id)) { IIdType id = nextRes.getIdElement();
addedResourceIds.add(id); if (id.hasResourceType() == false) {
addedResourcesThisPass.add(nextRes); String resName = myContext.getResourceDefinition(nextRes).getName();
} id = id.withResourceType(resName);
}
} if (!addedResourceIds.contains(id)) {
} addedResourceIds.add(id);
} addedResourcesThisPass.add(nextRes);
}
// Linked resources may themselves have linked resources }
references = new ArrayList<IBaseReference>(); }
for (IAnyResource iResource : addedResourcesThisPass) { }
List<IBaseReference> newReferences = myContext.newTerser().getAllPopulatedChildElementsOfType(iResource, IBaseReference.class);
references.addAll(newReferences);
}
includedResources.addAll(addedResourcesThisPass); // Linked resources may themselves have linked resources
references = new ArrayList<IBaseReference>();
for (IAnyResource iResource : addedResourcesThisPass) {
List<IBaseReference> newReferences = myContext.newTerser().getAllPopulatedChildElementsOfType(iResource, IBaseReference.class);
references.addAll(newReferences);
}
} while (references.isEmpty() == false); includedResources.addAll(addedResourcesThisPass);
BundleEntryComponent entry = myBundle.addEntry().setResource(next); } while (references.isEmpty() == false);
if (next.getIdElement().hasBaseUrl()) {
entry.setFullUrl(next.getId());
}
String httpVerb = ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get(next); BundleEntryComponent entry = myBundle.addEntry().setResource(next);
if (httpVerb != null) { if (next.getIdElement().hasBaseUrl()) {
entry.getRequest().getMethodElement().setValueAsString(httpVerb); entry.setFullUrl(next.getId());
entry.getRequest().getUrlElement().setValue(next.getId()); }
}
}
/* String httpVerb = ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get(next);
* Actually add the resources to the bundle if (httpVerb != null) {
*/ entry.getRequest().getMethodElement().setValueAsString(httpVerb);
for (IBaseResource next : includedResources) { entry.getRequest().getUrlElement().setValue(next.getId());
BundleEntryComponent entry = myBundle.addEntry(); }
entry.setResource((Resource) next).getSearch().setMode(SearchEntryMode.INCLUDE); }
if (next.getIdElement().hasBaseUrl()) {
entry.setFullUrl(next.getIdElement().getValue());
}
}
}
@Override /*
public void addResourcesToBundle(List<IBaseResource> theResult, BundleTypeEnum theBundleType, String theServerBase, BundleInclusionRule theBundleInclusionRule, Set<Include> theIncludes) { * Actually add the resources to the bundle
if (myBundle == null) { */
myBundle = new Bundle(); for (IBaseResource next : includedResources) {
} BundleEntryComponent entry = myBundle.addEntry();
entry.setResource((Resource) next).getSearch().setMode(SearchEntryMode.INCLUDE);
if (next.getIdElement().hasBaseUrl()) {
entry.setFullUrl(next.getIdElement().getValue());
}
}
}
List<IAnyResource> includedResources = new ArrayList<IAnyResource>(); @Override
Set<IIdType> addedResourceIds = new HashSet<IIdType>(); public void addResourcesToBundle(List<IBaseResource> theResult, BundleTypeEnum theBundleType, String theServerBase, BundleInclusionRule theBundleInclusionRule, Set<Include> theIncludes) {
if (myBundle == null) {
myBundle = new Bundle();
}
for (IBaseResource next : theResult) { List<IAnyResource> includedResources = new ArrayList<IAnyResource>();
if (next.getIdElement().isEmpty() == false) { Set<IIdType> addedResourceIds = new HashSet<IIdType>();
addedResourceIds.add(next.getIdElement());
}
}
for (IBaseResource next : theResult) { for (IBaseResource next : theResult) {
if (next.getIdElement().isEmpty() == false) {
addedResourceIds.add(next.getIdElement());
}
}
Set<String> containedIds = new HashSet<String>(); for (IBaseResource next : theResult) {
if (next instanceof DomainResource) {
for (Resource nextContained : ((DomainResource)next).getContained()) {
if (isNotBlank(nextContained.getId())) {
containedIds.add(nextContained.getId());
}
}
}
List<ResourceReferenceInfo> references = myContext.newTerser().getAllResourceReferences(next); Set<String> containedIds = new HashSet<String>();
do {
List<IAnyResource> addedResourcesThisPass = new ArrayList<IAnyResource>();
for (ResourceReferenceInfo nextRefInfo : references) { if (next instanceof DomainResource) {
if (!theBundleInclusionRule.shouldIncludeReferencedResource(nextRefInfo, theIncludes)) { for (Resource nextContained : ((DomainResource) next).getContained()) {
continue; if (isNotBlank(nextContained.getId())) {
} containedIds.add(nextContained.getId());
}
}
}
IAnyResource nextRes = (IAnyResource) nextRefInfo.getResourceReference().getResource(); List<ResourceReferenceInfo> references = myContext.newTerser().getAllResourceReferences(next);
if (nextRes != null) { do {
if (nextRes.getIdElement().hasIdPart()) { List<IAnyResource> addedResourcesThisPass = new ArrayList<IAnyResource>();
if (containedIds.contains(nextRes.getIdElement().getValue())) {
// Don't add contained IDs as top level resources
continue;
}
IIdType id = nextRes.getIdElement(); for (ResourceReferenceInfo nextRefInfo : references) {
if (id.hasResourceType() == false) { if (!theBundleInclusionRule.shouldIncludeReferencedResource(nextRefInfo, theIncludes)) {
String resName = myContext.getResourceDefinition(nextRes).getName(); continue;
id = id.withResourceType(resName); }
}
if (!addedResourceIds.contains(id)) { IAnyResource nextRes = (IAnyResource) nextRefInfo.getResourceReference().getResource();
addedResourceIds.add(id); if (nextRes != null) {
addedResourcesThisPass.add(nextRes); if (nextRes.getIdElement().hasIdPart()) {
} if (containedIds.contains(nextRes.getIdElement().getValue())) {
// Don't add contained IDs as top level resources
continue;
}
} IIdType id = nextRes.getIdElement();
} if (id.hasResourceType() == false) {
} String resName = myContext.getResourceDefinition(nextRes).getName();
id = id.withResourceType(resName);
}
includedResources.addAll(addedResourcesThisPass); if (!addedResourceIds.contains(id)) {
addedResourceIds.add(id);
addedResourcesThisPass.add(nextRes);
}
// Linked resources may themselves have linked resources }
references = new ArrayList<ResourceReferenceInfo>(); }
for (IAnyResource iResource : addedResourcesThisPass) { }
List<ResourceReferenceInfo> newReferences = myContext.newTerser().getAllResourceReferences(iResource);
references.addAll(newReferences);
}
} while (references.isEmpty() == false);
BundleEntryComponent entry = myBundle.addEntry().setResource((Resource) next); includedResources.addAll(addedResourcesThisPass);
Resource nextAsResource = (Resource)next;
IIdType id = populateBundleEntryFullUrl(next, entry);
String httpVerb = ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get(nextAsResource);
if (httpVerb != null) {
entry.getRequest().getMethodElement().setValueAsString(httpVerb);
if (id != null) {
entry.getRequest().setUrl(id.getValue());
}
}
String searchMode = ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(nextAsResource);
if (searchMode != null) {
entry.getSearch().getModeElement().setValueAsString(searchMode);
}
}
/* // Linked resources may themselves have linked resources
* Actually add the resources to the bundle references = new ArrayList<ResourceReferenceInfo>();
*/ for (IAnyResource iResource : addedResourcesThisPass) {
for (IAnyResource next : includedResources) { List<ResourceReferenceInfo> newReferences = myContext.newTerser().getAllResourceReferences(iResource);
BundleEntryComponent entry = myBundle.addEntry(); references.addAll(newReferences);
entry.setResource((Resource) next).getSearch().setMode(SearchEntryMode.INCLUDE); }
populateBundleEntryFullUrl(next, entry); } while (references.isEmpty() == false);
}
} BundleEntryComponent entry = myBundle.addEntry().setResource((Resource) next);
Resource nextAsResource = (Resource) next;
IIdType id = populateBundleEntryFullUrl(next, entry);
String httpVerb = ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get(nextAsResource);
if (httpVerb != null) {
entry.getRequest().getMethodElement().setValueAsString(httpVerb);
if (id != null) {
entry.getRequest().setUrl(id.getValue());
}
}
private IIdType populateBundleEntryFullUrl(IBaseResource next, BundleEntryComponent entry) { String searchMode = ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(nextAsResource);
IIdType idElement = null; if (searchMode != null) {
if (next.getIdElement().hasBaseUrl()) { entry.getSearch().getModeElement().setValueAsString(searchMode);
idElement = next.getIdElement(); }
entry.setFullUrl(idElement.toVersionless().getValue()); }
} else {
if (isNotBlank(myBase) && next.getIdElement().hasIdPart()) {
idElement = next.getIdElement();
idElement = idElement.withServerBase(myBase, myContext.getResourceDefinition(next).getName());
entry.setFullUrl(idElement.toVersionless().getValue());
}
}
return idElement;
}
@Override /*
public void addRootPropertiesToBundle(String theAuthor, String theServerBase, String theCompleteUrl, Integer theTotalResults, BundleTypeEnum theBundleType, IPrimitiveType<Date> theLastUpdated) { * Actually add the resources to the bundle
*/
for (IAnyResource next : includedResources) {
BundleEntryComponent entry = myBundle.addEntry();
entry.setResource((Resource) next).getSearch().setMode(SearchEntryMode.INCLUDE);
populateBundleEntryFullUrl(next, entry);
}
myBase = theServerBase; }
if (myBundle.getIdElement().isEmpty()) {
myBundle.setId(UUID.randomUUID().toString());
}
if (myBundle.getMeta().getLastUpdated() == null && theLastUpdated != null) { private IIdType populateBundleEntryFullUrl(IBaseResource next, BundleEntryComponent entry) {
myBundle.getMeta().getLastUpdatedElement().setValueAsString(theLastUpdated.getValueAsString()); IIdType idElement = null;
} if (next.getIdElement().hasBaseUrl()) {
idElement = next.getIdElement();
entry.setFullUrl(idElement.toVersionless().getValue());
} else {
if (isNotBlank(myBase) && next.getIdElement().hasIdPart()) {
idElement = next.getIdElement();
idElement = idElement.withServerBase(myBase, myContext.getResourceDefinition(next).getName());
entry.setFullUrl(idElement.toVersionless().getValue());
}
}
return idElement;
}
if (!hasLink(Constants.LINK_SELF, myBundle) && isNotBlank(theCompleteUrl)) { @Override
myBundle.addLink().setRelation("self").setUrl(theCompleteUrl); public void addRootPropertiesToBundle(String theAuthor, String theServerBase, String theCompleteUrl, Integer theTotalResults, BundleTypeEnum theBundleType, IPrimitiveType<Date> theLastUpdated) {
}
if (myBundle.getTypeElement().isEmpty() && theBundleType != null) { myBase = theServerBase;
myBundle.getTypeElement().setValueAsString(theBundleType.getCode());
}
if (myBundle.getTotalElement().isEmpty() && theTotalResults != null) { if (myBundle.getIdElement().isEmpty()) {
myBundle.getTotalElement().setValue(theTotalResults); myBundle.setId(UUID.randomUUID().toString());
} }
}
@Override if (myBundle.getMeta().getLastUpdated() == null && theLastUpdated != null) {
public ca.uhn.fhir.model.api.Bundle getDstu1Bundle() { myBundle.getMeta().getLastUpdatedElement().setValueAsString(theLastUpdated.getValueAsString());
return null; }
}
@Override if (!hasLink(Constants.LINK_SELF, myBundle) && isNotBlank(theCompleteUrl)) {
public IBaseResource getResourceBundle() { myBundle.addLink().setRelation("self").setUrl(theCompleteUrl);
return myBundle; }
}
private boolean hasLink(String theLinkType, Bundle theBundle) { if (myBundle.getTypeElement().isEmpty() && theBundleType != null) {
for (BundleLinkComponent next : theBundle.getLink()) { myBundle.getTypeElement().setValueAsString(theBundleType.getCode());
if (theLinkType.equals(next.getRelation())) { }
return true;
}
}
return false;
}
@Override if (myBundle.getTotalElement().isEmpty() && theTotalResults != null) {
public void initializeBundleFromBundleProvider(IRestfulServer<?> theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, myBundle.getTotalElement().setValue(theTotalResults);
boolean thePrettyPrint, int theOffset, Integer theLimit, String theSearchId, BundleTypeEnum theBundleType, Set<Include> theIncludes) { }
myBase = theServerBase; }
int numToReturn;
String searchId = null;
List<IBaseResource> resourceList;
if (theServer.getPagingProvider() == null) {
numToReturn = theResult.size();
if (numToReturn > 0) {
resourceList = theResult.getResources(0, numToReturn);
} else {
resourceList = Collections.emptyList();
}
RestfulServerUtils.validateResourceListNotNull(resourceList);
} else { @Override
IPagingProvider pagingProvider = theServer.getPagingProvider(); public ca.uhn.fhir.model.api.Bundle getDstu1Bundle() {
if (theLimit == null) { return null;
numToReturn = pagingProvider.getDefaultPageSize(); }
} else {
numToReturn = Math.min(pagingProvider.getMaximumPageSize(), theLimit);
}
numToReturn = Math.min(numToReturn, theResult.size() - theOffset); @Override
if (numToReturn > 0) { public IBaseResource getResourceBundle() {
resourceList = theResult.getResources(theOffset, numToReturn + theOffset); return myBundle;
} else { }
resourceList = Collections.emptyList();
}
RestfulServerUtils.validateResourceListNotNull(resourceList);
if (theSearchId != null) { private boolean hasLink(String theLinkType, Bundle theBundle) {
searchId = theSearchId; for (BundleLinkComponent next : theBundle.getLink()) {
} else { if (theLinkType.equals(next.getRelation())) {
if (theResult.size() > numToReturn) { return true;
searchId = pagingProvider.storeResultList(theResult); }
Validate.notNull(searchId, "Paging provider returned null searchId"); }
} return false;
} }
}
for (IBaseResource next : resourceList) { @Override
if (next.getIdElement() == null || next.getIdElement().isEmpty()) { public void initializeBundleFromBundleProvider(IRestfulServer<?> theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl,
if (!(next instanceof BaseOperationOutcome)) { boolean thePrettyPrint, int theOffset, Integer theLimit, String theSearchId, BundleTypeEnum theBundleType, Set<Include> theIncludes) {
throw new InternalErrorException("Server method returned resource of type[" + next.getClass().getSimpleName() + "] with no ID specified (IResource#setId(IdDt) must be called)"); myBase = theServerBase;
}
}
}
addResourcesToBundle(new ArrayList<IBaseResource>(resourceList), theBundleType, theServerBase, theServer.getBundleInclusionRule(), theIncludes); int numToReturn;
addRootPropertiesToBundle(null, theServerBase, theCompleteUrl, theResult.size(), theBundleType, theResult.getPublished()); String searchId = null;
List<IBaseResource> resourceList;
Integer numTotalResults = theResult.size();
if (theServer.getPagingProvider() == null) {
numToReturn = numTotalResults;
if (numToReturn > 0) {
resourceList = theResult.getResources(0, numToReturn);
} else {
resourceList = Collections.emptyList();
}
RestfulServerUtils.validateResourceListNotNull(resourceList);
if (theServer.getPagingProvider() != null) { } else {
int limit; IPagingProvider pagingProvider = theServer.getPagingProvider();
limit = theLimit != null ? theLimit : theServer.getPagingProvider().getDefaultPageSize(); if (theLimit == null || theLimit.equals(Integer.valueOf(0))) {
limit = Math.min(limit, theServer.getPagingProvider().getMaximumPageSize()); numToReturn = pagingProvider.getDefaultPageSize();
} else {
numToReturn = Math.min(pagingProvider.getMaximumPageSize(), theLimit);
}
if (searchId != null) { if (numTotalResults != null) {
if (theOffset + numToReturn < theResult.size()) { numToReturn = Math.min(numToReturn, numTotalResults - theOffset);
myBundle.addLink().setRelation(Constants.LINK_NEXT) }
.setUrl(RestfulServerUtils.createPagingLink(theIncludes, theServerBase, searchId, theOffset + numToReturn, numToReturn, theResponseEncoding, thePrettyPrint, theBundleType));
}
if (theOffset > 0) {
int start = Math.max(0, theOffset - limit);
myBundle.addLink().setRelation(Constants.LINK_PREVIOUS)
.setUrl(RestfulServerUtils.createPagingLink(theIncludes, theServerBase, searchId, start, limit, theResponseEncoding, thePrettyPrint, theBundleType));
}
}
}
}
@Override if (numToReturn > 0) {
public void initializeBundleFromResourceList(String theAuthor, List<? extends IBaseResource> theResources, String theServerBase, String theCompleteUrl, int theTotalResults, resourceList = theResult.getResources(theOffset, numToReturn + theOffset);
BundleTypeEnum theBundleType) { } else {
myBundle = new Bundle(); resourceList = Collections.emptyList();
}
RestfulServerUtils.validateResourceListNotNull(resourceList);
myBundle.setId(UUID.randomUUID().toString()); if (theSearchId != null) {
searchId = theSearchId;
} else {
if (numTotalResults == null || numTotalResults > numToReturn) {
searchId = pagingProvider.storeResultList(theResult);
Validate.notNull(searchId, "Paging provider returned null searchId");
}
}
}
myBundle.getMeta().setLastUpdated(new Date()); for (IBaseResource next : resourceList) {
if (next.getIdElement() == null || next.getIdElement().isEmpty()) {
if (!(next instanceof BaseOperationOutcome)) {
throw new InternalErrorException("Server method returned resource of type[" + next.getClass().getSimpleName() + "] with no ID specified (IResource#setId(IdDt) must be called)");
}
}
}
myBundle.addLink().setRelation(Constants.LINK_FHIR_BASE).setUrl(theServerBase); addResourcesToBundle(new ArrayList<IBaseResource>(resourceList), theBundleType, theServerBase, theServer.getBundleInclusionRule(), theIncludes);
myBundle.addLink().setRelation(Constants.LINK_SELF).setUrl(theCompleteUrl); addRootPropertiesToBundle(null, theServerBase, theCompleteUrl, theResult.size(), theBundleType, theResult.getPublished());
myBundle.getTypeElement().setValueAsString(theBundleType.getCode());
if (theBundleType.equals(BundleTypeEnum.TRANSACTION)) { if (theServer.getPagingProvider() != null) {
for (IBaseResource nextBaseRes : theResources) { int limit;
Resource next = (Resource) nextBaseRes; limit = theLimit != null ? theLimit : theServer.getPagingProvider().getDefaultPageSize();
BundleEntryComponent nextEntry = myBundle.addEntry(); limit = Math.min(limit, theServer.getPagingProvider().getMaximumPageSize());
nextEntry.setResource(next); if (searchId != null) {
if (next.getIdElement().isEmpty()) { if (numTotalResults == null || theOffset + numToReturn < numTotalResults) {
nextEntry.getRequest().setMethod(HTTPVerb.POST); myBundle.addLink().setRelation(Constants.LINK_NEXT)
} else { .setUrl(RestfulServerUtils.createPagingLink(theIncludes, theServerBase, searchId, theOffset + numToReturn, numToReturn, theResponseEncoding, thePrettyPrint, theBundleType));
nextEntry.getRequest().setMethod(HTTPVerb.PUT); }
if (next.getIdElement().isAbsolute()) { if (theOffset > 0) {
nextEntry.getRequest().setUrl(next.getId()); int start = Math.max(0, theOffset - limit);
} else { myBundle.addLink().setRelation(Constants.LINK_PREVIOUS)
String resourceType = myContext.getResourceDefinition(next).getName(); .setUrl(RestfulServerUtils.createPagingLink(theIncludes, theServerBase, searchId, start, limit, theResponseEncoding, thePrettyPrint, theBundleType));
nextEntry.getRequest().setUrl(new IdType(theServerBase, resourceType, next.getIdElement().getIdPart(), next.getIdElement().getVersionIdPart()).getValue()); }
} }
} }
} }
} else {
addResourcesForSearch(theResources);
}
myBundle.getTotalElement().setValue(theTotalResults); @Override
} public void initializeBundleFromResourceList(String theAuthor, List<? extends IBaseResource> theResources, String theServerBase, String theCompleteUrl, int theTotalResults,
BundleTypeEnum theBundleType) {
myBundle = new Bundle();
@Override myBundle.setId(UUID.randomUUID().toString());
public void initializeWithBundleResource(IBaseResource theBundle) {
myBundle = (Bundle) theBundle;
}
@Override myBundle.getMeta().setLastUpdated(new Date());
public List<IBaseResource> toListOfResources() {
ArrayList<IBaseResource> retVal = new ArrayList<IBaseResource>(); myBundle.addLink().setRelation(Constants.LINK_FHIR_BASE).setUrl(theServerBase);
for (BundleEntryComponent next : myBundle.getEntry()) { myBundle.addLink().setRelation(Constants.LINK_SELF).setUrl(theCompleteUrl);
if (next.getResource() != null) { myBundle.getTypeElement().setValueAsString(theBundleType.getCode());
retVal.add(next.getResource());
} else if (next.getResponse().getLocationElement().isEmpty() == false) { if (theBundleType.equals(BundleTypeEnum.TRANSACTION)) {
IdType id = new IdType(next.getResponse().getLocation()); for (IBaseResource nextBaseRes : theResources) {
String resourceType = id.getResourceType(); Resource next = (Resource) nextBaseRes;
if (isNotBlank(resourceType)) { BundleEntryComponent nextEntry = myBundle.addEntry();
IAnyResource res = (IAnyResource) myContext.getResourceDefinition(resourceType).newInstance();
res.setId(id); nextEntry.setResource(next);
retVal.add(res); if (next.getIdElement().isEmpty()) {
} nextEntry.getRequest().setMethod(HTTPVerb.POST);
} } else {
} nextEntry.getRequest().setMethod(HTTPVerb.PUT);
return retVal; if (next.getIdElement().isAbsolute()) {
} nextEntry.getRequest().setUrl(next.getId());
} else {
String resourceType = myContext.getResourceDefinition(next).getName();
nextEntry.getRequest().setUrl(new IdType(theServerBase, resourceType, next.getIdElement().getIdPart(), next.getIdElement().getVersionIdPart()).getValue());
}
}
}
} else {
addResourcesForSearch(theResources);
}
myBundle.getTotalElement().setValue(theTotalResults);
}
@Override
public void initializeWithBundleResource(IBaseResource theBundle) {
myBundle = (Bundle) theBundle;
}
@Override
public List<IBaseResource> toListOfResources() {
ArrayList<IBaseResource> retVal = new ArrayList<IBaseResource>();
for (BundleEntryComponent next : myBundle.getEntry()) {
if (next.getResource() != null) {
retVal.add(next.getResource());
} else if (next.getResponse().getLocationElement().isEmpty() == false) {
IdType id = new IdType(next.getResponse().getLocation());
String resourceType = id.getResourceType();
if (isNotBlank(resourceType)) {
IAnyResource res = (IAnyResource) myContext.getResourceDefinition(resourceType).newInstance();
res.setId(id);
retVal.add(res);
}
}
}
return retVal;
}
} }

View File

@ -1,8 +1,7 @@
package ca.uhn.fhir.rest.server; package ca.uhn.fhir.rest.server;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.*;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.*;
import static org.mockito.Mockito.when;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
@ -19,6 +18,8 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.dstu3.model.Bundle.BundleLinkComponent;
import org.hl7.fhir.dstu3.model.HumanName; import org.hl7.fhir.dstu3.model.HumanName;
import org.hl7.fhir.dstu3.model.OperationOutcome; import org.hl7.fhir.dstu3.model.OperationOutcome;
import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.dstu3.model.Patient;
@ -27,6 +28,10 @@ import org.junit.AfterClass;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import com.google.common.collect.Lists;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.RequiredParam; import ca.uhn.fhir.rest.annotation.RequiredParam;
@ -58,16 +63,78 @@ public class SearchBundleProviderWithNoSizeDstu3Test {
Bundle respBundle; Bundle respBundle;
ourLastBundleProvider = mock(IBundleProvider.class); ourLastBundleProvider = mock(IBundleProvider.class);
when(ourLastBundleProvider.size()).thenReturn(-1); when(ourLastBundleProvider.size()).thenReturn(null);
when(ourLastBundleProvider.getResources(any(int.class), any(int.class))).then(new Answer<List<IBaseResource>>() {
@Override
public List<IBaseResource> answer(InvocationOnMock theInvocation) throws Throwable {
int from =(Integer)theInvocation.getArguments()[0];
int to =(Integer)theInvocation.getArguments()[1];
ArrayList<IBaseResource> retVal = Lists.newArrayList();
for (int i = from; i < to; i++) {
Patient p = new Patient();
p.setId(Integer.toString(i));
retVal.add(p);
}
return retVal;
}});
HttpGet httpGet;
CloseableHttpResponse status = null;
BundleLinkComponent linkNext;
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_format=json");
CloseableHttpResponse status = ourClient.execute(httpGet);
try { try {
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_format=json");
status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent); ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode()); assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("searchAll", ourLastMethod); assertEquals("searchAll", ourLastMethod);
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, responseContent); respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, responseContent);
assertEquals(10, respBundle.getEntry().size());
assertEquals("Patient/0", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
linkNext = respBundle.getLink("next");
assertNotNull(linkNext);
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
}
when(ourLastBundleProvider.size()).thenReturn(25);
try {
httpGet = new HttpGet(linkNext.getUrl());
status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("searchAll", ourLastMethod);
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, responseContent);
assertEquals(10, respBundle.getEntry().size());
assertEquals("Patient/10", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
linkNext = respBundle.getLink("next");
assertNotNull(linkNext);
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
}
try {
httpGet = new HttpGet(linkNext.getUrl());
status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("searchAll", ourLastMethod);
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, responseContent);
assertEquals(5, respBundle.getEntry().size());
assertEquals("Patient/20", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
linkNext = respBundle.getLink("next");
assertNull(linkNext);
} finally { } finally {
IOUtils.closeQuietly(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent());
} }