Fix #116 - Preserve _include links across pages

This commit is contained in:
James Agnew 2015-04-02 12:49:45 -04:00
parent 317251ca4d
commit 94af375e4d
5 changed files with 109 additions and 33 deletions

View File

@ -206,11 +206,11 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
if (searchId != null) {
if (theOffset + numToReturn < theResult.size()) {
myBundle.getLinkNext().setValue(RestfulServerUtils.createPagingLink(theServerBase, searchId, theOffset + numToReturn, numToReturn, theResponseEncoding, thePrettyPrint));
myBundle.getLinkNext().setValue(RestfulServerUtils.createPagingLink(theIncludes, theServerBase, searchId, theOffset + numToReturn, numToReturn, theResponseEncoding, thePrettyPrint));
}
if (theOffset > 0) {
int start = Math.max(0, theOffset - limit);
myBundle.getLinkPrevious().setValue(RestfulServerUtils.createPagingLink(theServerBase, searchId, start, limit, theResponseEncoding, thePrettyPrint));
myBundle.getLinkPrevious().setValue(RestfulServerUtils.createPagingLink(theIncludes, theServerBase, searchId, start, limit, theResponseEncoding, thePrettyPrint));
}
}
}

View File

@ -45,6 +45,7 @@ import ca.uhn.fhir.context.ProvidedResourceScanner;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome.BaseIssue;
import ca.uhn.fhir.model.primitive.IdDt;
@ -456,8 +457,17 @@ public class RestfulServer extends HttpServlet {
boolean respondGzip = theRequest.isRespondGzip();
IVersionSpecificBundleFactory bundleFactory = myFhirContext.newBundleFactory();
bundleFactory.initializeBundleFromBundleProvider(this, resultList, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, start, count, thePagingAction,
null, IResource.WILDCARD_ALL_SET);
Set<Include> includes = new HashSet<Include>();
String[] reqIncludes = theRequest.getServletRequest().getParameterValues(Constants.PARAM_INCLUDE);
if (reqIncludes != null) {
for (String nextInclude : reqIncludes) {
includes.add(new Include(nextInclude));
}
}
bundleFactory.initializeBundleFromBundleProvider(this, resultList, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, start, count, thePagingAction,
null, includes);
Bundle bundle = bundleFactory.getDstu1Bundle();
if (bundle != null) {

View File

@ -30,6 +30,7 @@ import java.net.URLEncoder;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletOutputStream;
@ -44,6 +45,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.TagList;
@ -218,38 +220,51 @@ public class RestfulServerUtils {
return EncodingEnum.XML;
}
public static String createPagingLink(String theServerBase, String theSearchId, int theOffset, int theCount, EncodingEnum theResponseEncoding, boolean thePrettyPrint) {
StringBuilder b = new StringBuilder();
b.append(theServerBase);
b.append('?');
b.append(Constants.PARAM_PAGINGACTION);
b.append('=');
public static String createPagingLink(Set<Include> theIncludes, String theServerBase, String theSearchId, int theOffset, int theCount, EncodingEnum theResponseEncoding, boolean thePrettyPrint) {
try {
StringBuilder b = new StringBuilder();
b.append(theServerBase);
b.append('?');
b.append(Constants.PARAM_PAGINGACTION);
b.append('=');
b.append(URLEncoder.encode(theSearchId, "UTF-8"));
b.append('&');
b.append(Constants.PARAM_PAGINGOFFSET);
b.append('=');
b.append(theOffset);
b.append('&');
b.append(Constants.PARAM_COUNT);
b.append('=');
b.append(theCount);
if (theResponseEncoding != null) {
b.append('&');
b.append(Constants.PARAM_FORMAT);
b.append('=');
b.append(theResponseEncoding.getRequestContentType());
}
if (thePrettyPrint) {
b.append('&');
b.append(Constants.PARAM_PRETTY);
b.append('=');
b.append(Constants.PARAM_PRETTY_VALUE_TRUE);
}
if (theIncludes != null) {
for (Include nextInclude : theIncludes) {
if (isNotBlank(nextInclude.getValue())) {
b.append('&');
b.append(Constants.PARAM_INCLUDE);
b.append('=');
b.append(URLEncoder.encode(nextInclude.getValue(), "UTF-8"));
}
}
}
return b.toString();
} catch (UnsupportedEncodingException e) {
throw new Error("UTF-8 not supported", e);// should not happen
}
b.append('&');
b.append(Constants.PARAM_PAGINGOFFSET);
b.append('=');
b.append(theOffset);
b.append('&');
b.append(Constants.PARAM_COUNT);
b.append('=');
b.append(theCount);
if (theResponseEncoding != null) {
b.append('&');
b.append(Constants.PARAM_FORMAT);
b.append('=');
b.append(theResponseEncoding.getRequestContentType());
}
if (thePrettyPrint) {
b.append('&');
b.append(Constants.PARAM_PRETTY);
b.append('=');
b.append(Constants.PARAM_PRETTY_VALUE_TRUE);
}
return b.toString();
}
public static void addProfileToBundleEntry(FhirContext theContext, IResource theResource, String theServerBase) {

View File

@ -6,6 +6,7 @@ import static org.mockito.Mockito.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
@ -25,7 +26,9 @@ import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.rest.annotation.IncludeParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.util.PortUtil;
@ -151,6 +154,49 @@ public class PagingTest {
}
}
/**
* See #116
*/
@Test()
public void testPagingPreservesIncludes() throws Exception {
when(myPagingProvider.getDefaultPageSize()).thenReturn(5);
when(myPagingProvider.getMaximumPageSize()).thenReturn(9);
when(myPagingProvider.storeResultList(any(IBundleProvider.class))).thenReturn("ABCD");
when(myPagingProvider.retrieveResultList(eq("ABCD"))).thenReturn(ourBundleProvider);
String link;
String base = "http://localhost:" + ourPort;
{
HttpGet httpGet = new HttpGet(base + "/Patient?_count=2&_format=xml&_include=Patient.managingOrganization&_include=foo");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
Bundle bundle = ourContext.newXmlParser().parseBundle(responseContent);
assertEquals(2, bundle.getEntries().size());
assertEquals("0", bundle.getEntries().get(0).getId().getIdPart());
assertEquals("1", bundle.getEntries().get(1).getId().getIdPart());
assertEquals(base + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=2&" + Constants.PARAM_COUNT + "=2&_format=xml&_include=Patient.managingOrganization&_include=foo", bundle.getLinkNext().getValue());
assertNull(bundle.getLinkPrevious().getValue());
link = bundle.getLinkNext().getValue();
}
{
HttpGet httpGet = new HttpGet(link);
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
Bundle bundle = ourContext.newXmlParser().parseBundle(responseContent);
assertEquals(2, bundle.getEntries().size());
assertEquals("2", bundle.getEntries().get(0).getId().getIdPart());
assertEquals("3", bundle.getEntries().get(1).getId().getIdPart());
assertEquals(base + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=4&" + Constants.PARAM_COUNT + "=2&_format=xml&_include=Patient.managingOrganization&_include=foo", bundle.getLinkNext().getValue());
assertEquals(base + '/' + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=2&" + Constants.PARAM_COUNT + "=2&_format=xml&_include=Patient.managingOrganization&_include=foo", bundle.getLinkSelf().getValue());
assertEquals(base + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=0&" + Constants.PARAM_COUNT + "=2&_format=xml&_include=Patient.managingOrganization&_include=foo", bundle.getLinkPrevious().getValue());
}
}
@Test
public void testSearchSmallPages() throws Exception {
@ -243,7 +289,7 @@ public class PagingTest {
public static class DummyPatientResourceProvider implements IResourceProvider {
@Search
public IBundleProvider findPatient() {
public IBundleProvider findPatient(@IncludeParam Set<Include> theIncludes) {
return ourBundleProvider;
}

View File

@ -88,7 +88,12 @@
</action>
<action type="add">
Android JAR now includes servlet-API classes, as the project will not
work without them.
work without them. Thanks
</action>
<action type="fix" issue="116">
Requested _include values are preserved across paging links when the
server returns multiple pages. Thanks to Bill de Beaubien for
reporting!
</action>
</release>
<release version="0.9" date="2015-Mar-14">