Add support for Cache-Control header in JPA server and client
This commit is contained in:
parent
4d1ab2734f
commit
ce720f5601
|
@ -1,5 +1,6 @@
|
|||
package example;
|
||||
|
||||
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
|
||||
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.api.*;
|
||||
import ca.uhn.fhir.rest.client.interceptor.*;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
|
||||
public class ClientExamples {
|
||||
|
||||
|
@ -52,6 +54,26 @@ public class ClientExamples {
|
|||
// 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")
|
||||
public void createOkHttp() {
|
||||
// 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 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 Charset CHARSET_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_VALPREFIX_BASIC = "Basic ";
|
||||
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_ENCODING = "Content-Encoding";
|
||||
public static final String HEADER_CONTENT_LOCATION = "Content-Location";
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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.SummaryEnum;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
@ -38,6 +39,12 @@ public interface IClientExecutable<T extends IClientExecutable<?,Y>, Y> {
|
|||
@Deprecated
|
||||
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.
|
||||
* 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.Set;
|
||||
|
||||
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
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) {
|
||||
BaseHttpClientInvocation clientInvocation = new HttpGetClientInvocation(getFhirContext(), theUrl);
|
||||
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() {
|
||||
|
@ -198,11 +199,11 @@ public abstract class BaseClient implements IRestfulClient {
|
|||
}
|
||||
|
||||
<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,
|
||||
boolean theLogRequestAndResponse, SummaryEnum theSummaryMode, Set<String> theSubsetElements) {
|
||||
boolean theLogRequestAndResponse, SummaryEnum theSummaryMode, Set<String> theSubsetElements, CacheControlDirective theCacheControlDirective) {
|
||||
|
||||
if (!myDontValidateConformance) {
|
||||
myFactory.validateServerBaseIfConfiguredToDoSo(myUrlBase, myClient, this);
|
||||
|
@ -244,6 +245,18 @@ public abstract class BaseClient implements IRestfulClient {
|
|||
|
||||
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) {
|
||||
ourLog.info("Client invoking: {}", httpRequest);
|
||||
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!
|
||||
*/
|
||||
|
|
|
@ -120,10 +120,10 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
ResourceResponseHandler<T> binding = new ResourceResponseHandler<T>(theType, (Class<? extends IBaseResource>) null, id, allowHtmlResponse);
|
||||
|
||||
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 {
|
||||
return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements);
|
||||
return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements, null);
|
||||
} catch (NotModifiedException e) {
|
||||
return theNotModifiedHandler.call();
|
||||
}
|
||||
|
@ -373,6 +373,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
private boolean myQueryLogRequestAndResponse;
|
||||
private HashSet<String> mySubsetElements;
|
||||
protected SummaryEnum mySummaryMode;
|
||||
protected CacheControlDirective myCacheControlDirective;
|
||||
|
||||
@Deprecated // override deprecated method
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -382,6 +383,12 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
return (T) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T cacheControl(CacheControlDirective theCacheControlDirective) {
|
||||
myCacheControlDirective = theCacheControlDirective;
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
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) {
|
||||
// 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()) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -36,10 +36,7 @@ import ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils;
|
|||
import ca.uhn.fhir.jpa.util.xmlpatch.XmlPatchUtils;
|
||||
import ca.uhn.fhir.model.api.*;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.PatchTypeEnum;
|
||||
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.*;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
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
|
||||
|
|
|
@ -107,6 +107,7 @@ public class DaoConfig {
|
|||
private Set<String> myTreatBaseUrlsAsLocal = new HashSet<String>();
|
||||
private Set<String> myTreatReferencesAsLogical = new HashSet<String>(DEFAULT_LOGICAL_BASE_URLS);
|
||||
private boolean myAutoCreatePlaceholderReferenceTargets;
|
||||
private Integer myCacheControlNoStoreMaxResultsUpperLimit = 1000;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -131,6 +132,26 @@ public class DaoConfig {
|
|||
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,
|
||||
* 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.
|
||||
*/
|
||||
public void setInterceptors(List<IServerInterceptor> theInterceptors) {
|
||||
myInterceptors = theInterceptors;
|
||||
public void setInterceptors(IServerInterceptor... theInterceptor) {
|
||||
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
|
||||
* searches may potentially return slightly out-of-date results.
|
||||
* </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() {
|
||||
return myReuseCachedSearchResultsForMillis;
|
||||
|
@ -449,6 +478,11 @@ public class DaoConfig {
|
|||
* This approach can improve performance, especially under heavy load, but can also mean that
|
||||
* searches may potentially return slightly out-of-date results.
|
||||
* </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) {
|
||||
myReuseCachedSearchResultsForMillis = theReuseCachedSearchResultsForMillis;
|
||||
|
@ -925,11 +959,8 @@ public class DaoConfig {
|
|||
/**
|
||||
* This may be used to optionally register server interceptors directly against the DAOs.
|
||||
*/
|
||||
public void setInterceptors(IServerInterceptor... theInterceptor) {
|
||||
setInterceptors(new ArrayList<IServerInterceptor>());
|
||||
if (theInterceptor != null && theInterceptor.length != 0) {
|
||||
getInterceptors().addAll(Arrays.asList(theInterceptor));
|
||||
}
|
||||
public void setInterceptors(List<IServerInterceptor> theInterceptors) {
|
||||
myInterceptors = theInterceptors;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -65,7 +65,7 @@ public class FhirResourceDaoPatientDstu2 extends FhirResourceDaoDstu2<Patient>im
|
|||
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
|
||||
|
|
|
@ -24,6 +24,7 @@ import java.util.Collections;
|
|||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||
import org.hl7.fhir.dstu3.model.Patient;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
@ -66,7 +67,7 @@ public class FhirResourceDaoPatientDstu3 extends FhirResourceDaoDstu3<Patient>im
|
|||
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
|
||||
|
|
|
@ -24,6 +24,7 @@ import java.util.Collections;
|
|||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
@ -66,7 +67,7 @@ public class FhirResourceDaoPatientR4 extends FhirResourceDaoR4<Patient>implemen
|
|||
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
|
||||
|
|
|
@ -20,18 +20,19 @@ package ca.uhn.fhir.jpa.search;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.IDao;
|
||||
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ISearchCoordinatorSvc {
|
||||
|
||||
List<Long> getResources(String theUuid, int theFrom, int theTo);
|
||||
|
||||
IBundleProvider registerSearch(IDao theCallingDao, SearchParameterMap theParams, String theResourceType);
|
||||
|
||||
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 ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
|
@ -55,7 +57,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||
public static final int DEFAULT_SYNC_SIZE = 250;
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchCoordinatorSvcImpl.class);
|
||||
|
||||
private final ConcurrentHashMap<String, SearchTask> myIdToSearchTask = new ConcurrentHashMap<String, SearchTask>();
|
||||
@Autowired
|
||||
private FhirContext myContext;
|
||||
@Autowired
|
||||
|
@ -63,7 +65,6 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||
@Autowired
|
||||
private EntityManager myEntityManager;
|
||||
private ExecutorService myExecutor;
|
||||
private final ConcurrentHashMap<String, SearchTask> myIdToSearchTask = new ConcurrentHashMap<String, SearchTask>();
|
||||
private Integer myLoadingThrottleForUnitTests = null;
|
||||
private long myMaxMillisToWaitForRemoteResults = DateUtils.MILLIS_PER_MINUTE;
|
||||
private boolean myNeverUseLocalSearchForUnitTests;
|
||||
|
@ -186,7 +187,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||
}
|
||||
|
||||
@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();
|
||||
final String searchUuid = UUID.randomUUID().toString();
|
||||
|
||||
|
@ -194,7 +195,21 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||
final ISearchBuilder sb = theCallingDao.newSearchBuilder();
|
||||
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
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager);
|
||||
|
@ -209,6 +224,9 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||
Iterator<Long> resultIter = sb.createQuery(theParams, searchUuid);
|
||||
while (resultIter.hasNext()) {
|
||||
pids.add(resultIter.next());
|
||||
if (loadSynchronousUpTo != null && pids.size() >= loadSynchronousUpTo) {
|
||||
break;
|
||||
}
|
||||
if (theParams.getLoadSynchronousUpTo() != null && pids.size() >= theParams.getLoadSynchronousUpTo()) {
|
||||
break;
|
||||
}
|
||||
|
@ -238,9 +256,13 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||
* See if there are any cached searches whose results we can return
|
||||
* instead
|
||||
*/
|
||||
boolean useCache = true;
|
||||
if (theCacheControlDirective != null && theCacheControlDirective.isNoCache() == true) {
|
||||
useCache = false;
|
||||
}
|
||||
final String queryString = theParams.toNormalizedQueryString(myContext);
|
||||
if (theParams.getEverythingMode() == null) {
|
||||
if (myDaoConfig.getReuseCachedSearchResultsForMillis() != null) {
|
||||
if (myDaoConfig.getReuseCachedSearchResultsForMillis() != null && useCache) {
|
||||
|
||||
final Date createdCutoff = new Date(System.currentTimeMillis() - myDaoConfig.getReuseCachedSearchResultsForMillis());
|
||||
final String resourceType = theResourceType;
|
||||
|
@ -401,16 +423,16 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||
|
||||
public class SearchTask implements Callable<Void> {
|
||||
|
||||
private boolean myAbortRequested;
|
||||
private final IDao myCallingDao;
|
||||
private final CountDownLatch myCompletionLatch;
|
||||
private int myCountSaved = 0;
|
||||
private final CountDownLatch myInitialCollectionLatch = new CountDownLatch(1);
|
||||
private final SearchParameterMap myParams;
|
||||
private final String myResourceType;
|
||||
private final Search mySearch;
|
||||
private final ArrayList<Long> mySyncedPids = new ArrayList<Long>();
|
||||
private final ArrayList<Long> myUnsyncedPids = new ArrayList<Long>();
|
||||
private boolean myAbortRequested;
|
||||
private int myCountSaved = 0;
|
||||
private String mySearchUuid;
|
||||
|
||||
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;
|
||||
|
||||
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.jpa.dao.*;
|
||||
import ca.uhn.fhir.jpa.dao.data.*;
|
||||
import ca.uhn.fhir.jpa.entity.*;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.dao.IDao;
|
||||
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.model.dstu2.resource.Patient;
|
||||
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.StringParam;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
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;
|
||||
|
||||
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)
|
||||
public class SearchCoordinatorSvcImplTest {
|
||||
|
||||
private static FhirContext ourCtx = FhirContext.forDstu3();
|
||||
@Captor
|
||||
ArgumentCaptor<Iterable<SearchResult>> mySearchResultIterCaptor;
|
||||
@Mock
|
||||
private IDao myCallingDao;
|
||||
@Mock
|
||||
|
@ -49,10 +68,6 @@ public class SearchCoordinatorSvcImplTest {
|
|||
private ISearchIncludeDao mySearchIncludeDao;
|
||||
@Mock
|
||||
private ISearchResultDao mySearchResultDao;
|
||||
@Captor
|
||||
ArgumentCaptor<Iterable<SearchResult>> mySearchResultIterCaptor;
|
||||
|
||||
|
||||
private SearchCoordinatorSvcImpl mySvc;
|
||||
|
||||
@Mock
|
||||
|
@ -63,6 +78,7 @@ public class SearchCoordinatorSvcImplTest {
|
|||
public void after() {
|
||||
verify(myCallingDao, atMost(myExpectedNumberOfSearchBuildersCreated)).newSearchBuilder();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
|
||||
|
@ -89,7 +105,8 @@ public class SearchCoordinatorSvcImplTest {
|
|||
provider.setEntityManager(myEntityManager);
|
||||
provider.setContext(ourCtx);
|
||||
return null;
|
||||
}}).when(myCallingDao).injectDependenciesIntoBundleProvider(any(PersistedJpaBundleProvider.class));
|
||||
}
|
||||
}).when(myCallingDao).injectDependenciesIntoBundleProvider(any(PersistedJpaBundleProvider.class));
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient");
|
||||
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective());
|
||||
assertNotNull(result.getUuid());
|
||||
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));
|
||||
|
||||
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient");
|
||||
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective());
|
||||
assertNotNull(result.getUuid());
|
||||
assertEquals(null, result.size());
|
||||
|
||||
|
@ -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));
|
||||
|
||||
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient");
|
||||
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective());
|
||||
assertNotNull(result.getUuid());
|
||||
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));
|
||||
|
||||
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient");
|
||||
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective());
|
||||
assertNotNull(result.getUuid());
|
||||
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));
|
||||
|
||||
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient");
|
||||
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective());
|
||||
assertNotNull(result.getUuid());
|
||||
assertEquals(90, result.size().intValue());
|
||||
|
||||
|
@ -318,7 +335,8 @@ public class SearchCoordinatorSvcImplTest {
|
|||
}
|
||||
|
||||
return new PageImpl<SearchResult>(results);
|
||||
}});
|
||||
}
|
||||
});
|
||||
search.setStatus(SearchStatusEnum.FINISHED);
|
||||
}
|
||||
}.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));
|
||||
|
||||
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient");
|
||||
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective());
|
||||
assertNull(result.getUuid());
|
||||
assertEquals(790, result.size().intValue());
|
||||
|
||||
|
@ -375,7 +393,7 @@ public class SearchCoordinatorSvcImplTest {
|
|||
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));
|
||||
|
||||
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient");
|
||||
IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective());
|
||||
assertNull(result.getUuid());
|
||||
assertEquals(100, result.size().intValue());
|
||||
|
||||
|
|
|
@ -53,8 +53,8 @@ import ca.uhn.fhir.util.*;
|
|||
|
||||
public class GenericClientTest {
|
||||
|
||||
private static FhirContext ourCtx;
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClientTest.class);
|
||||
private static FhirContext ourCtx;
|
||||
private HttpClient myHttpClient;
|
||||
|
||||
private HttpResponse myHttpResponse;
|
||||
|
@ -71,30 +71,40 @@ public class GenericClientTest {
|
|||
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 {
|
||||
String body = IOUtils.toString(((HttpEntityEnclosingRequestBase) capt.getAllValues().get(count)).getEntity().getContent(), "UTF-8");
|
||||
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() {
|
||||
return ClientR4Test.getPatientFeedWithOneResult(ourCtx);
|
||||
// //@formatter:off
|
||||
|
@ -139,6 +149,81 @@ public class GenericClientTest {
|
|||
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
|
||||
public void testCreatePopulatesIsCreated() throws Exception {
|
||||
|
||||
|
@ -165,13 +250,6 @@ public class GenericClientTest {
|
|||
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
|
||||
public void testCreateWithStringAutoDetectsEncoding() throws Exception {
|
||||
|
||||
|
@ -401,6 +479,48 @@ public class GenericClientTest {
|
|||
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
|
||||
public void testMissing() throws Exception {
|
||||
|
||||
|
@ -597,29 +717,6 @@ public class GenericClientTest {
|
|||
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
|
||||
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
|
||||
*/
|
||||
|
@ -1114,6 +1168,49 @@ public class GenericClientTest {
|
|||
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")
|
||||
@Test
|
||||
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
|
||||
public void testUpdate() throws Exception {
|
||||
|
||||
|
@ -1558,32 +1633,6 @@ public class GenericClientTest {
|
|||
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
|
||||
public void testVReadWithAbsoluteUrl() throws Exception {
|
||||
|
||||
|
@ -1613,9 +1662,30 @@ public class GenericClientTest {
|
|||
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
ourCtx = FhirContext.forR4();
|
||||
@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());
|
||||
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
|
@ -1623,4 +1693,9 @@ public class GenericClientTest {
|
|||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
ourCtx = FhirContext.forR4();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,6 +27,16 @@
|
|||
has been changed to only accept "url".
|
||||
Thanks to Avinash Shanbhag for reporting!
|
||||
</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 version="3.0.0" date="2017-09-27">
|
||||
<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>).
|
||||
For example:
|
||||
</p>
|
||||
<code>
|
||||
<div class="source">
|
||||
<pre>
|
||||
// Treat specific URL as logical
|
||||
myDaoConfig.getTreatReferencesAsLogical().add("http://mysystem.com/ValueSet/cats-and-dogs");
|
||||
|
||||
// Treat all references with given prefix as logical
|
||||
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>
|
||||
|
||||
</section>
|
||||
|
|
|
@ -534,6 +534,34 @@
|
|||
|
||||
</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>
|
||||
|
||||
</document>
|
||||
|
|
Loading…
Reference in New Issue