Fix #750 - Elements are not preserved in page requests
This commit is contained in:
parent
2b4a492870
commit
59f4177a59
|
@ -43,7 +43,7 @@ public abstract class BaseParser implements IParser {
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseParser.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseParser.class);
|
||||||
|
|
||||||
private ContainedResources myContainedResources;
|
private ContainedResources myContainedResources;
|
||||||
|
private boolean myEncodeElementsAppliesToChildResourcesOnly;
|
||||||
private FhirContext myContext;
|
private FhirContext myContext;
|
||||||
private Set<String> myDontEncodeElements;
|
private Set<String> myDontEncodeElements;
|
||||||
private boolean myDontEncodeElementsIncludesStars;
|
private boolean myDontEncodeElementsIncludesStars;
|
||||||
|
@ -556,6 +556,16 @@ public abstract class BaseParser implements IParser {
|
||||||
&& theIncludedResource == false;
|
&& theIncludedResource == false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEncodeElementsAppliesToChildResourcesOnly() {
|
||||||
|
return myEncodeElementsAppliesToChildResourcesOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEncodeElementsAppliesToChildResourcesOnly(boolean theEncodeElementsAppliesToChildResourcesOnly) {
|
||||||
|
myEncodeElementsAppliesToChildResourcesOnly = theEncodeElementsAppliesToChildResourcesOnly;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isOmitResourceId() {
|
public boolean isOmitResourceId() {
|
||||||
return myOmitResourceId;
|
return myOmitResourceId;
|
||||||
|
@ -1039,7 +1049,13 @@ public abstract class BaseParser implements IParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkIfParentShouldBeEncodedAndBuildPath(StringBuilder thePathBuilder, boolean theStarPass) {
|
private boolean checkIfParentShouldBeEncodedAndBuildPath(StringBuilder thePathBuilder, boolean theStarPass) {
|
||||||
return checkIfPathMatchesForEncoding(thePathBuilder, theStarPass, myEncodeElementsAppliesToResourceTypes, myEncodeElements, true);
|
Set<String> encodeElements = myEncodeElements;
|
||||||
|
if (encodeElements != null && encodeElements.isEmpty() == false) {
|
||||||
|
if (isEncodeElementsAppliesToChildResourcesOnly() && !mySubResource) {
|
||||||
|
encodeElements = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return checkIfPathMatchesForEncoding(thePathBuilder, theStarPass, myEncodeElementsAppliesToResourceTypes, encodeElements, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkIfParentShouldNotBeEncodedAndBuildPath(StringBuilder thePathBuilder, boolean theStarPass) {
|
private boolean checkIfParentShouldNotBeEncodedAndBuildPath(StringBuilder thePathBuilder, boolean theStarPass) {
|
||||||
|
@ -1058,6 +1074,9 @@ public abstract class BaseParser implements IParser {
|
||||||
} else {
|
} else {
|
||||||
thePathBuilder.append(myResDef.getName());
|
thePathBuilder.append(myResDef.getName());
|
||||||
}
|
}
|
||||||
|
if (theElements == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (theElements.contains(thePathBuilder.toString())) {
|
if (theElements.contains(thePathBuilder.toString())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -206,6 +206,22 @@ public interface IParser {
|
||||||
*/
|
*/
|
||||||
void setEncodeElements(Set<String> theEncodeElements);
|
void setEncodeElements(Set<String> theEncodeElements);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set to <code>true</code> (default is false), the values supplied
|
||||||
|
* to {@link #setEncodeElements(Set)} will not be applied to the root
|
||||||
|
* resource (typically a Bundle), but will be applied to any sub-resources
|
||||||
|
* contained within it (i.e. search result resources in that bundle)
|
||||||
|
*/
|
||||||
|
void setEncodeElementsAppliesToChildResourcesOnly(boolean theEncodeElementsAppliesToChildResourcesOnly);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set to <code>true</code> (default is false), the values supplied
|
||||||
|
* to {@link #setEncodeElements(Set)} will not be applied to the root
|
||||||
|
* resource (typically a Bundle), but will be applied to any sub-resources
|
||||||
|
* contained within it (i.e. search result resources in that bundle)
|
||||||
|
*/
|
||||||
|
boolean isEncodeElementsAppliesToChildResourcesOnly();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If provided, tells the parse which resource types to apply {@link #setEncodeElements(Set) encode elements} to. Any
|
* If provided, tells the parse which resource types to apply {@link #setEncodeElements(Set) encode elements} to. Any
|
||||||
* resource types not specified here will be encoded completely, with no elements excluded.
|
* resource types not specified here will be encoded completely, with no elements excluded.
|
||||||
|
|
|
@ -19,11 +19,12 @@ public class BinaryUtil {
|
||||||
public static IBaseReference getSecurityContext(FhirContext theCtx, IBaseBinary theBinary) {
|
public static IBaseReference getSecurityContext(FhirContext theCtx, IBaseBinary theBinary) {
|
||||||
RuntimeResourceDefinition def = theCtx.getResourceDefinition("Binary");
|
RuntimeResourceDefinition def = theCtx.getResourceDefinition("Binary");
|
||||||
BaseRuntimeChildDefinition child = def.getChildByName("securityContext");
|
BaseRuntimeChildDefinition child = def.getChildByName("securityContext");
|
||||||
|
|
||||||
List<IBase> values = child.getAccessor().getValues(theBinary);
|
|
||||||
IBaseReference retVal = null;
|
IBaseReference retVal = null;
|
||||||
if (values.size() > 0) {
|
if (child != null) {
|
||||||
retVal = (IBaseReference) values.get(0);
|
List<IBase> values = child.getAccessor().getValues(theBinary);
|
||||||
|
if (values.size() > 0) {
|
||||||
|
retVal = (IBaseReference) values.get(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,10 +86,41 @@ public class RestfulServerUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (elements != null && elements.size() > 0) {
|
if (elements != null && elements.size() > 0) {
|
||||||
Set<String> newElements = new HashSet<String>();
|
Set<String> newElements = new HashSet<>();
|
||||||
for (String next : elements) {
|
for (String next : elements) {
|
||||||
newElements.add("*." + next);
|
newElements.add("*." + next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We try to be smart about what the user is asking for
|
||||||
|
* when they include an _elements parameter. If we're responding
|
||||||
|
* to something that returns a Bundle (e.g. a search) we assume
|
||||||
|
* the elements don't apply to the Bundle itself, unless
|
||||||
|
* the client has explicitly scoped the Bundle
|
||||||
|
* (i.e. with Bundle.total or something like that)
|
||||||
|
*/
|
||||||
|
switch (theRequestDetails.getRestOperationType()) {
|
||||||
|
case SEARCH_SYSTEM:
|
||||||
|
case SEARCH_TYPE:
|
||||||
|
case HISTORY_SYSTEM:
|
||||||
|
case HISTORY_TYPE:
|
||||||
|
case HISTORY_INSTANCE:
|
||||||
|
case GET_PAGE:
|
||||||
|
boolean haveExplicitBundleElement = false;
|
||||||
|
for (String next : newElements) {
|
||||||
|
if (next.startsWith("Bundle.")) {
|
||||||
|
haveExplicitBundleElement = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!haveExplicitBundleElement) {
|
||||||
|
parser.setEncodeElementsAppliesToChildResourcesOnly(true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
parser.setEncodeElements(newElements);
|
parser.setEncodeElements(newElements);
|
||||||
parser.setEncodeElementsAppliesToResourceTypes(elementsAppliesTo);
|
parser.setEncodeElementsAppliesToResourceTypes(elementsAppliesTo);
|
||||||
}
|
}
|
||||||
|
@ -147,6 +178,19 @@ public class RestfulServerUtils {
|
||||||
b.append(theBundleType.getCode());
|
b.append(theBundleType.getCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String paramName = Constants.PARAM_ELEMENTS;
|
||||||
|
String[] params = theRequestParameters.get(paramName);
|
||||||
|
if (params != null) {
|
||||||
|
for (String nextValue : params) {
|
||||||
|
if (isNotBlank(nextValue)) {
|
||||||
|
b.append('&');
|
||||||
|
b.append(paramName);
|
||||||
|
b.append('=');
|
||||||
|
b.append(UrlUtil.escape(nextValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return b.toString();
|
return b.toString();
|
||||||
} catch (UnsupportedEncodingException e) {
|
} catch (UnsupportedEncodingException e) {
|
||||||
throw new Error("UTF-8 not supported", e);// should not happen
|
throw new Error("UTF-8 not supported", e);// should not happen
|
||||||
|
@ -587,9 +631,11 @@ public class RestfulServerUtils {
|
||||||
response.addHeader(Constants.HEADER_CONTENT_DISPOSITION, "Attachment;");
|
response.addHeader(Constants.HEADER_CONTENT_DISPOSITION, "Attachment;");
|
||||||
|
|
||||||
IBaseReference securityContext = BinaryUtil.getSecurityContext(theServer.getFhirContext(), bin);
|
IBaseReference securityContext = BinaryUtil.getSecurityContext(theServer.getFhirContext(), bin);
|
||||||
String securityContextRef = securityContext.getReferenceElement().getValue();
|
if (securityContext != null) {
|
||||||
if (isNotBlank(securityContextRef)) {
|
String securityContextRef = securityContext.getReferenceElement().getValue();
|
||||||
response.addHeader(Constants.HEADER_X_SECURITY_CONTEXT, securityContextRef);
|
if (isNotBlank(securityContextRef)) {
|
||||||
|
response.addHeader(Constants.HEADER_X_SECURITY_CONTEXT, securityContextRef);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.sendAttachmentResponse(bin, theStausCode, contentType);
|
return response.sendAttachmentResponse(bin, theStausCode, contentType);
|
||||||
|
|
|
@ -332,7 +332,7 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
|
||||||
}
|
}
|
||||||
|
|
||||||
bundleFactory.addRootPropertiesToBundle(theResult.getUuid(), serverBase, theLinkSelf, linkPrev, linkNext, theResult.size(), theBundleType, theResult.getPublished());
|
bundleFactory.addRootPropertiesToBundle(theResult.getUuid(), serverBase, theLinkSelf, linkPrev, linkNext, theResult.size(), theBundleType, theResult.getPublished());
|
||||||
bundleFactory.addResourcesToBundle(new ArrayList<IBaseResource>(resourceList), theBundleType, serverBase, theServer.getBundleInclusionRule(), theIncludes);
|
bundleFactory.addResourcesToBundle(new ArrayList<>(resourceList), theBundleType, serverBase, theServer.getBundleInclusionRule(), theIncludes);
|
||||||
|
|
||||||
if (theServer.getPagingProvider() != null) {
|
if (theServer.getPagingProvider() != null) {
|
||||||
int limit;
|
int limit;
|
||||||
|
|
|
@ -1499,7 +1499,7 @@ public class JsonParserDstu2_1Test {
|
||||||
|
|
||||||
String val = ourCtx.newJsonParser().encodeResourceToString(patient);
|
String val = ourCtx.newJsonParser().encodeResourceToString(patient);
|
||||||
|
|
||||||
String expected = "{\"resourceType\":\"Binary\",\"id\":\"11\",\"contentType\":\"foo\",\"content\":\"AQIDBA==\"}";
|
String expected = "{\"resourceType\":\"Binary\",\"id\":\"11\",\"meta\":{\"versionId\":\"22\"},\"contentType\":\"foo\",\"content\":\"AQIDBA==\"}";
|
||||||
ourLog.info("Expected: {}", expected);
|
ourLog.info("Expected: {}", expected);
|
||||||
ourLog.info("Actual : {}", val);
|
ourLog.info("Actual : {}", val);
|
||||||
assertEquals(expected, val);
|
assertEquals(expected, val);
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,32 +1,5 @@
|
||||||
package ca.uhn.fhir.rest.server;
|
package ca.uhn.fhir.rest.server;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
|
||||||
import static org.hamcrest.Matchers.not;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
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.ClientProtocolException;
|
|
||||||
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.hl7.fhir.r4.model.*;
|
|
||||||
import org.junit.*;
|
|
||||||
|
|
||||||
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;
|
||||||
import ca.uhn.fhir.rest.annotation.Search;
|
import ca.uhn.fhir.rest.annotation.Search;
|
||||||
|
@ -38,15 +11,46 @@ import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
||||||
import ca.uhn.fhir.rest.gclient.StringClientParam;
|
import ca.uhn.fhir.rest.gclient.StringClientParam;
|
||||||
import ca.uhn.fhir.rest.param.TokenAndListParam;
|
import ca.uhn.fhir.rest.param.TokenAndListParam;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.util.*;
|
import ca.uhn.fhir.util.PortUtil;
|
||||||
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
|
import ca.uhn.fhir.util.UrlUtil;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.http.client.ClientProtocolException;
|
||||||
|
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.hl7.fhir.r4.model.Bundle;
|
||||||
|
import org.hl7.fhir.r4.model.HumanName;
|
||||||
|
import org.hl7.fhir.r4.model.OperationOutcome;
|
||||||
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.not;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
public class SearchR4Test {
|
public class SearchR4Test {
|
||||||
|
|
||||||
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchR4Test.class);
|
||||||
private static CloseableHttpClient ourClient;
|
private static CloseableHttpClient ourClient;
|
||||||
private static FhirContext ourCtx = FhirContext.forR4();
|
private static FhirContext ourCtx = FhirContext.forR4();
|
||||||
private static TokenAndListParam ourIdentifiers;
|
private static TokenAndListParam ourIdentifiers;
|
||||||
private static String ourLastMethod;
|
private static String ourLastMethod;
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchR4Test.class);
|
|
||||||
private static int ourPort;
|
private static int ourPort;
|
||||||
|
|
||||||
private static Server ourServer;
|
private static Server ourServer;
|
||||||
|
@ -57,74 +61,63 @@ public class SearchR4Test {
|
||||||
ourIdentifiers = null;
|
ourIdentifiers = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
private Bundle executeAndReturnLinkNext(HttpGet httpGet, EncodingEnum theExpectEncoding) throws IOException, ClientProtocolException {
|
||||||
public void testSearchNormal() throws Exception {
|
|
||||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar");
|
|
||||||
CloseableHttpResponse status = ourClient.execute(httpGet);
|
CloseableHttpResponse status = ourClient.execute(httpGet);
|
||||||
|
Bundle bundle;
|
||||||
try {
|
try {
|
||||||
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());
|
||||||
|
EncodingEnum ct = EncodingEnum.forContentType(status.getEntity().getContentType().getValue().replaceAll(";.*", "").trim());
|
||||||
assertEquals("search", ourLastMethod);
|
assertEquals(theExpectEncoding, ct);
|
||||||
|
bundle = ct.newParser(ourCtx).parseResource(Bundle.class, responseContent);
|
||||||
assertEquals("foo", ourIdentifiers.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getSystem());
|
assertEquals(10, bundle.getEntry().size());
|
||||||
assertEquals("bar", ourIdentifiers.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getValue());
|
String linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
||||||
|
assertNotNull(linkNext);
|
||||||
} finally {
|
} finally {
|
||||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||||
}
|
}
|
||||||
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSearchWithInvalidChain() throws Exception {
|
public void testPagingPreservesElements() throws Exception {
|
||||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier.chain=foo%7Cbar");
|
|
||||||
CloseableHttpResponse status = ourClient.execute(httpGet);
|
|
||||||
try {
|
|
||||||
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
|
||||||
ourLog.info(responseContent);
|
|
||||||
assertEquals(400, status.getStatusLine().getStatusCode());
|
|
||||||
|
|
||||||
OperationOutcome oo = (OperationOutcome) ourCtx.newJsonParser().parseResource(responseContent);
|
|
||||||
assertEquals(
|
|
||||||
"Invalid search parameter \"identifier.chain\". Parameter contains a chain (.chain) and chains are not supported for this parameter (chaining is only allowed on reference parameters)",
|
|
||||||
oo.getIssueFirstRep().getDiagnostics());
|
|
||||||
} finally {
|
|
||||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPagingPreservesEncodingJson() throws Exception {
|
|
||||||
HttpGet httpGet;
|
HttpGet httpGet;
|
||||||
String linkNext;
|
String linkNext;
|
||||||
Bundle bundle;
|
Bundle bundle;
|
||||||
|
String linkSelf;
|
||||||
|
|
||||||
// Initial search
|
// Initial search
|
||||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=json");
|
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_elements=name");
|
||||||
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
|
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
|
||||||
|
assertThat(toJson(bundle), not(containsString("active")));
|
||||||
|
linkSelf = bundle.getLink(Constants.LINK_SELF).getUrl();
|
||||||
|
assertThat(linkSelf, containsString("_elements=name"));
|
||||||
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
||||||
assertThat(linkNext, containsString("_format=json"));
|
assertThat(linkNext, containsString("_elements=name"));
|
||||||
|
|
||||||
|
ourLog.info(toJson(bundle));
|
||||||
|
|
||||||
// Fetch the next page
|
// Fetch the next page
|
||||||
httpGet = new HttpGet(linkNext);
|
httpGet = new HttpGet(linkNext);
|
||||||
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
|
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
|
||||||
|
assertThat(toJson(bundle), not(containsString("active")));
|
||||||
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
||||||
assertThat(linkNext, containsString("_format=json"));
|
assertThat(linkNext, containsString("_elements=name"));
|
||||||
|
|
||||||
// Fetch the next page
|
// Fetch the next page
|
||||||
httpGet = new HttpGet(linkNext);
|
httpGet = new HttpGet(linkNext);
|
||||||
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
|
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
|
||||||
|
assertThat(toJson(bundle), not(containsString("active")));
|
||||||
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
||||||
assertThat(linkNext, containsString("_format=json"));
|
assertThat(linkNext, containsString("_elements=name"));
|
||||||
|
|
||||||
// Fetch the next page
|
// Fetch the next page
|
||||||
httpGet = new HttpGet(linkNext);
|
httpGet = new HttpGet(linkNext);
|
||||||
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
|
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
|
||||||
|
assertThat(toJson(bundle), not(containsString("active")));
|
||||||
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
||||||
assertThat(linkNext, containsString("_format=json"));
|
assertThat(linkNext, containsString("_elements=name"));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,34 +154,35 @@ public class SearchR4Test {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPagingPreservesEncodingXml() throws Exception {
|
public void testPagingPreservesEncodingJson() throws Exception {
|
||||||
HttpGet httpGet;
|
HttpGet httpGet;
|
||||||
String linkNext;
|
String linkNext;
|
||||||
Bundle bundle;
|
Bundle bundle;
|
||||||
|
|
||||||
// Initial search
|
// Initial search
|
||||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=xml");
|
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=json");
|
||||||
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
|
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
|
||||||
|
assertThat(toJson(bundle), containsString("active"));
|
||||||
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
||||||
assertThat(linkNext, containsString("_format=xml"));
|
assertThat(linkNext, containsString("_format=json"));
|
||||||
|
|
||||||
// Fetch the next page
|
// Fetch the next page
|
||||||
httpGet = new HttpGet(linkNext);
|
httpGet = new HttpGet(linkNext);
|
||||||
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
|
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
|
||||||
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
||||||
assertThat(linkNext, containsString("_format=xml"));
|
assertThat(linkNext, containsString("_format=json"));
|
||||||
|
|
||||||
// Fetch the next page
|
// Fetch the next page
|
||||||
httpGet = new HttpGet(linkNext);
|
httpGet = new HttpGet(linkNext);
|
||||||
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
|
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
|
||||||
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
||||||
assertThat(linkNext, containsString("_format=xml"));
|
assertThat(linkNext, containsString("_format=json"));
|
||||||
|
|
||||||
// Fetch the next page
|
// Fetch the next page
|
||||||
httpGet = new HttpGet(linkNext);
|
httpGet = new HttpGet(linkNext);
|
||||||
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
|
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
|
||||||
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
||||||
assertThat(linkNext, containsString("_format=xml"));
|
assertThat(linkNext, containsString("_format=json"));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,26 +254,76 @@ public class SearchR4Test {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Bundle executeAndReturnLinkNext(HttpGet httpGet, EncodingEnum theExpectEncoding) throws IOException, ClientProtocolException {
|
@Test
|
||||||
CloseableHttpResponse status = ourClient.execute(httpGet);
|
public void testPagingPreservesEncodingXml() throws Exception {
|
||||||
|
HttpGet httpGet;
|
||||||
|
String linkNext;
|
||||||
Bundle bundle;
|
Bundle bundle;
|
||||||
|
|
||||||
|
// Initial search
|
||||||
|
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=xml");
|
||||||
|
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
|
||||||
|
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
||||||
|
assertThat(linkNext, containsString("_format=xml"));
|
||||||
|
|
||||||
|
// Fetch the next page
|
||||||
|
httpGet = new HttpGet(linkNext);
|
||||||
|
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
|
||||||
|
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
||||||
|
assertThat(linkNext, containsString("_format=xml"));
|
||||||
|
|
||||||
|
// Fetch the next page
|
||||||
|
httpGet = new HttpGet(linkNext);
|
||||||
|
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
|
||||||
|
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
||||||
|
assertThat(linkNext, containsString("_format=xml"));
|
||||||
|
|
||||||
|
// Fetch the next page
|
||||||
|
httpGet = new HttpGet(linkNext);
|
||||||
|
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
|
||||||
|
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
||||||
|
assertThat(linkNext, containsString("_format=xml"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchNormal() throws Exception {
|
||||||
|
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar");
|
||||||
|
CloseableHttpResponse status = ourClient.execute(httpGet);
|
||||||
try {
|
try {
|
||||||
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());
|
||||||
EncodingEnum ct = EncodingEnum.forContentType(status.getEntity().getContentType().getValue().replaceAll(";.*", "").trim());
|
|
||||||
assertEquals(theExpectEncoding, ct);
|
assertEquals("search", ourLastMethod);
|
||||||
bundle = ct.newParser(ourCtx).parseResource(Bundle.class, responseContent);
|
|
||||||
assertEquals(10, bundle.getEntry().size());
|
assertEquals("foo", ourIdentifiers.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getSystem());
|
||||||
String linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
assertEquals("bar", ourIdentifiers.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getValue());
|
||||||
assertNotNull(linkNext);
|
|
||||||
} finally {
|
} finally {
|
||||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||||
}
|
}
|
||||||
return bundle;
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchWithInvalidChain() throws Exception {
|
||||||
|
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier.chain=foo%7Cbar");
|
||||||
|
CloseableHttpResponse status = ourClient.execute(httpGet);
|
||||||
|
try {
|
||||||
|
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
|
ourLog.info(responseContent);
|
||||||
|
assertEquals(400, status.getStatusLine().getStatusCode());
|
||||||
|
|
||||||
|
OperationOutcome oo = (OperationOutcome) ourCtx.newJsonParser().parseResource(responseContent);
|
||||||
|
assertEquals(
|
||||||
|
"Invalid search parameter \"identifier.chain\". Parameter contains a chain (.chain) and chains are not supported for this parameter (chaining is only allowed on reference parameters)",
|
||||||
|
oo.getIssueFirstRep().getDiagnostics());
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSearchWithPostAndInvalidParameters() throws Exception {
|
public void testSearchWithPostAndInvalidParameters() throws Exception {
|
||||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort);
|
IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort);
|
||||||
|
@ -293,14 +337,14 @@ public class SearchR4Test {
|
||||||
client.registerInterceptor(interceptor);
|
client.registerInterceptor(interceptor);
|
||||||
try {
|
try {
|
||||||
client
|
client
|
||||||
.search()
|
.search()
|
||||||
.forResource(Patient.class)
|
.forResource(Patient.class)
|
||||||
.where(new StringClientParam("foo").matches().value("bar"))
|
.where(new StringClientParam("foo").matches().value("bar"))
|
||||||
.prettyPrint()
|
.prettyPrint()
|
||||||
.usingStyle(SearchStyleEnum.POST)
|
.usingStyle(SearchStyleEnum.POST)
|
||||||
.returnBundle(org.hl7.fhir.r4.model.Bundle.class)
|
.returnBundle(org.hl7.fhir.r4.model.Bundle.class)
|
||||||
.encodedJson()
|
.encodedJson()
|
||||||
.execute();
|
.execute();
|
||||||
fail();
|
fail();
|
||||||
} catch (InvalidRequestException e) {
|
} catch (InvalidRequestException e) {
|
||||||
assertThat(e.getMessage(), containsString("Invalid request: The FHIR endpoint on this server does not know how to handle POST operation[Patient/_search] with parameters [[_pretty, foo]]"));
|
assertThat(e.getMessage(), containsString("Invalid request: The FHIR endpoint on this server does not know how to handle POST operation[Patient/_search] with parameters [[_pretty, foo]]"));
|
||||||
|
@ -308,6 +352,10 @@ public class SearchR4Test {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String toJson(Bundle theBundle) {
|
||||||
|
return ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(theBundle);
|
||||||
|
}
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
public static void afterClassClearContext() throws Exception {
|
public static void afterClassClearContext() throws Exception {
|
||||||
ourServer.stop();
|
ourServer.stop();
|
||||||
|
@ -349,16 +397,17 @@ public class SearchR4Test {
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
@Search()
|
@Search()
|
||||||
public List search(
|
public List search(
|
||||||
@RequiredParam(name = Patient.SP_IDENTIFIER) TokenAndListParam theIdentifiers) {
|
@RequiredParam(name = Patient.SP_IDENTIFIER) TokenAndListParam theIdentifiers) {
|
||||||
ourLastMethod = "search";
|
ourLastMethod = "search";
|
||||||
ourIdentifiers = theIdentifiers;
|
ourIdentifiers = theIdentifiers;
|
||||||
ArrayList<Patient> retVal = new ArrayList<Patient>();
|
ArrayList<Patient> retVal = new ArrayList<Patient>();
|
||||||
|
|
||||||
for (int i = 0; i < 200; i++) {
|
for (int i = 0; i < 200; i++) {
|
||||||
Patient patient = new Patient();
|
Patient patient = new Patient();
|
||||||
patient.addName(new HumanName().setFamily("FAMILY"));
|
patient.addName(new HumanName().setFamily("FAMILY"));
|
||||||
|
patient.setActive(true);
|
||||||
patient.getIdElement().setValue("Patient/" + i);
|
patient.getIdElement().setValue("Patient/" + i);
|
||||||
retVal.add((Patient) patient);
|
retVal.add(patient);
|
||||||
}
|
}
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,11 +175,26 @@
|
||||||
was not encoded correctly. Thanks to Malcolm McRoberts for the pull
|
was not encoded correctly. Thanks to Malcolm McRoberts for the pull
|
||||||
request with fix and test case!
|
request with fix and test case!
|
||||||
</action>
|
</action>
|
||||||
|
<action type="add">
|
||||||
|
Bundle resources did not have their version encoded when serializing
|
||||||
|
in FHIR resource (XML/JSON) format.
|
||||||
|
</action>
|
||||||
<action type="add">
|
<action type="add">
|
||||||
The Binary resource endpoint now supports the `X-Security-Context` header when
|
The Binary resource endpoint now supports the `X-Security-Context` header when
|
||||||
reading or writing Binary contents using their native Content-Type (i.e exchanging
|
reading or writing Binary contents using their native Content-Type (i.e exchanging
|
||||||
the raw binary with the server, as opposed to exchanging a FHIR resource).
|
the raw binary with the server, as opposed to exchanging a FHIR resource).
|
||||||
</action>
|
</action>
|
||||||
|
<action type="fix">
|
||||||
|
When paging through multiple pages of search results, if the
|
||||||
|
client had requested a subset of resources to be returned using the
|
||||||
|
<![CDATA[<code>_elements</code>]]> parameter, the elements list
|
||||||
|
was lost after the first page of results.
|
||||||
|
In addition, elements will not remove elements from
|
||||||
|
search/history Bundles (i.e. elements from the Bundle itself, as opposed
|
||||||
|
to elements in the entry resources) unless the Bundle elements are
|
||||||
|
explicitly listed, e.g. <![CDATA[<code>_include=Bundle.total</code>]]>.
|
||||||
|
Thanks to @parisni for reporting!
|
||||||
|
</action>
|
||||||
</release>
|
</release>
|
||||||
<release version="3.0.0" date="2017-09-27">
|
<release version="3.0.0" date="2017-09-27">
|
||||||
<action type="add">
|
<action type="add">
|
||||||
|
|
Loading…
Reference in New Issue