Merge branch 'master' of github.com:jamesagnew/hapi-fhir
This commit is contained in:
commit
b4127674e4
|
@ -1,5 +1,6 @@
|
||||||
package example;
|
package example;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||||
import org.hl7.fhir.dstu3.model.Bundle;
|
import org.hl7.fhir.dstu3.model.Bundle;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
@ -8,6 +9,7 @@ import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||||
import ca.uhn.fhir.rest.client.apache.GZipContentInterceptor;
|
import ca.uhn.fhir.rest.client.apache.GZipContentInterceptor;
|
||||||
import ca.uhn.fhir.rest.client.api.*;
|
import ca.uhn.fhir.rest.client.api.*;
|
||||||
import ca.uhn.fhir.rest.client.interceptor.*;
|
import ca.uhn.fhir.rest.client.interceptor.*;
|
||||||
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
|
|
||||||
public class ClientExamples {
|
public class ClientExamples {
|
||||||
|
|
||||||
|
@ -52,6 +54,26 @@ public class ClientExamples {
|
||||||
// END SNIPPET: processMessage
|
// END SNIPPET: processMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public void cacheControl() {
|
||||||
|
FhirContext ctx = FhirContext.forDstu3();
|
||||||
|
|
||||||
|
// Create the client
|
||||||
|
IGenericClient client = ctx.newRestfulGenericClient("http://localhost:9999/fhir");
|
||||||
|
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
// ..populate the bundle..
|
||||||
|
|
||||||
|
// START SNIPPET: cacheControl
|
||||||
|
Bundle response = client
|
||||||
|
.search()
|
||||||
|
.forResource(Patient.class)
|
||||||
|
.returnBundle(Bundle.class)
|
||||||
|
.cacheControl(new CacheControlDirective().setNoCache(true)) // <-- add a directive
|
||||||
|
.execute();
|
||||||
|
// END SNIPPET: cacheControl
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public void createOkHttp() {
|
public void createOkHttp() {
|
||||||
// START SNIPPET: okhttp
|
// START SNIPPET: okhttp
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
package ca.uhn.fhir.rest.api;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.trim;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses and stores the value(s) within HTTP Cache-Control headers
|
||||||
|
*/
|
||||||
|
public class CacheControlDirective {
|
||||||
|
|
||||||
|
private static final String MAX_RESULTS_EQUALS = Constants.CACHE_CONTROL_MAX_RESULTS + "=";
|
||||||
|
private static final Logger ourLog = LoggerFactory.getLogger(CacheControlDirective.class);
|
||||||
|
private boolean myNoCache;
|
||||||
|
private boolean myNoStore;
|
||||||
|
private Integer myMaxResults;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
public CacheControlDirective() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the {@link #isNoStore() no-store} directive is set, this HAPI FHIR extention
|
||||||
|
* to the <code>Cache-Control</code> header called <code>max-results=123</code>
|
||||||
|
* specified the maximum number of results which will be fetched from the
|
||||||
|
* database before returning.
|
||||||
|
*/
|
||||||
|
public Integer getMaxResults() {
|
||||||
|
return myMaxResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the {@link #isNoStore() no-store} directive is set, this HAPI FHIR extention
|
||||||
|
* to the <code>Cache-Control</code> header called <code>max-results=123</code>
|
||||||
|
* specified the maximum number of results which will be fetched from the
|
||||||
|
* database before returning.
|
||||||
|
*/
|
||||||
|
public CacheControlDirective setMaxResults(Integer theMaxResults) {
|
||||||
|
myMaxResults = theMaxResults;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If <code>true<</code>, adds the <code>no-cache</code> directive to the
|
||||||
|
* request. This directive indicates that the cache should not be used to
|
||||||
|
* serve this request.
|
||||||
|
*/
|
||||||
|
public boolean isNoCache() {
|
||||||
|
return myNoCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If <code>true<</code>, adds the <code>no-cache</code> directive to the
|
||||||
|
* request. This directive indicates that the cache should not be used to
|
||||||
|
* serve this request.
|
||||||
|
*/
|
||||||
|
public CacheControlDirective setNoCache(boolean theNoCache) {
|
||||||
|
myNoCache = theNoCache;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNoStore() {
|
||||||
|
return myNoStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CacheControlDirective setNoStore(boolean theNoStore) {
|
||||||
|
myNoStore = theNoStore;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a list of <code>Cache-Control</code> header values
|
||||||
|
*
|
||||||
|
* @param theValues The <code>Cache-Control</code> header values
|
||||||
|
*/
|
||||||
|
public CacheControlDirective parse(List<String> theValues) {
|
||||||
|
if (theValues != null) {
|
||||||
|
for (String nextValue : theValues) {
|
||||||
|
StringTokenizer tok = new StringTokenizer(nextValue, ",");
|
||||||
|
while (tok.hasMoreTokens()) {
|
||||||
|
String next = trim(tok.nextToken());
|
||||||
|
if (Constants.CACHE_CONTROL_NO_CACHE.equals(next)) {
|
||||||
|
myNoCache = true;
|
||||||
|
} else if (Constants.CACHE_CONTROL_NO_STORE.equals(next)) {
|
||||||
|
myNoStore = true;
|
||||||
|
} else if (next.startsWith(MAX_RESULTS_EQUALS)) {
|
||||||
|
String valueString = trim(next.substring(MAX_RESULTS_EQUALS.length()));
|
||||||
|
try {
|
||||||
|
myMaxResults = Integer.parseInt(valueString);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
ourLog.warn("Invalid {} value: {}", Constants.CACHE_CONTROL_MAX_RESULTS, valueString);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,9 @@ import java.util.*;
|
||||||
|
|
||||||
public class Constants {
|
public class Constants {
|
||||||
|
|
||||||
|
public static final String CACHE_CONTROL_MAX_RESULTS = "max-results";
|
||||||
|
public static final String CACHE_CONTROL_NO_CACHE = "no-cache";
|
||||||
|
public static final String CACHE_CONTROL_NO_STORE = "no-store";
|
||||||
public static final String CHARSET_NAME_UTF8 = "UTF-8";
|
public static final String CHARSET_NAME_UTF8 = "UTF-8";
|
||||||
public static final Charset CHARSET_UTF8;
|
public static final Charset CHARSET_UTF8;
|
||||||
public static final String CHARSET_UTF8_CTSUFFIX = "; charset=" + CHARSET_NAME_UTF8;
|
public static final String CHARSET_UTF8_CTSUFFIX = "; charset=" + CHARSET_NAME_UTF8;
|
||||||
|
@ -67,6 +70,7 @@ public class Constants {
|
||||||
public static final String HEADER_AUTHORIZATION = "Authorization";
|
public static final String HEADER_AUTHORIZATION = "Authorization";
|
||||||
public static final String HEADER_AUTHORIZATION_VALPREFIX_BASIC = "Basic ";
|
public static final String HEADER_AUTHORIZATION_VALPREFIX_BASIC = "Basic ";
|
||||||
public static final String HEADER_AUTHORIZATION_VALPREFIX_BEARER = "Bearer ";
|
public static final String HEADER_AUTHORIZATION_VALPREFIX_BEARER = "Bearer ";
|
||||||
|
public static final String HEADER_CACHE_CONTROL = "Cache-Control";
|
||||||
public static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition";
|
public static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition";
|
||||||
public static final String HEADER_CONTENT_ENCODING = "Content-Encoding";
|
public static final String HEADER_CONTENT_ENCODING = "Content-Encoding";
|
||||||
public static final String HEADER_CONTENT_LOCATION = "Content-Location";
|
public static final String HEADER_CONTENT_LOCATION = "Content-Location";
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package ca.uhn.fhir.rest.gclient;
|
package ca.uhn.fhir.rest.gclient;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||||
import ca.uhn.fhir.rest.api.SummaryEnum;
|
import ca.uhn.fhir.rest.api.SummaryEnum;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
@ -38,6 +39,12 @@ public interface IClientExecutable<T extends IClientExecutable<?,Y>, Y> {
|
||||||
@Deprecated
|
@Deprecated
|
||||||
T andLogRequestAndResponse(boolean theLogRequestAndResponse);
|
T andLogRequestAndResponse(boolean theLogRequestAndResponse);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the <code>Cache-Control</code> header value, which advises the server (or any cache in front of it)
|
||||||
|
* how to behave in terms of cached requests
|
||||||
|
*/
|
||||||
|
T cacheControl(CacheControlDirective theCacheControlDirective);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request that the server return subsetted resources, containing only the elements specified in the given parameters.
|
* Request that the server return subsetted resources, containing only the elements specified in the given parameters.
|
||||||
* For example: <code>subsetElements("name", "identifier")</code> requests that the server only return
|
* For example: <code>subsetElements("name", "identifier")</code> requests that the server only return
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
package ca.uhn.fhir.rest.api;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class CacheControlDirectiveTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseNoCache() {
|
||||||
|
List<String> values = Arrays.asList(Constants.CACHE_CONTROL_NO_CACHE);
|
||||||
|
CacheControlDirective ccd = new CacheControlDirective();
|
||||||
|
ccd.parse(values);
|
||||||
|
assertTrue(ccd.isNoCache());
|
||||||
|
assertFalse(ccd.isNoStore());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseNoCacheNoStore() {
|
||||||
|
List<String> values = Arrays.asList(Constants.CACHE_CONTROL_NO_CACHE + " , " + Constants.CACHE_CONTROL_NO_STORE);
|
||||||
|
CacheControlDirective ccd = new CacheControlDirective();
|
||||||
|
ccd.parse(values);
|
||||||
|
assertTrue(ccd.isNoCache());
|
||||||
|
assertTrue(ccd.isNoStore());
|
||||||
|
assertEquals(null, ccd.getMaxResults());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseNoCacheNoStoreMaxResults() {
|
||||||
|
List<String> values = Arrays.asList(Constants.CACHE_CONTROL_NO_STORE + ", "+ Constants.CACHE_CONTROL_MAX_RESULTS + "=5");
|
||||||
|
CacheControlDirective ccd = new CacheControlDirective();
|
||||||
|
ccd.parse(values);
|
||||||
|
assertFalse(ccd.isNoCache());
|
||||||
|
assertTrue(ccd.isNoStore());
|
||||||
|
assertEquals(5, ccd.getMaxResults().intValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseNoCacheNoStoreMaxResultsInvalid() {
|
||||||
|
List<String> values = Arrays.asList(Constants.CACHE_CONTROL_NO_STORE + ", "+ Constants.CACHE_CONTROL_MAX_RESULTS + "=A");
|
||||||
|
CacheControlDirective ccd = new CacheControlDirective();
|
||||||
|
ccd.parse(values);
|
||||||
|
assertFalse(ccd.isNoCache());
|
||||||
|
assertTrue(ccd.isNoStore());
|
||||||
|
assertEquals(null, ccd.getMaxResults());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseNull() {
|
||||||
|
CacheControlDirective ccd = new CacheControlDirective();
|
||||||
|
ccd.parse(null);
|
||||||
|
assertFalse(ccd.isNoCache());
|
||||||
|
assertFalse(ccd.isNoStore());
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,6 +34,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
|
@ -135,7 +136,7 @@ public abstract class BaseClient implements IRestfulClient {
|
||||||
public <T extends IBaseResource> T fetchResourceFromUrl(Class<T> theResourceType, String theUrl) {
|
public <T extends IBaseResource> T fetchResourceFromUrl(Class<T> theResourceType, String theUrl) {
|
||||||
BaseHttpClientInvocation clientInvocation = new HttpGetClientInvocation(getFhirContext(), theUrl);
|
BaseHttpClientInvocation clientInvocation = new HttpGetClientInvocation(getFhirContext(), theUrl);
|
||||||
ResourceResponseHandler<T> binding = new ResourceResponseHandler<T>(theResourceType);
|
ResourceResponseHandler<T> binding = new ResourceResponseHandler<T>(theResourceType);
|
||||||
return invokeClient(getFhirContext(), binding, clientInvocation, null, false, false, null, null);
|
return invokeClient(getFhirContext(), binding, clientInvocation, null, false, false, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
void forceConformanceCheck() {
|
void forceConformanceCheck() {
|
||||||
|
@ -198,11 +199,11 @@ public abstract class BaseClient implements IRestfulClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
<T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation, boolean theLogRequestAndResponse) {
|
<T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation, boolean theLogRequestAndResponse) {
|
||||||
return invokeClient(theContext, binding, clientInvocation, null, null, theLogRequestAndResponse, null, null);
|
return invokeClient(theContext, binding, clientInvocation, null, null, theLogRequestAndResponse, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
<T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation, EncodingEnum theEncoding, Boolean thePrettyPrint,
|
<T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation, EncodingEnum theEncoding, Boolean thePrettyPrint,
|
||||||
boolean theLogRequestAndResponse, SummaryEnum theSummaryMode, Set<String> theSubsetElements) {
|
boolean theLogRequestAndResponse, SummaryEnum theSummaryMode, Set<String> theSubsetElements, CacheControlDirective theCacheControlDirective) {
|
||||||
|
|
||||||
if (!myDontValidateConformance) {
|
if (!myDontValidateConformance) {
|
||||||
myFactory.validateServerBaseIfConfiguredToDoSo(myUrlBase, myClient, this);
|
myFactory.validateServerBaseIfConfiguredToDoSo(myUrlBase, myClient, this);
|
||||||
|
@ -244,6 +245,18 @@ public abstract class BaseClient implements IRestfulClient {
|
||||||
|
|
||||||
httpRequest = clientInvocation.asHttpRequest(myUrlBase, params, encoding, thePrettyPrint);
|
httpRequest = clientInvocation.asHttpRequest(myUrlBase, params, encoding, thePrettyPrint);
|
||||||
|
|
||||||
|
if (theCacheControlDirective != null) {
|
||||||
|
StringBuilder b = new StringBuilder();
|
||||||
|
addToCacheControlHeader(b, Constants.CACHE_CONTROL_NO_CACHE, theCacheControlDirective.isNoCache());
|
||||||
|
addToCacheControlHeader(b, Constants.CACHE_CONTROL_NO_STORE, theCacheControlDirective.isNoStore());
|
||||||
|
if (theCacheControlDirective.getMaxResults() != null) {
|
||||||
|
addToCacheControlHeader(b, Constants.CACHE_CONTROL_MAX_RESULTS+"="+ Integer.toString(theCacheControlDirective.getMaxResults().intValue()), true);
|
||||||
|
}
|
||||||
|
if (b.length() > 0) {
|
||||||
|
httpRequest.addHeader(Constants.HEADER_CACHE_CONTROL, b.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (theLogRequestAndResponse) {
|
if (theLogRequestAndResponse) {
|
||||||
ourLog.info("Client invoking: {}", httpRequest);
|
ourLog.info("Client invoking: {}", httpRequest);
|
||||||
String body = httpRequest.getRequestBodyFromStream();
|
String body = httpRequest.getRequestBodyFromStream();
|
||||||
|
@ -366,6 +379,15 @@ public abstract class BaseClient implements IRestfulClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addToCacheControlHeader(StringBuilder theBuilder, String theDirective, boolean theActive) {
|
||||||
|
if (theActive) {
|
||||||
|
if (theBuilder.length() > 0) {
|
||||||
|
theBuilder.append(", ");
|
||||||
|
}
|
||||||
|
theBuilder.append(theDirective);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
|
* For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -120,10 +120,10 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
||||||
ResourceResponseHandler<T> binding = new ResourceResponseHandler<T>(theType, (Class<? extends IBaseResource>) null, id, allowHtmlResponse);
|
ResourceResponseHandler<T> binding = new ResourceResponseHandler<T>(theType, (Class<? extends IBaseResource>) null, id, allowHtmlResponse);
|
||||||
|
|
||||||
if (theNotModifiedHandler == null) {
|
if (theNotModifiedHandler == null) {
|
||||||
return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements);
|
return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements, null);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements);
|
return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements, null);
|
||||||
} catch (NotModifiedException e) {
|
} catch (NotModifiedException e) {
|
||||||
return theNotModifiedHandler.call();
|
return theNotModifiedHandler.call();
|
||||||
}
|
}
|
||||||
|
@ -373,6 +373,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
||||||
private boolean myQueryLogRequestAndResponse;
|
private boolean myQueryLogRequestAndResponse;
|
||||||
private HashSet<String> mySubsetElements;
|
private HashSet<String> mySubsetElements;
|
||||||
protected SummaryEnum mySummaryMode;
|
protected SummaryEnum mySummaryMode;
|
||||||
|
protected CacheControlDirective myCacheControlDirective;
|
||||||
|
|
||||||
@Deprecated // override deprecated method
|
@Deprecated // override deprecated method
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@ -382,6 +383,12 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
||||||
return (T) this;
|
return (T) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T cacheControl(CacheControlDirective theCacheControlDirective) {
|
||||||
|
myCacheControlDirective = theCacheControlDirective;
|
||||||
|
return (T) this;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
public T elementsSubset(String... theElements) {
|
public T elementsSubset(String... theElements) {
|
||||||
|
@ -434,19 +441,11 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected <Z> Z invoke(Map<String, List<String>> theParams, IClientResponseHandler<Z> theHandler, BaseHttpClientInvocation theInvocation) {
|
protected <Z> Z invoke(Map<String, List<String>> theParams, IClientResponseHandler<Z> theHandler, BaseHttpClientInvocation theInvocation) {
|
||||||
// if (myParamEncoding != null) {
|
|
||||||
// theParams.put(Constants.PARAM_FORMAT, Collections.singletonList(myParamEncoding.getFormatContentType()));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (myPrettyPrint != null) {
|
|
||||||
// theParams.put(Constants.PARAM_PRETTY, Collections.singletonList(myPrettyPrint.toString()));
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (isKeepResponses()) {
|
if (isKeepResponses()) {
|
||||||
myLastRequest = theInvocation.asHttpRequest(getServerBase(), theParams, getEncoding(), myPrettyPrint);
|
myLastRequest = theInvocation.asHttpRequest(getServerBase(), theParams, getEncoding(), myPrettyPrint);
|
||||||
}
|
}
|
||||||
|
|
||||||
Z resp = invokeClient(myContext, theHandler, theInvocation, myParamEncoding, myPrettyPrint, myQueryLogRequestAndResponse || myLogRequestAndResponse, mySummaryMode, mySubsetElements);
|
Z resp = invokeClient(myContext, theHandler, theInvocation, myParamEncoding, myPrettyPrint, myQueryLogRequestAndResponse || myLogRequestAndResponse, mySummaryMode, mySubsetElements, myCacheControlDirective);
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,10 +36,7 @@ import ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils;
|
||||||
import ca.uhn.fhir.jpa.util.xmlpatch.XmlPatchUtils;
|
import ca.uhn.fhir.jpa.util.xmlpatch.XmlPatchUtils;
|
||||||
import ca.uhn.fhir.model.api.*;
|
import ca.uhn.fhir.model.api.*;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.rest.api.PatchTypeEnum;
|
import ca.uhn.fhir.rest.api.*;
|
||||||
import ca.uhn.fhir.rest.api.QualifiedParamList;
|
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
|
||||||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.param.ParameterUtil;
|
import ca.uhn.fhir.rest.param.ParameterUtil;
|
||||||
|
@ -928,7 +925,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mySearchCoordinatorSvc.registerSearch(this, theParams, getResourceName());
|
CacheControlDirective cacheControlDirective = new CacheControlDirective();
|
||||||
|
if (theRequestDetails != null) {
|
||||||
|
cacheControlDirective.parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mySearchCoordinatorSvc.registerSearch(this, theParams, getResourceName(), cacheControlDirective);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -107,6 +107,7 @@ public class DaoConfig {
|
||||||
private Set<String> myTreatBaseUrlsAsLocal = new HashSet<String>();
|
private Set<String> myTreatBaseUrlsAsLocal = new HashSet<String>();
|
||||||
private Set<String> myTreatReferencesAsLogical = new HashSet<String>(DEFAULT_LOGICAL_BASE_URLS);
|
private Set<String> myTreatReferencesAsLogical = new HashSet<String>(DEFAULT_LOGICAL_BASE_URLS);
|
||||||
private boolean myAutoCreatePlaceholderReferenceTargets;
|
private boolean myAutoCreatePlaceholderReferenceTargets;
|
||||||
|
private Integer myCacheControlNoStoreMaxResultsUpperLimit = 1000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
|
@ -131,6 +132,26 @@ public class DaoConfig {
|
||||||
myTreatReferencesAsLogical.add(theTreatReferencesAsLogical);
|
myTreatReferencesAsLogical.add(theTreatReferencesAsLogical);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the highest number that a client is permitted to use in a
|
||||||
|
* <code>Cache-Control: nostore, max-results=NNN</code>
|
||||||
|
* directive. If the client tries to exceed this limit, the
|
||||||
|
* request will be denied. Defaults to 1000.
|
||||||
|
*/
|
||||||
|
public Integer getCacheControlNoStoreMaxResultsUpperLimit() {
|
||||||
|
return myCacheControlNoStoreMaxResultsUpperLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the highest number that a client is permitted to use in a
|
||||||
|
* <code>Cache-Control: nostore, max-results=NNN</code>
|
||||||
|
* directive. If the client tries to exceed this limit, the
|
||||||
|
* request will be denied. Defaults to 1000.
|
||||||
|
*/
|
||||||
|
public void setCacheControlNoStoreMaxResultsUpperLimit(Integer theCacheControlNoStoreMaxResults) {
|
||||||
|
myCacheControlNoStoreMaxResultsUpperLimit = theCacheControlNoStoreMaxResults;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When a code system is added that contains more than this number of codes,
|
* When a code system is added that contains more than this number of codes,
|
||||||
* the code system will be indexed later in an incremental process in order to
|
* the code system will be indexed later in an incremental process in order to
|
||||||
|
@ -336,8 +357,11 @@ public class DaoConfig {
|
||||||
/**
|
/**
|
||||||
* This may be used to optionally register server interceptors directly against the DAOs.
|
* This may be used to optionally register server interceptors directly against the DAOs.
|
||||||
*/
|
*/
|
||||||
public void setInterceptors(List<IServerInterceptor> theInterceptors) {
|
public void setInterceptors(IServerInterceptor... theInterceptor) {
|
||||||
myInterceptors = theInterceptors;
|
setInterceptors(new ArrayList<IServerInterceptor>());
|
||||||
|
if (theInterceptor != null && theInterceptor.length != 0) {
|
||||||
|
getInterceptors().addAll(Arrays.asList(theInterceptor));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -434,6 +458,11 @@ public class DaoConfig {
|
||||||
* This approach can improve performance, especially under heavy load, but can also mean that
|
* This approach can improve performance, especially under heavy load, but can also mean that
|
||||||
* searches may potentially return slightly out-of-date results.
|
* searches may potentially return slightly out-of-date results.
|
||||||
* </p>
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Note that if this is set to a non-null value, clients may override this setting by using
|
||||||
|
* the <code>Cache-Control</code> header. If this is set to <code>null</code>, the Cache-Control
|
||||||
|
* header will be ignored.
|
||||||
|
* </p>
|
||||||
*/
|
*/
|
||||||
public Long getReuseCachedSearchResultsForMillis() {
|
public Long getReuseCachedSearchResultsForMillis() {
|
||||||
return myReuseCachedSearchResultsForMillis;
|
return myReuseCachedSearchResultsForMillis;
|
||||||
|
@ -449,6 +478,11 @@ public class DaoConfig {
|
||||||
* This approach can improve performance, especially under heavy load, but can also mean that
|
* This approach can improve performance, especially under heavy load, but can also mean that
|
||||||
* searches may potentially return slightly out-of-date results.
|
* searches may potentially return slightly out-of-date results.
|
||||||
* </p>
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Note that if this is set to a non-null value, clients may override this setting by using
|
||||||
|
* the <code>Cache-Control</code> header. If this is set to <code>null</code>, the Cache-Control
|
||||||
|
* header will be ignored.
|
||||||
|
* </p>
|
||||||
*/
|
*/
|
||||||
public void setReuseCachedSearchResultsForMillis(Long theReuseCachedSearchResultsForMillis) {
|
public void setReuseCachedSearchResultsForMillis(Long theReuseCachedSearchResultsForMillis) {
|
||||||
myReuseCachedSearchResultsForMillis = theReuseCachedSearchResultsForMillis;
|
myReuseCachedSearchResultsForMillis = theReuseCachedSearchResultsForMillis;
|
||||||
|
@ -925,11 +959,8 @@ public class DaoConfig {
|
||||||
/**
|
/**
|
||||||
* This may be used to optionally register server interceptors directly against the DAOs.
|
* This may be used to optionally register server interceptors directly against the DAOs.
|
||||||
*/
|
*/
|
||||||
public void setInterceptors(IServerInterceptor... theInterceptor) {
|
public void setInterceptors(List<IServerInterceptor> theInterceptors) {
|
||||||
setInterceptors(new ArrayList<IServerInterceptor>());
|
myInterceptors = theInterceptors;
|
||||||
if (theInterceptor != null && theInterceptor.length != 0) {
|
|
||||||
getInterceptors().addAll(Arrays.asList(theInterceptor));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -65,7 +65,7 @@ public class FhirResourceDaoPatientDstu2 extends FhirResourceDaoDstu2<Patient>im
|
||||||
paramMap.setLoadSynchronous(true);
|
paramMap.setLoadSynchronous(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return mySearchCoordinatorSvc.registerSearch(this, paramMap, getResourceName());
|
return mySearchCoordinatorSvc.registerSearch(this, paramMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -24,6 +24,7 @@ import java.util.Collections;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||||
import org.hl7.fhir.dstu3.model.Patient;
|
import org.hl7.fhir.dstu3.model.Patient;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
|
@ -66,7 +67,7 @@ public class FhirResourceDaoPatientDstu3 extends FhirResourceDaoDstu3<Patient>im
|
||||||
paramMap.setLoadSynchronous(true);
|
paramMap.setLoadSynchronous(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return mySearchCoordinatorSvc.registerSearch(this, paramMap, getResourceName());
|
return mySearchCoordinatorSvc.registerSearch(this, paramMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -24,6 +24,7 @@ import java.util.Collections;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||||
import org.hl7.fhir.r4.model.Patient;
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
|
@ -66,7 +67,7 @@ public class FhirResourceDaoPatientR4 extends FhirResourceDaoR4<Patient>implemen
|
||||||
paramMap.setLoadSynchronous(true);
|
paramMap.setLoadSynchronous(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return mySearchCoordinatorSvc.registerSearch(this, paramMap, getResourceName());
|
return mySearchCoordinatorSvc.registerSearch(this, paramMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -20,18 +20,19 @@ package ca.uhn.fhir.jpa.search;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.IDao;
|
import ca.uhn.fhir.jpa.dao.IDao;
|
||||||
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||||
|
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public interface ISearchCoordinatorSvc {
|
public interface ISearchCoordinatorSvc {
|
||||||
|
|
||||||
List<Long> getResources(String theUuid, int theFrom, int theTo);
|
|
||||||
|
|
||||||
IBundleProvider registerSearch(IDao theCallingDao, SearchParameterMap theParams, String theResourceType);
|
|
||||||
|
|
||||||
void cancelAllActiveSearches();
|
void cancelAllActiveSearches();
|
||||||
|
|
||||||
|
List<Long> getResources(String theUuid, int theFrom, int theTo);
|
||||||
|
|
||||||
|
IBundleProvider registerSearch(IDao theCallingDao, SearchParameterMap theParams, String theResourceType, CacheControlDirective theCacheControlDirective);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,8 @@ import java.util.concurrent.*;
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||||
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||||
|
@ -55,7 +57,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
public static final int DEFAULT_SYNC_SIZE = 250;
|
public static final int DEFAULT_SYNC_SIZE = 250;
|
||||||
|
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchCoordinatorSvcImpl.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchCoordinatorSvcImpl.class);
|
||||||
|
private final ConcurrentHashMap<String, SearchTask> myIdToSearchTask = new ConcurrentHashMap<String, SearchTask>();
|
||||||
@Autowired
|
@Autowired
|
||||||
private FhirContext myContext;
|
private FhirContext myContext;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@ -63,7 +65,6 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
@Autowired
|
@Autowired
|
||||||
private EntityManager myEntityManager;
|
private EntityManager myEntityManager;
|
||||||
private ExecutorService myExecutor;
|
private ExecutorService myExecutor;
|
||||||
private final ConcurrentHashMap<String, SearchTask> myIdToSearchTask = new ConcurrentHashMap<String, SearchTask>();
|
|
||||||
private Integer myLoadingThrottleForUnitTests = null;
|
private Integer myLoadingThrottleForUnitTests = null;
|
||||||
private long myMaxMillisToWaitForRemoteResults = DateUtils.MILLIS_PER_MINUTE;
|
private long myMaxMillisToWaitForRemoteResults = DateUtils.MILLIS_PER_MINUTE;
|
||||||
private boolean myNeverUseLocalSearchForUnitTests;
|
private boolean myNeverUseLocalSearchForUnitTests;
|
||||||
|
@ -179,7 +180,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IBundleProvider registerSearch(final IDao theCallingDao, final SearchParameterMap theParams, String theResourceType) {
|
public IBundleProvider registerSearch(final IDao theCallingDao, final SearchParameterMap theParams, String theResourceType, CacheControlDirective theCacheControlDirective) {
|
||||||
StopWatch w = new StopWatch();
|
StopWatch w = new StopWatch();
|
||||||
final String searchUuid = UUID.randomUUID().toString();
|
final String searchUuid = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
@ -187,7 +188,21 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
final ISearchBuilder sb = theCallingDao.newSearchBuilder();
|
final ISearchBuilder sb = theCallingDao.newSearchBuilder();
|
||||||
sb.setType(resourceTypeClass, theResourceType);
|
sb.setType(resourceTypeClass, theResourceType);
|
||||||
|
|
||||||
if (theParams.isLoadSynchronous()) {
|
final Integer loadSynchronousUpTo;
|
||||||
|
if (theCacheControlDirective != null && theCacheControlDirective.isNoStore()) {
|
||||||
|
if (theCacheControlDirective.getMaxResults() != null) {
|
||||||
|
loadSynchronousUpTo = theCacheControlDirective.getMaxResults();
|
||||||
|
if (loadSynchronousUpTo > myDaoConfig.getCacheControlNoStoreMaxResultsUpperLimit()) {
|
||||||
|
throw new InvalidRequestException(Constants.HEADER_CACHE_CONTROL + " header " + Constants.CACHE_CONTROL_MAX_RESULTS + " value must not exceed " + myDaoConfig.getCacheControlNoStoreMaxResultsUpperLimit());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loadSynchronousUpTo = 100;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loadSynchronousUpTo = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (theParams.isLoadSynchronous() || loadSynchronousUpTo != null) {
|
||||||
|
|
||||||
// Execute the query and make sure we return distinct results
|
// Execute the query and make sure we return distinct results
|
||||||
TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager);
|
TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager);
|
||||||
|
@ -202,6 +217,9 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
Iterator<Long> resultIter = sb.createQuery(theParams, searchUuid);
|
Iterator<Long> resultIter = sb.createQuery(theParams, searchUuid);
|
||||||
while (resultIter.hasNext()) {
|
while (resultIter.hasNext()) {
|
||||||
pids.add(resultIter.next());
|
pids.add(resultIter.next());
|
||||||
|
if (loadSynchronousUpTo != null && pids.size() >= loadSynchronousUpTo) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (theParams.getLoadSynchronousUpTo() != null && pids.size() >= theParams.getLoadSynchronousUpTo()) {
|
if (theParams.getLoadSynchronousUpTo() != null && pids.size() >= theParams.getLoadSynchronousUpTo()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -231,9 +249,13 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
* See if there are any cached searches whose results we can return
|
* See if there are any cached searches whose results we can return
|
||||||
* instead
|
* instead
|
||||||
*/
|
*/
|
||||||
|
boolean useCache = true;
|
||||||
|
if (theCacheControlDirective != null && theCacheControlDirective.isNoCache() == true) {
|
||||||
|
useCache = false;
|
||||||
|
}
|
||||||
final String queryString = theParams.toNormalizedQueryString(myContext);
|
final String queryString = theParams.toNormalizedQueryString(myContext);
|
||||||
if (theParams.getEverythingMode() == null) {
|
if (theParams.getEverythingMode() == null) {
|
||||||
if (myDaoConfig.getReuseCachedSearchResultsForMillis() != null) {
|
if (myDaoConfig.getReuseCachedSearchResultsForMillis() != null && useCache) {
|
||||||
|
|
||||||
final Date createdCutoff = new Date(System.currentTimeMillis() - myDaoConfig.getReuseCachedSearchResultsForMillis());
|
final Date createdCutoff = new Date(System.currentTimeMillis() - myDaoConfig.getReuseCachedSearchResultsForMillis());
|
||||||
final String resourceType = theResourceType;
|
final String resourceType = theResourceType;
|
||||||
|
@ -394,16 +416,16 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
|
|
||||||
public class SearchTask implements Callable<Void> {
|
public class SearchTask implements Callable<Void> {
|
||||||
|
|
||||||
private boolean myAbortRequested;
|
|
||||||
private final IDao myCallingDao;
|
private final IDao myCallingDao;
|
||||||
private final CountDownLatch myCompletionLatch;
|
private final CountDownLatch myCompletionLatch;
|
||||||
private int myCountSaved = 0;
|
|
||||||
private final CountDownLatch myInitialCollectionLatch = new CountDownLatch(1);
|
private final CountDownLatch myInitialCollectionLatch = new CountDownLatch(1);
|
||||||
private final SearchParameterMap myParams;
|
private final SearchParameterMap myParams;
|
||||||
private final String myResourceType;
|
private final String myResourceType;
|
||||||
private final Search mySearch;
|
private final Search mySearch;
|
||||||
private final ArrayList<Long> mySyncedPids = new ArrayList<Long>();
|
private final ArrayList<Long> mySyncedPids = new ArrayList<Long>();
|
||||||
private final ArrayList<Long> myUnsyncedPids = new ArrayList<Long>();
|
private final ArrayList<Long> myUnsyncedPids = new ArrayList<Long>();
|
||||||
|
private boolean myAbortRequested;
|
||||||
|
private int myCountSaved = 0;
|
||||||
private String mySearchUuid;
|
private String mySearchUuid;
|
||||||
|
|
||||||
public SearchTask(Search theSearch, IDao theCallingDao, SearchParameterMap theParams, String theResourceType, String theSearchUuid) {
|
public SearchTask(Search theSearch, IDao theCallingDao, SearchParameterMap theParams, String theResourceType, String theSearchUuid) {
|
||||||
|
|
|
@ -0,0 +1,164 @@
|
||||||
|
package ca.uhn.fhir.jpa.provider.r4;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||||
|
import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl;
|
||||||
|
import ca.uhn.fhir.parser.StrictErrorHandler;
|
||||||
|
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
|
import org.hl7.fhir.r4.model.Bundle;
|
||||||
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.test.util.AopTestUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class ResourceProviderR4CacheTest extends BaseResourceProviderR4Test {
|
||||||
|
|
||||||
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderR4CacheTest.class);
|
||||||
|
private SearchCoordinatorSvcImpl mySearchCoordinatorSvcRaw;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@After
|
||||||
|
public void after() throws Exception {
|
||||||
|
super.after();
|
||||||
|
myDaoConfig.setReuseCachedSearchResultsForMillis(new DaoConfig().getReuseCachedSearchResultsForMillis());
|
||||||
|
myDaoConfig.setCacheControlNoStoreMaxResultsUpperLimit(new DaoConfig().getCacheControlNoStoreMaxResultsUpperLimit());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void before() throws Exception {
|
||||||
|
super.before();
|
||||||
|
myFhirCtx.setParserErrorHandler(new StrictErrorHandler());
|
||||||
|
mySearchCoordinatorSvcRaw = AopTestUtils.getTargetObject(mySearchCoordinatorSvc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCacheNoStore() throws IOException {
|
||||||
|
|
||||||
|
Patient pt1 = new Patient();
|
||||||
|
pt1.addName().setFamily("FAM");
|
||||||
|
ourClient.create().resource(pt1).execute();
|
||||||
|
|
||||||
|
Bundle results = ourClient
|
||||||
|
.search()
|
||||||
|
.forResource("Patient")
|
||||||
|
.where(Patient.FAMILY.matches().value("FAM"))
|
||||||
|
.returnBundle(Bundle.class)
|
||||||
|
.cacheControl(new CacheControlDirective().setNoStore(true))
|
||||||
|
.execute();
|
||||||
|
assertEquals(1, results.getEntry().size());
|
||||||
|
assertEquals(0, mySearchEntityDao.count());
|
||||||
|
|
||||||
|
Patient pt2 = new Patient();
|
||||||
|
pt2.addName().setFamily("FAM");
|
||||||
|
ourClient.create().resource(pt2).execute();
|
||||||
|
|
||||||
|
results = ourClient
|
||||||
|
.search()
|
||||||
|
.forResource("Patient")
|
||||||
|
.where(Patient.FAMILY.matches().value("FAM"))
|
||||||
|
.returnBundle(Bundle.class)
|
||||||
|
.cacheControl(new CacheControlDirective().setNoStore(true))
|
||||||
|
.execute();
|
||||||
|
assertEquals(2, results.getEntry().size());
|
||||||
|
assertEquals(0, mySearchEntityDao.count());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCacheNoStoreMaxResults() throws IOException {
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
Patient pt1 = new Patient();
|
||||||
|
pt1.addName().setFamily("FAM" + i);
|
||||||
|
ourClient.create().resource(pt1).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
Bundle results = ourClient
|
||||||
|
.search()
|
||||||
|
.forResource("Patient")
|
||||||
|
.where(Patient.FAMILY.matches().value("FAM"))
|
||||||
|
.returnBundle(Bundle.class)
|
||||||
|
.cacheControl(new CacheControlDirective().setNoStore(true).setMaxResults(5))
|
||||||
|
.execute();
|
||||||
|
assertEquals(5, results.getEntry().size());
|
||||||
|
assertEquals(0, mySearchEntityDao.count());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCacheNoStoreMaxResultsWithIllegalValue() throws IOException {
|
||||||
|
myDaoConfig.setCacheControlNoStoreMaxResultsUpperLimit(123);
|
||||||
|
try {
|
||||||
|
ourClient
|
||||||
|
.search()
|
||||||
|
.forResource("Patient")
|
||||||
|
.where(Patient.FAMILY.matches().value("FAM"))
|
||||||
|
.returnBundle(Bundle.class)
|
||||||
|
.cacheControl(new CacheControlDirective().setNoStore(true).setMaxResults(5000))
|
||||||
|
.execute();
|
||||||
|
fail();
|
||||||
|
} catch (InvalidRequestException e) {
|
||||||
|
assertEquals("HTTP 400 Bad Request: Cache-Control header max-results value must not exceed 123", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCacheSuppressed() throws IOException {
|
||||||
|
|
||||||
|
Patient pt1 = new Patient();
|
||||||
|
pt1.addName().setFamily("FAM");
|
||||||
|
ourClient.create().resource(pt1).execute();
|
||||||
|
|
||||||
|
Bundle results = ourClient.search().forResource("Patient").where(Patient.FAMILY.matches().value("FAM")).returnBundle(Bundle.class).execute();
|
||||||
|
assertEquals(1, results.getEntry().size());
|
||||||
|
assertEquals(1, mySearchEntityDao.count());
|
||||||
|
|
||||||
|
Patient pt2 = new Patient();
|
||||||
|
pt2.addName().setFamily("FAM");
|
||||||
|
ourClient.create().resource(pt2).execute();
|
||||||
|
|
||||||
|
results = ourClient
|
||||||
|
.search()
|
||||||
|
.forResource("Patient")
|
||||||
|
.where(Patient.FAMILY.matches().value("FAM"))
|
||||||
|
.returnBundle(Bundle.class)
|
||||||
|
.cacheControl(new CacheControlDirective().setNoCache(true))
|
||||||
|
.execute();
|
||||||
|
assertEquals(2, results.getEntry().size());
|
||||||
|
assertEquals(2, mySearchEntityDao.count());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCacheUsedNormally() throws IOException {
|
||||||
|
|
||||||
|
Patient pt1 = new Patient();
|
||||||
|
pt1.addName().setFamily("FAM");
|
||||||
|
ourClient.create().resource(pt1).execute();
|
||||||
|
|
||||||
|
Bundle results = ourClient.search().forResource("Patient").where(Patient.FAMILY.matches().value("FAM")).returnBundle(Bundle.class).execute();
|
||||||
|
assertEquals(1, results.getEntry().size());
|
||||||
|
assertEquals(1, mySearchEntityDao.count());
|
||||||
|
|
||||||
|
Patient pt2 = new Patient();
|
||||||
|
pt2.addName().setFamily("FAM");
|
||||||
|
ourClient.create().resource(pt2).execute();
|
||||||
|
|
||||||
|
results = ourClient.search().forResource("Patient").where(Patient.FAMILY.matches().value("FAM")).returnBundle(Bundle.class).execute();
|
||||||
|
assertEquals(1, results.getEntry().size());
|
||||||
|
assertEquals(1, mySearchEntityDao.count());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void afterClassClearContext() {
|
||||||
|
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,41 +1,60 @@
|
||||||
package ca.uhn.fhir.jpa.search;
|
package ca.uhn.fhir.jpa.search;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
import static org.mockito.Matchers.*;
|
|
||||||
import static org.mockito.Mockito.*;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
|
||||||
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
|
||||||
import org.junit.*;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.mockito.*;
|
|
||||||
import org.mockito.invocation.InvocationOnMock;
|
|
||||||
import org.mockito.runners.MockitoJUnitRunner;
|
|
||||||
import org.mockito.stubbing.Answer;
|
|
||||||
import org.springframework.data.domain.*;
|
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.jpa.dao.*;
|
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.dao.data.*;
|
import ca.uhn.fhir.jpa.dao.IDao;
|
||||||
import ca.uhn.fhir.jpa.entity.*;
|
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
|
||||||
|
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.ISearchIncludeDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
|
||||||
|
import ca.uhn.fhir.jpa.entity.Search;
|
||||||
|
import ca.uhn.fhir.jpa.entity.SearchResult;
|
||||||
|
import ca.uhn.fhir.jpa.entity.SearchStatusEnum;
|
||||||
|
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
||||||
import ca.uhn.fhir.jpa.util.BaseIterator;
|
import ca.uhn.fhir.jpa.util.BaseIterator;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||||
|
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
import ca.uhn.fhir.rest.param.StringParam;
|
import ca.uhn.fhir.rest.param.StringParam;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Captor;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
|
import org.mockito.runners.MockitoJUnitRunner;
|
||||||
|
import org.mockito.stubbing.Answer;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.PageImpl;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
|
|
||||||
@SuppressWarnings({ "unchecked" })
|
import javax.persistence.EntityManager;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.Matchers.any;
|
||||||
|
import static org.mockito.Matchers.anyBoolean;
|
||||||
|
import static org.mockito.Matchers.eq;
|
||||||
|
import static org.mockito.Matchers.same;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
@SuppressWarnings({"unchecked"})
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
public class SearchCoordinatorSvcImplTest {
|
public class SearchCoordinatorSvcImplTest {
|
||||||
|
|
||||||
private static FhirContext ourCtx = FhirContext.forDstu3();
|
private static FhirContext ourCtx = FhirContext.forDstu3();
|
||||||
|
@Captor
|
||||||
|
ArgumentCaptor<Iterable<SearchResult>> mySearchResultIterCaptor;
|
||||||
@Mock
|
@Mock
|
||||||
private IDao myCallingDao;
|
private IDao myCallingDao;
|
||||||
@Mock
|
@Mock
|
||||||
|
@ -49,10 +68,6 @@ public class SearchCoordinatorSvcImplTest {
|
||||||
private ISearchIncludeDao mySearchIncludeDao;
|
private ISearchIncludeDao mySearchIncludeDao;
|
||||||
@Mock
|
@Mock
|
||||||
private ISearchResultDao mySearchResultDao;
|
private ISearchResultDao mySearchResultDao;
|
||||||
@Captor
|
|
||||||
ArgumentCaptor<Iterable<SearchResult>> mySearchResultIterCaptor;
|
|
||||||
|
|
||||||
|
|
||||||
private SearchCoordinatorSvcImpl mySvc;
|
private SearchCoordinatorSvcImpl mySvc;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
|
@ -63,6 +78,7 @@ public class SearchCoordinatorSvcImplTest {
|
||||||
public void after() {
|
public void after() {
|
||||||
verify(myCallingDao, atMost(myExpectedNumberOfSearchBuildersCreated)).newSearchBuilder();
|
verify(myCallingDao, atMost(myExpectedNumberOfSearchBuildersCreated)).newSearchBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void before() {
|
public void before() {
|
||||||
|
|
||||||
|
@ -89,7 +105,8 @@ public class SearchCoordinatorSvcImplTest {
|
||||||
provider.setEntityManager(myEntityManager);
|
provider.setEntityManager(myEntityManager);
|
||||||
provider.setContext(ourCtx);
|
provider.setContext(ourCtx);
|
||||||
return null;
|
return null;
|
||||||
}}).when(myCallingDao).injectDependenciesIntoBundleProvider(any(PersistedJpaBundleProvider.class));
|
}
|
||||||
|
}).when(myCallingDao).injectDependenciesIntoBundleProvider(any(PersistedJpaBundleProvider.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Long> createPidSequence(int from, int to) {
|
private List<Long> createPidSequence(int from, int to) {
|
||||||
|
@ -128,7 +145,7 @@ public class SearchCoordinatorSvcImplTest {
|
||||||
|
|
||||||
doAnswer(loadPids()).when(mySearchBuider).loadResourcesByPid(any(List.class), any(List.class), any(Set.class), anyBoolean(), any(EntityManager.class), any(FhirContext.class), same(myCallingDao));
|
doAnswer(loadPids()).when(mySearchBuider).loadResourcesByPid(any(List.class), any(List.class), any(Set.class), anyBoolean(), any(EntityManager.class), any(FhirContext.class), same(myCallingDao));
|
||||||
|
|
||||||
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient");
|
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective());
|
||||||
assertNotNull(result.getUuid());
|
assertNotNull(result.getUuid());
|
||||||
assertEquals(null, result.size());
|
assertEquals(null, result.size());
|
||||||
|
|
||||||
|
@ -151,7 +168,7 @@ public class SearchCoordinatorSvcImplTest {
|
||||||
|
|
||||||
doAnswer(loadPids()).when(mySearchBuider).loadResourcesByPid(any(List.class), any(List.class), any(Set.class), anyBoolean(), any(EntityManager.class), any(FhirContext.class), same(myCallingDao));
|
doAnswer(loadPids()).when(mySearchBuider).loadResourcesByPid(any(List.class), any(List.class), any(Set.class), anyBoolean(), any(EntityManager.class), any(FhirContext.class), same(myCallingDao));
|
||||||
|
|
||||||
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient");
|
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective());
|
||||||
assertNotNull(result.getUuid());
|
assertNotNull(result.getUuid());
|
||||||
assertEquals(null, result.size());
|
assertEquals(null, result.size());
|
||||||
|
|
||||||
|
@ -166,7 +183,7 @@ public class SearchCoordinatorSvcImplTest {
|
||||||
verify(mySearchDao, atLeastOnce()).save(searchCaptor.capture());
|
verify(mySearchDao, atLeastOnce()).save(searchCaptor.capture());
|
||||||
|
|
||||||
verify(mySearchResultDao, atLeastOnce()).save(mySearchResultIterCaptor.capture());
|
verify(mySearchResultDao, atLeastOnce()).save(mySearchResultIterCaptor.capture());
|
||||||
List<SearchResult> allResults= new ArrayList<SearchResult>();
|
List<SearchResult> allResults = new ArrayList<SearchResult>();
|
||||||
for (Iterable<SearchResult> next : mySearchResultIterCaptor.getAllValues()) {
|
for (Iterable<SearchResult> next : mySearchResultIterCaptor.getAllValues()) {
|
||||||
allResults.addAll(Lists.newArrayList(next));
|
allResults.addAll(Lists.newArrayList(next));
|
||||||
}
|
}
|
||||||
|
@ -187,7 +204,7 @@ public class SearchCoordinatorSvcImplTest {
|
||||||
|
|
||||||
doAnswer(loadPids()).when(mySearchBuider).loadResourcesByPid(any(List.class), any(List.class), any(Set.class), anyBoolean(), any(EntityManager.class), any(FhirContext.class), same(myCallingDao));
|
doAnswer(loadPids()).when(mySearchBuider).loadResourcesByPid(any(List.class), any(List.class), any(Set.class), anyBoolean(), any(EntityManager.class), any(FhirContext.class), same(myCallingDao));
|
||||||
|
|
||||||
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient");
|
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective());
|
||||||
assertNotNull(result.getUuid());
|
assertNotNull(result.getUuid());
|
||||||
assertEquals(null, result.size());
|
assertEquals(null, result.size());
|
||||||
|
|
||||||
|
@ -215,7 +232,7 @@ public class SearchCoordinatorSvcImplTest {
|
||||||
|
|
||||||
doAnswer(loadPids()).when(mySearchBuider).loadResourcesByPid(any(List.class), any(List.class), any(Set.class), anyBoolean(), any(EntityManager.class), any(FhirContext.class), same(myCallingDao));
|
doAnswer(loadPids()).when(mySearchBuider).loadResourcesByPid(any(List.class), any(List.class), any(Set.class), anyBoolean(), any(EntityManager.class), any(FhirContext.class), same(myCallingDao));
|
||||||
|
|
||||||
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient");
|
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective());
|
||||||
assertNotNull(result.getUuid());
|
assertNotNull(result.getUuid());
|
||||||
assertEquals(null, result.size());
|
assertEquals(null, result.size());
|
||||||
|
|
||||||
|
@ -265,7 +282,7 @@ public class SearchCoordinatorSvcImplTest {
|
||||||
|
|
||||||
doAnswer(loadPids()).when(mySearchBuider).loadResourcesByPid(any(List.class), any(List.class), any(Set.class), anyBoolean(), any(EntityManager.class), any(FhirContext.class), same(myCallingDao));
|
doAnswer(loadPids()).when(mySearchBuider).loadResourcesByPid(any(List.class), any(List.class), any(Set.class), anyBoolean(), any(EntityManager.class), any(FhirContext.class), same(myCallingDao));
|
||||||
|
|
||||||
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient");
|
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective());
|
||||||
assertNotNull(result.getUuid());
|
assertNotNull(result.getUuid());
|
||||||
assertEquals(90, result.size().intValue());
|
assertEquals(90, result.size().intValue());
|
||||||
|
|
||||||
|
@ -318,7 +335,8 @@ public class SearchCoordinatorSvcImplTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PageImpl<SearchResult>(results);
|
return new PageImpl<SearchResult>(results);
|
||||||
}});
|
}
|
||||||
|
});
|
||||||
search.setStatus(SearchStatusEnum.FINISHED);
|
search.setStatus(SearchStatusEnum.FINISHED);
|
||||||
}
|
}
|
||||||
}.start();
|
}.start();
|
||||||
|
@ -353,7 +371,7 @@ public class SearchCoordinatorSvcImplTest {
|
||||||
|
|
||||||
doAnswer(loadPids()).when(mySearchBuider).loadResourcesByPid(eq(pids), any(List.class), any(Set.class), anyBoolean(), any(EntityManager.class), any(FhirContext.class), same(myCallingDao));
|
doAnswer(loadPids()).when(mySearchBuider).loadResourcesByPid(eq(pids), any(List.class), any(Set.class), anyBoolean(), any(EntityManager.class), any(FhirContext.class), same(myCallingDao));
|
||||||
|
|
||||||
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient");
|
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective());
|
||||||
assertNull(result.getUuid());
|
assertNull(result.getUuid());
|
||||||
assertEquals(790, result.size().intValue());
|
assertEquals(790, result.size().intValue());
|
||||||
|
|
||||||
|
@ -375,7 +393,7 @@ public class SearchCoordinatorSvcImplTest {
|
||||||
pids = createPidSequence(10, 110);
|
pids = createPidSequence(10, 110);
|
||||||
doAnswer(loadPids()).when(mySearchBuider).loadResourcesByPid(eq(pids), any(List.class), any(Set.class), anyBoolean(), any(EntityManager.class), any(FhirContext.class), same(myCallingDao));
|
doAnswer(loadPids()).when(mySearchBuider).loadResourcesByPid(eq(pids), any(List.class), any(Set.class), anyBoolean(), any(EntityManager.class), any(FhirContext.class), same(myCallingDao));
|
||||||
|
|
||||||
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient");
|
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective());
|
||||||
assertNull(result.getUuid());
|
assertNull(result.getUuid());
|
||||||
assertEquals(100, result.size().intValue());
|
assertEquals(100, result.size().intValue());
|
||||||
|
|
||||||
|
|
|
@ -53,8 +53,8 @@ import ca.uhn.fhir.util.*;
|
||||||
|
|
||||||
public class GenericClientTest {
|
public class GenericClientTest {
|
||||||
|
|
||||||
private static FhirContext ourCtx;
|
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClientTest.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClientTest.class);
|
||||||
|
private static FhirContext ourCtx;
|
||||||
private HttpClient myHttpClient;
|
private HttpClient myHttpClient;
|
||||||
|
|
||||||
private HttpResponse myHttpResponse;
|
private HttpResponse myHttpResponse;
|
||||||
|
@ -71,30 +71,40 @@ public class GenericClientTest {
|
||||||
System.setProperty(BaseClient.HAPI_CLIENT_KEEPRESPONSES, "true");
|
System.setProperty(BaseClient.HAPI_CLIENT_KEEPRESPONSES, "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Patient createPatientP1() {
|
||||||
|
Patient p1 = new Patient();
|
||||||
|
p1.addIdentifier().setSystem("foo:bar").setValue("12345");
|
||||||
|
p1.addName().setFamily("Smith").addGiven("John");
|
||||||
|
return p1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bundle createTransactionBundleInput() {
|
||||||
|
Bundle input = new Bundle();
|
||||||
|
input.setType(BundleType.TRANSACTION);
|
||||||
|
input
|
||||||
|
.addEntry()
|
||||||
|
.setResource(createPatientP1())
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(HTTPVerb.POST);
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bundle createTransactionBundleOutput() {
|
||||||
|
Bundle output = new Bundle();
|
||||||
|
output.setType(BundleType.TRANSACTIONRESPONSE);
|
||||||
|
output
|
||||||
|
.addEntry()
|
||||||
|
.setResource(createPatientP1())
|
||||||
|
.getResponse()
|
||||||
|
.setLocation(createPatientP1().getId());
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
private String extractBody(ArgumentCaptor<HttpUriRequest> capt, int count) throws IOException {
|
private String extractBody(ArgumentCaptor<HttpUriRequest> capt, int count) throws IOException {
|
||||||
String body = IOUtils.toString(((HttpEntityEnclosingRequestBase) capt.getAllValues().get(count)).getEntity().getContent(), "UTF-8");
|
String body = IOUtils.toString(((HttpEntityEnclosingRequestBase) capt.getAllValues().get(count)).getEntity().getContent(), "UTF-8");
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
@Ignore
|
|
||||||
public void testInvalidCalls() {
|
|
||||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
|
||||||
|
|
||||||
try {
|
|
||||||
client.meta();
|
|
||||||
fail();
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
assertEquals("Can not call $meta operations on a DSTU1 client", e.getMessage());
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
client.operation();
|
|
||||||
fail();
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
assertEquals("Operations are only supported in FHIR DSTU2 and later. This client was created using a context configured for DSTU1", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getPatientFeedWithOneResult() {
|
private String getPatientFeedWithOneResult() {
|
||||||
return ClientR4Test.getPatientFeedWithOneResult(ourCtx);
|
return ClientR4Test.getPatientFeedWithOneResult(ourCtx);
|
||||||
// //@formatter:off
|
// //@formatter:off
|
||||||
|
@ -139,6 +149,81 @@ public class GenericClientTest {
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Test
|
||||||
|
public void testCacheControlNoStore() throws Exception {
|
||||||
|
|
||||||
|
String msg = ourCtx.newXmlParser().encodeResourceToString(new Bundle());
|
||||||
|
|
||||||
|
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||||
|
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
|
||||||
|
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
||||||
|
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
||||||
|
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
|
||||||
|
|
||||||
|
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||||
|
|
||||||
|
Bundle response = client.search()
|
||||||
|
.forResource(Observation.class)
|
||||||
|
.returnBundle(Bundle.class)
|
||||||
|
.cacheControl(new CacheControlDirective().setNoStore(true))
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals("http://example.com/fhir/Observation", capt.getValue().getURI().toString());
|
||||||
|
assertEquals(1, capt.getValue().getHeaders("Cache-Control").length);
|
||||||
|
assertEquals("no-store", capt.getValue().getHeaders("Cache-Control")[0].getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Test
|
||||||
|
public void testCacheControlNoStoreMaxResults() throws Exception {
|
||||||
|
|
||||||
|
String msg = ourCtx.newXmlParser().encodeResourceToString(new Bundle());
|
||||||
|
|
||||||
|
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||||
|
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
|
||||||
|
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
||||||
|
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
||||||
|
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
|
||||||
|
|
||||||
|
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||||
|
|
||||||
|
Bundle response = client.search()
|
||||||
|
.forResource(Observation.class)
|
||||||
|
.returnBundle(Bundle.class)
|
||||||
|
.cacheControl(new CacheControlDirective().setNoStore(true).setMaxResults(100))
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals("http://example.com/fhir/Observation", capt.getValue().getURI().toString());
|
||||||
|
assertEquals(1, capt.getValue().getHeaders("Cache-Control").length);
|
||||||
|
assertEquals("no-store, max-results=100", capt.getValue().getHeaders("Cache-Control")[0].getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Test
|
||||||
|
public void testCacheControlNoStoreNoCache() throws Exception {
|
||||||
|
|
||||||
|
String msg = ourCtx.newXmlParser().encodeResourceToString(new Bundle());
|
||||||
|
|
||||||
|
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||||
|
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
|
||||||
|
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
||||||
|
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
||||||
|
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
|
||||||
|
|
||||||
|
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||||
|
|
||||||
|
Bundle response = client.search()
|
||||||
|
.forResource(Observation.class)
|
||||||
|
.returnBundle(Bundle.class)
|
||||||
|
.cacheControl(new CacheControlDirective().setNoStore(true).setNoCache(true))
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals("http://example.com/fhir/Observation", capt.getValue().getURI().toString());
|
||||||
|
assertEquals(1, capt.getValue().getHeaders("Cache-Control").length);
|
||||||
|
assertEquals("no-cache, no-store", capt.getValue().getHeaders("Cache-Control")[0].getValue());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreatePopulatesIsCreated() throws Exception {
|
public void testCreatePopulatesIsCreated() throws Exception {
|
||||||
|
|
||||||
|
@ -165,13 +250,6 @@ public class GenericClientTest {
|
||||||
ourLog.info("lastResponseBody: {}", ((GenericClient) client).getLastResponseBody());
|
ourLog.info("lastResponseBody: {}", ((GenericClient) client).getLastResponseBody());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Patient createPatientP1() {
|
|
||||||
Patient p1 = new Patient();
|
|
||||||
p1.addIdentifier().setSystem("foo:bar").setValue("12345");
|
|
||||||
p1.addName().setFamily("Smith").addGiven("John");
|
|
||||||
return p1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateWithStringAutoDetectsEncoding() throws Exception {
|
public void testCreateWithStringAutoDetectsEncoding() throws Exception {
|
||||||
|
|
||||||
|
@ -401,6 +479,48 @@ public class GenericClientTest {
|
||||||
idx++;
|
idx++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore
|
||||||
|
public void testInvalidCalls() {
|
||||||
|
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||||
|
|
||||||
|
try {
|
||||||
|
client.meta();
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
assertEquals("Can not call $meta operations on a DSTU1 client", e.getMessage());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
client.operation();
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
assertEquals("Operations are only supported in FHIR DSTU2 and later. This client was created using a context configured for DSTU1", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoadPageAndReturnDstu1Bundle() throws Exception {
|
||||||
|
|
||||||
|
String msg = getPatientFeedWithOneResult();
|
||||||
|
|
||||||
|
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||||
|
|
||||||
|
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
||||||
|
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
||||||
|
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
|
||||||
|
|
||||||
|
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
|
||||||
|
|
||||||
|
IGenericClient client = ourCtx.newRestfulGenericClient("http://foo");
|
||||||
|
client
|
||||||
|
.loadPage()
|
||||||
|
.byUrl("http://example.com/page1")
|
||||||
|
.andReturnBundle(Bundle.class)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals("http://example.com/page1", capt.getValue().getURI().toString());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMissing() throws Exception {
|
public void testMissing() throws Exception {
|
||||||
|
|
||||||
|
@ -597,29 +717,6 @@ public class GenericClientTest {
|
||||||
assertEquals("name=" + longValue, string);
|
assertEquals("name=" + longValue, string);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLoadPageAndReturnDstu1Bundle() throws Exception {
|
|
||||||
|
|
||||||
String msg = getPatientFeedWithOneResult();
|
|
||||||
|
|
||||||
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
|
||||||
|
|
||||||
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
|
||||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
|
||||||
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
|
|
||||||
|
|
||||||
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
|
|
||||||
|
|
||||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://foo");
|
|
||||||
client
|
|
||||||
.loadPage()
|
|
||||||
.byUrl("http://example.com/page1")
|
|
||||||
.andReturnBundle(Bundle.class)
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
assertEquals("http://example.com/page1", capt.getValue().getURI().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSearchByCompartment() throws Exception {
|
public void testSearchByCompartment() throws Exception {
|
||||||
|
|
||||||
|
@ -1023,49 +1120,6 @@ public class GenericClientTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testSearchByTokenWithSystemAndNoCode() throws Exception {
|
|
||||||
|
|
||||||
final String msg = getPatientFeedWithOneResult();
|
|
||||||
|
|
||||||
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
|
||||||
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
|
|
||||||
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
|
||||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
|
||||||
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
|
|
||||||
@Override
|
|
||||||
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
|
||||||
return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
|
||||||
|
|
||||||
int idx = 0;
|
|
||||||
|
|
||||||
Bundle response = client.search()
|
|
||||||
.forResource("Patient")
|
|
||||||
.where(Patient.IDENTIFIER.hasSystemWithAnyCode("urn:foo"))
|
|
||||||
.returnBundle(Bundle.class)
|
|
||||||
.execute();
|
|
||||||
assertEquals("http://example.com/fhir/Patient?identifier=urn%3Afoo%7C", capt.getAllValues().get(idx++).getURI().toString());
|
|
||||||
|
|
||||||
response = client.search()
|
|
||||||
.forResource("Patient")
|
|
||||||
.where(Patient.IDENTIFIER.exactly().systemAndCode("urn:foo", null))
|
|
||||||
.returnBundle(Bundle.class)
|
|
||||||
.execute();
|
|
||||||
assertEquals("http://example.com/fhir/Patient?identifier=urn%3Afoo%7C", capt.getAllValues().get(idx++).getURI().toString());
|
|
||||||
|
|
||||||
response = client.search()
|
|
||||||
.forResource("Patient")
|
|
||||||
.where(Patient.IDENTIFIER.exactly().systemAndCode("urn:foo", ""))
|
|
||||||
.returnBundle(Bundle.class)
|
|
||||||
.execute();
|
|
||||||
assertEquals("http://example.com/fhir/Patient?identifier=urn%3Afoo%7C", capt.getAllValues().get(idx++).getURI().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for #192
|
* Test for #192
|
||||||
*/
|
*/
|
||||||
|
@ -1114,6 +1168,49 @@ public class GenericClientTest {
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Test
|
||||||
|
public void testSearchByTokenWithSystemAndNoCode() throws Exception {
|
||||||
|
|
||||||
|
final String msg = getPatientFeedWithOneResult();
|
||||||
|
|
||||||
|
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||||
|
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
|
||||||
|
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
||||||
|
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
||||||
|
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
|
||||||
|
@Override
|
||||||
|
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||||
|
return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||||
|
|
||||||
|
int idx = 0;
|
||||||
|
|
||||||
|
Bundle response = client.search()
|
||||||
|
.forResource("Patient")
|
||||||
|
.where(Patient.IDENTIFIER.hasSystemWithAnyCode("urn:foo"))
|
||||||
|
.returnBundle(Bundle.class)
|
||||||
|
.execute();
|
||||||
|
assertEquals("http://example.com/fhir/Patient?identifier=urn%3Afoo%7C", capt.getAllValues().get(idx++).getURI().toString());
|
||||||
|
|
||||||
|
response = client.search()
|
||||||
|
.forResource("Patient")
|
||||||
|
.where(Patient.IDENTIFIER.exactly().systemAndCode("urn:foo", null))
|
||||||
|
.returnBundle(Bundle.class)
|
||||||
|
.execute();
|
||||||
|
assertEquals("http://example.com/fhir/Patient?identifier=urn%3Afoo%7C", capt.getAllValues().get(idx++).getURI().toString());
|
||||||
|
|
||||||
|
response = client.search()
|
||||||
|
.forResource("Patient")
|
||||||
|
.where(Patient.IDENTIFIER.exactly().systemAndCode("urn:foo", ""))
|
||||||
|
.returnBundle(Bundle.class)
|
||||||
|
.execute();
|
||||||
|
assertEquals("http://example.com/fhir/Patient?identifier=urn%3Afoo%7C", capt.getAllValues().get(idx++).getURI().toString());
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@Test
|
@Test
|
||||||
public void testSearchIncludeRecursive() throws Exception {
|
public void testSearchIncludeRecursive() throws Exception {
|
||||||
|
@ -1429,28 +1526,6 @@ public class GenericClientTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Bundle createTransactionBundleOutput() {
|
|
||||||
Bundle output = new Bundle();
|
|
||||||
output.setType(BundleType.TRANSACTIONRESPONSE);
|
|
||||||
output
|
|
||||||
.addEntry()
|
|
||||||
.setResource(createPatientP1())
|
|
||||||
.getResponse()
|
|
||||||
.setLocation(createPatientP1().getId());
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Bundle createTransactionBundleInput() {
|
|
||||||
Bundle input = new Bundle();
|
|
||||||
input.setType(BundleType.TRANSACTION);
|
|
||||||
input
|
|
||||||
.addEntry()
|
|
||||||
.setResource(createPatientP1())
|
|
||||||
.getRequest()
|
|
||||||
.setMethod(HTTPVerb.POST);
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUpdate() throws Exception {
|
public void testUpdate() throws Exception {
|
||||||
|
|
||||||
|
@ -1558,32 +1633,6 @@ public class GenericClientTest {
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testValidateNonFluent() throws Exception {
|
|
||||||
|
|
||||||
OperationOutcome oo = new OperationOutcome();
|
|
||||||
oo.addIssue().setDiagnostics("OOOK");
|
|
||||||
|
|
||||||
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
|
||||||
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
|
|
||||||
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] {});
|
|
||||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
|
||||||
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(ourCtx.newXmlParser().encodeResourceToString(oo)), Charset.forName("UTF-8")));
|
|
||||||
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
|
||||||
|
|
||||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
|
||||||
|
|
||||||
Patient p1 = new Patient();
|
|
||||||
p1.addIdentifier().setSystem("foo:bar").setValue("12345");
|
|
||||||
p1.addName().setFamily("Smith").addGiven("John");
|
|
||||||
|
|
||||||
MethodOutcome resp = client.validate(p1);
|
|
||||||
assertEquals("http://example.com/fhir/Patient/$validate", capt.getValue().getURI().toString());
|
|
||||||
oo = (OperationOutcome) resp.getOperationOutcome();
|
|
||||||
assertEquals("OOOK", oo.getIssueFirstRep().getDiagnostics());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testVReadWithAbsoluteUrl() throws Exception {
|
public void testVReadWithAbsoluteUrl() throws Exception {
|
||||||
|
|
||||||
|
@ -1613,9 +1662,30 @@ public class GenericClientTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeClass
|
@Test
|
||||||
public static void beforeClass() {
|
public void testValidateNonFluent() throws Exception {
|
||||||
ourCtx = FhirContext.forR4();
|
|
||||||
|
OperationOutcome oo = new OperationOutcome();
|
||||||
|
oo.addIssue().setDiagnostics("OOOK");
|
||||||
|
|
||||||
|
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||||
|
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
|
||||||
|
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] {});
|
||||||
|
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
||||||
|
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(ourCtx.newXmlParser().encodeResourceToString(oo)), Charset.forName("UTF-8")));
|
||||||
|
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
||||||
|
|
||||||
|
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||||
|
|
||||||
|
Patient p1 = new Patient();
|
||||||
|
p1.addIdentifier().setSystem("foo:bar").setValue("12345");
|
||||||
|
p1.addName().setFamily("Smith").addGiven("John");
|
||||||
|
|
||||||
|
MethodOutcome resp = client.validate(p1);
|
||||||
|
assertEquals("http://example.com/fhir/Patient/$validate", capt.getValue().getURI().toString());
|
||||||
|
oo = (OperationOutcome) resp.getOperationOutcome();
|
||||||
|
assertEquals("OOOK", oo.getIssueFirstRep().getDiagnostics());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
|
@ -1623,4 +1693,9 @@ public class GenericClientTest {
|
||||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void beforeClass() {
|
||||||
|
ourCtx = FhirContext.forR4();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,16 @@
|
||||||
a search URL longer than 255 characters caused a mysterious failure. Thanks to
|
a search URL longer than 255 characters caused a mysterious failure. Thanks to
|
||||||
Chris Schuler and Bryn Rhodes for all of their help in reproducing this issue.
|
Chris Schuler and Bryn Rhodes for all of their help in reproducing this issue.
|
||||||
</action>
|
</action>
|
||||||
|
<action type="add">
|
||||||
|
JPA server now supports the use of the
|
||||||
|
<![CDATA[<code>Cache-Control</code>]]>
|
||||||
|
header in order to allow the client to selectively disable the
|
||||||
|
search result cache. This directive can also be used to disable result paging
|
||||||
|
and return results faster when only a small number of results is needed.
|
||||||
|
See the
|
||||||
|
<![CDATA[<a href="http://hapifhir.io/doc_jpa.html">JPA Page</a>]]>
|
||||||
|
for more information.
|
||||||
|
</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">
|
||||||
|
|
|
@ -198,13 +198,70 @@ public DaoConfig daoConfig() {
|
||||||
(see <a href="./apidocs-jpaserver/ca/uhn/fhir/jpa/dao/DaoConfig.html#setTreatReferencesAsLogical-java.util.Set-">JavaDoc</a>).
|
(see <a href="./apidocs-jpaserver/ca/uhn/fhir/jpa/dao/DaoConfig.html#setTreatReferencesAsLogical-java.util.Set-">JavaDoc</a>).
|
||||||
For example:
|
For example:
|
||||||
</p>
|
</p>
|
||||||
<code>
|
<div class="source">
|
||||||
|
<pre>
|
||||||
// Treat specific URL as logical
|
// Treat specific URL as logical
|
||||||
myDaoConfig.getTreatReferencesAsLogical().add("http://mysystem.com/ValueSet/cats-and-dogs");
|
myDaoConfig.getTreatReferencesAsLogical().add("http://mysystem.com/ValueSet/cats-and-dogs");
|
||||||
|
|
||||||
// Treat all references with given prefix as logical
|
// Treat all references with given prefix as logical
|
||||||
myDaoConfig.getTreatReferencesAsLogical().add("http://mysystem.com/mysystem-vs-*");
|
myDaoConfig.getTreatReferencesAsLogical().add("http://mysystem.com/mysystem-vs-*");
|
||||||
</code>
|
</pre>
|
||||||
|
</div>
|
||||||
|
<a name="search_result caching"/>
|
||||||
|
</subsection>
|
||||||
|
|
||||||
|
<subsection name="Search Result Caching">
|
||||||
|
|
||||||
|
<p>
|
||||||
|
By default, search results will be cached for one minute. This means that
|
||||||
|
if a client performs a search for <code>Patient?name=smith</code> and gets back
|
||||||
|
500 results, if a client performs the same search within 60000 milliseconds the
|
||||||
|
previously loaded search results will be returned again. This also means that
|
||||||
|
any new Patient resources named "Smith" within the last minute will not be
|
||||||
|
reflected in the results.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Under many normal scenarios this is a n acceptable performance tradeoff,
|
||||||
|
but in some cases it is not. If you want to disable caching, you have two
|
||||||
|
options:
|
||||||
|
</p>
|
||||||
|
<p><b>Globally Disable / Change Caching Timeout</b></p>
|
||||||
|
<p>
|
||||||
|
You can change the global cache using the following setting:
|
||||||
|
</p>
|
||||||
|
<div class="source">
|
||||||
|
<pre>
|
||||||
|
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<p><b>Disable Cache at the Request Level</b></p>
|
||||||
|
<p>
|
||||||
|
Clients can selectively disable caching for an individual request
|
||||||
|
using the Cache-Control header:
|
||||||
|
</p>
|
||||||
|
<div class="source">
|
||||||
|
<pre>
|
||||||
|
Cache-Control: nocache
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<p><b>Disable Paging at the Request Level</b></p>
|
||||||
|
<p>
|
||||||
|
If the client knows that they will only want a small number of results
|
||||||
|
(for example, a UI containing 20 results is being shown and the client
|
||||||
|
knows that they will never load the next page of results) the client
|
||||||
|
may also use the <code>nostore</code> directive along with a HAPI FHIR
|
||||||
|
extension called <code>max-results</code> in order to specify that
|
||||||
|
only the given number of results should be fetched. This directive
|
||||||
|
disabled paging entirely for the request and causes the request to
|
||||||
|
return immediately when the given number of results is found. This
|
||||||
|
can cause a noticeable performance improvement in some cases.
|
||||||
|
</p>
|
||||||
|
<div class="source">
|
||||||
|
<pre>
|
||||||
|
Cache-Control: nostore, max-results=20
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
</subsection>
|
</subsection>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -534,6 +534,34 @@
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section name="Additional Properties">
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This section contains ways of customizing the request sent by the client
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<subsection name="Cache-Control">
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The <code>Cache-Control</code> header can be used by the client in a request
|
||||||
|
to signal to the server (or any cache in front of it) that the client wants specific
|
||||||
|
behaviour from the cache, or wants the cache to not act on the request altogether.
|
||||||
|
Naturally, not all servers will honour this header.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To add a cache control directive in a request:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<macro name="snippet">
|
||||||
|
<param name="id" value="cacheControl" />
|
||||||
|
<param name="file" value="examples/src/main/java/example/ClientExamples.java" />
|
||||||
|
</macro>
|
||||||
|
|
||||||
|
</subsection>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</document>
|
</document>
|
||||||
|
|
Loading…
Reference in New Issue