Allow to set additional Http headers directly on the IClientExecutable

This commit is contained in:
Christian Ohr 2018-12-19 15:33:45 +01:00 committed by James Agnew
parent 66a669949d
commit cd8b2feb2f
5 changed files with 81 additions and 24 deletions

View File

@ -88,6 +88,21 @@ public interface IClientExecutable<T extends IClientExecutable<?, Y>, Y> {
*/
T encodedXml();
/**
* Set a HTTP header not explicitly defined in FHIR but commonly used in real-world scenarios. One
* important example is to set the Authorization header (e.g. Basic Auth or OAuth2-based Bearer auth),
* which tends to be cumbersome using {@link ca.uhn.fhir.rest.client.api.IClientInterceptor IClientInterceptors},
* particularly when REST clients shall be reused and are thus supposed to remain stateless.
* <p>It is the responsibility of the caller to care for proper encoding of the header value, e.g.
* using Base64.</p>
* <p>This is a short-cut alternative to using a corresponding client interceptor</p>
*
* @param theHeaderName header name
* @param theHeaderValue header value
* @return
*/
T withAdditionalHeader(String theHeaderName, String theHeaderValue);
/**
* Actually execute the client operation
*/

View File

@ -138,7 +138,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, null, null);
return invokeClient(getFhirContext(), binding, clientInvocation, null, false, false, null, null, null, null, null);
}
void forceConformanceCheck() {
@ -215,11 +215,12 @@ 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, null, null);
return invokeClient(theContext, binding, clientInvocation, null, null, theLogRequestAndResponse, null, null, null, null, null);
}
<T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation, EncodingEnum theEncoding, Boolean thePrettyPrint,
boolean theLogRequestAndResponse, SummaryEnum theSummaryMode, Set<String> theSubsetElements, CacheControlDirective theCacheControlDirective, String theCustomAcceptHeader) {
boolean theLogRequestAndResponse, SummaryEnum theSummaryMode, Set<String> theSubsetElements, CacheControlDirective theCacheControlDirective, String theCustomAcceptHeader,
Map<String, List<String>> theCustomHeaders) {
if (!myDontValidateConformance) {
myFactory.validateServerBaseIfConfiguredToDoSo(myUrlBase, myClient, this);
@ -280,6 +281,14 @@ public abstract class BaseClient implements IRestfulClient {
}
}
if (theCustomHeaders != null) {
for (Map.Entry<String, List<String>> customHeader: theCustomHeaders.entrySet()) {
for (String value: customHeader.getValue()) {
httpRequest.addHeader(customHeader.getKey(), value);
}
}
}
if (theLogRequestAndResponse) {
ourLog.info("Client invoking: {}", httpRequest);
String body = httpRequest.getRequestBodyFromStream();

View File

@ -54,7 +54,6 @@ import org.hl7.fhir.instance.model.api.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.*;
import java.util.Map.Entry;
@ -98,7 +97,8 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
private <T extends IBaseResource> T doReadOrVRead(final Class<T> theType, IIdType theId, boolean theVRead, ICallable<T> theNotModifiedHandler, String theIfVersionMatches, Boolean thePrettyPrint,
SummaryEnum theSummary, EncodingEnum theEncoding, Set<String> theSubsetElements, String theCustomAcceptHeaderValue) {
SummaryEnum theSummary, EncodingEnum theEncoding, Set<String> theSubsetElements, String theCustomAcceptHeaderValue,
Map<String, List<String>> theCustomHeaders) {
String resName = toResourceName(theType);
IIdType id = theId;
if (!id.hasBaseUrl()) {
@ -131,10 +131,10 @@ public class GenericClient extends BaseClient implements IGenericClient {
ResourceResponseHandler<T> binding = new ResourceResponseHandler<>(theType, (Class<? extends IBaseResource>) null, id, allowHtmlResponse);
if (theNotModifiedHandler == null) {
return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements, null, theCustomAcceptHeaderValue);
return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements, null, theCustomAcceptHeaderValue, theCustomHeaders);
}
try {
return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements, null, theCustomAcceptHeaderValue);
return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements, null, theCustomAcceptHeaderValue, theCustomHeaders);
} catch (NotModifiedException e) {
return theNotModifiedHandler.call();
}
@ -228,7 +228,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
@Override
public <T extends IBaseResource> T read(final Class<T> theType, UriDt theUrl) {
IdDt id = theUrl instanceof IdDt ? ((IdDt) theUrl) : new IdDt(theUrl);
return doReadOrVRead(theType, id, false, null, null, false, null, null, null, null);
return doReadOrVRead(theType, id, false, null, null, false, null, null, null, null, null);
}
@Override
@ -303,10 +303,10 @@ public class GenericClient extends BaseClient implements IGenericClient {
@Override
public <T extends IBaseResource> T vread(final Class<T> theType, IdDt theId) {
if (theId.hasVersionIdPart() == false) {
if (!theId.hasVersionIdPart()) {
throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_NO_VERSION_ID_FOR_VREAD, theId.getValue()));
}
return doReadOrVRead(theType, theId, true, null, null, false, null, null, null, null);
return doReadOrVRead(theType, theId, true, null, null, false, null, null, null, null, null);
}
@Override
@ -327,15 +327,17 @@ public class GenericClient extends BaseClient implements IGenericClient {
Boolean myPrettyPrint;
SummaryEnum mySummaryMode;
CacheControlDirective myCacheControlDirective;
Map<String, List<String>> myCustomHeaderValues = new HashMap<>();
private String myCustomAcceptHeaderValue;
private List<Class<? extends IBaseResource>> myPreferResponseTypes;
private boolean myQueryLogRequestAndResponse;
private HashSet<String> mySubsetElements;
private Set<String> mySubsetElements;
public String getCustomAcceptHeaderValue() {
return myCustomAcceptHeaderValue;
}
@SuppressWarnings("unchecked")
@Override
public T accept(String theHeaderValue) {
myCustomAcceptHeaderValue = theHeaderValue;
@ -350,6 +352,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
return (T) this;
}
@SuppressWarnings("unchecked")
@Override
public T cacheControl(CacheControlDirective theCacheControlDirective) {
myCacheControlDirective = theCacheControlDirective;
@ -367,6 +370,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
return (T) this;
}
@SuppressWarnings("unchecked")
@Override
public T encoded(EncodingEnum theEncoding) {
Validate.notNull(theEncoding, "theEncoding must not be null");
@ -388,6 +392,18 @@ public class GenericClient extends BaseClient implements IGenericClient {
return (T) this;
}
@SuppressWarnings("unchecked")
@Override
public T withAdditionalHeader(String theHeaderName, String theHeaderValue) {
Objects.requireNonNull(theHeaderName, "headerName cannot be null");
Objects.requireNonNull(theHeaderValue, "headerValue cannot be null");
if (!myCustomHeaderValues.containsKey(theHeaderName)) {
myCustomHeaderValues.put(theHeaderName, new ArrayList<>());
}
myCustomHeaderValues.get(theHeaderName).add(theHeaderValue);
return (T) this;
}
protected EncodingEnum getParamEncoding() {
return myParamEncoding;
}
@ -403,7 +419,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
return toTypeList(theDefault);
}
protected HashSet<String> getSubsetElements() {
protected Set<String> getSubsetElements() {
return mySubsetElements;
}
@ -412,7 +428,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
myLastRequest = theInvocation.asHttpRequest(getServerBase(), theParams, getEncoding(), myPrettyPrint);
}
Z resp = invokeClient(myContext, theHandler, theInvocation, myParamEncoding, myPrettyPrint, myQueryLogRequestAndResponse || myLogRequestAndResponse, mySummaryMode, mySubsetElements, myCacheControlDirective, myCustomAcceptHeaderValue);
Z resp = invokeClient(myContext, theHandler, theInvocation, myParamEncoding, myPrettyPrint, myQueryLogRequestAndResponse || myLogRequestAndResponse, mySummaryMode, mySubsetElements, myCacheControlDirective, myCustomAcceptHeaderValue, myCustomHeaderValues);
return resp;
}
@ -612,7 +628,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
Validate.notNull(theResource, "theResource can not be null");
IIdType id = theResource.getIdElement();
Validate.notNull(id, "theResource.getIdElement() can not be null");
if (id.hasResourceType() == false || id.hasIdPart() == false) {
if (!id.hasResourceType() || !id.hasIdPart()) {
throw new IllegalArgumentException("theResource.getId() must contain a resource type and logical ID at a minimum (e.g. Patient/1234), found: " + id.getValue());
}
myId = id;
@ -622,7 +638,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
@Override
public IDeleteTyped resourceById(IIdType theId) {
Validate.notNull(theId, "theId can not be null");
if (theId.hasResourceType() == false || theId.hasIdPart() == false) {
if (!theId.hasResourceType() || !theId.hasIdPart()) {
throw new IllegalArgumentException("theId must contain a resource type and logical ID at a minimum (e.g. Patient/1234)found: " + theId.getValue());
}
myId = theId;
@ -773,7 +789,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
@Override
public IHistoryUntyped onInstance(IIdType theId) {
if (theId.hasResourceType() == false) {
if (!theId.hasResourceType()) {
throw new IllegalArgumentException("Resource ID does not have a resource type: " + theId.getValue());
}
myId = theId;
@ -849,7 +865,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
continue;
}
String relation = ((IPrimitiveType<?>) rel.get(0)).getValueAsString();
if (theWantRel.equals(relation) || (theWantRel == PREVIOUS && PREV.equals(relation))) {
if (theWantRel.equals(relation) || (PREVIOUS.equals(theWantRel) && PREV.equals(relation))) {
List<IBase> urls = linkDef.getChildByName("url").getAccessor().getValues(nextLink);
if (urls == null || urls.isEmpty()) {
continue;
@ -1514,9 +1530,9 @@ public class GenericClient extends BaseClient implements IGenericClient {
@Override
public Object execute() {// AAA
if (myId.hasVersionIdPart()) {
return doReadOrVRead(myType.getImplementingClass(), myId, true, myNotModifiedHandler, myIfVersionMatches, myPrettyPrint, mySummaryMode, myParamEncoding, getSubsetElements(), getCustomAcceptHeaderValue());
return doReadOrVRead(myType.getImplementingClass(), myId, true, myNotModifiedHandler, myIfVersionMatches, myPrettyPrint, mySummaryMode, myParamEncoding, getSubsetElements(), getCustomAcceptHeaderValue(), myCustomHeaderValues);
}
return doReadOrVRead(myType.getImplementingClass(), myId, false, myNotModifiedHandler, myIfVersionMatches, myPrettyPrint, mySummaryMode, myParamEncoding, getSubsetElements(), getCustomAcceptHeaderValue());
return doReadOrVRead(myType.getImplementingClass(), myId, false, myNotModifiedHandler, myIfVersionMatches, myPrettyPrint, mySummaryMode, myParamEncoding, getSubsetElements(), getCustomAcceptHeaderValue(), myCustomHeaderValues);
}
@Override
@ -2256,7 +2272,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
private static void addParam(Map<String, List<String>> params, String parameterName, String parameterValue) {
if (!params.containsKey(parameterName)) {
params.put(parameterName, new ArrayList<>());
params.put(parameterName, new ArrayList<String>());
}
params.get(parameterName).add(parameterValue);
}

View File

@ -33,12 +33,15 @@ import java.util.Objects;
/**
* This interceptor adds arbitrary header values to requests made by the client.
*
* This is now also possible directly on the Fluent Client API by calling
* {@link ca.uhn.fhir.rest.gclient.IClientExecutable#withAdditionalHeader(String, String)}
*/
public class AdditionalRequestHeadersInterceptor implements IClientInterceptor {
private final Map<String, List<String>> additionalHttpHeaders = new HashMap<>();
public AdditionalRequestHeadersInterceptor() {
this(new HashMap<String, List<String>>());
this(new HashMap<>());
}
public AdditionalRequestHeadersInterceptor(Map<String, List<String>> additionalHttpHeaders) {
@ -84,7 +87,7 @@ public class AdditionalRequestHeadersInterceptor implements IClientInterceptor {
*/
private List<String> getHeaderValues(String headerName) {
if (additionalHttpHeaders.get(headerName) == null) {
additionalHttpHeaders.put(headerName, new ArrayList<String>());
additionalHttpHeaders.put(headerName, new ArrayList<>());
}
return additionalHttpHeaders.get(headerName);
}

View File

@ -315,7 +315,7 @@ public class GenericClientTest {
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
MethodOutcome outcome = client.create().resource(p1).execute();
MethodOutcome outcome = client.create().resource(p1).withAdditionalHeader("myHeaderName", "myHeaderValue").execute();
assertEquals("44", outcome.getId().getIdPart());
assertEquals("22", outcome.getId().getVersionIdPart());
@ -325,6 +325,7 @@ public class GenericClientTest {
assertEquals("POST", capt.getValue().getMethod());
assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length);
assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
assertEquals("myHeaderValue", capt.getValue().getFirstHeader("myHeaderName").getValue());
count++;
/*
@ -415,17 +416,20 @@ public class GenericClientTest {
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
OperationOutcome outcome = (OperationOutcome) client.delete().resourceById("Patient", "123").execute();
OperationOutcome outcome = (OperationOutcome) client.delete().resourceById("Patient", "123")
.withAdditionalHeader("myHeaderName", "myHeaderValue").execute();
assertEquals("http://example.com/fhir/Patient/123", capt.getValue().getURI().toString());
assertEquals("DELETE", capt.getValue().getMethod());
assertEquals("testDelete01", outcome.getIssueFirstRep().getLocation().get(0).getValue());
assertEquals("myHeaderValue", capt.getValue().getFirstHeader("myHeaderName").getValue());
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("LKJHLKJGLKJKLL"), Charset.forName("UTF-8")));
outcome = (OperationOutcome) client.delete().resourceById(new IdType("Location", "123", "456")).prettyPrint().encodedJson().execute();
assertEquals("http://example.com/fhir/Location/123?_pretty=true", capt.getAllValues().get(1).getURI().toString());
assertEquals("DELETE", capt.getValue().getMethod());
assertEquals(null, outcome);
}
@ -455,8 +459,10 @@ public class GenericClientTest {
.history()
.onServer()
.andReturnBundle(Bundle.class)
.withAdditionalHeader("myHeaderName", "myHeaderValue")
.execute();
assertEquals("http://example.com/fhir/_history", capt.getAllValues().get(idx).getURI().toString());
assertEquals("myHeaderValue", capt.getValue().getFirstHeader("myHeaderName").getValue());
assertEquals(1, response.getEntry().size());
idx++;
@ -464,9 +470,13 @@ public class GenericClientTest {
.history()
.onType(Patient.class)
.andReturnBundle(Bundle.class)
.withAdditionalHeader("myHeaderName", "myHeaderValue1")
.withAdditionalHeader("myHeaderName", "myHeaderValue2")
.execute();
assertEquals("http://example.com/fhir/Patient/_history", capt.getAllValues().get(idx).getURI().toString());
assertEquals(1, response.getEntry().size());
assertEquals("myHeaderValue1", capt.getValue().getHeaders("myHeaderName")[0].getValue());
assertEquals("myHeaderValue2", capt.getValue().getHeaders("myHeaderName")[1].getValue());
idx++;
response = client
@ -1145,10 +1155,12 @@ public class GenericClientTest {
.forResource(Patient.class)
.withTag("urn:foo", "123")
.withTag("urn:bar", "456")
.withAdditionalHeader("myHeaderName", "myHeaderValue")
.returnBundle(Bundle.class)
.execute();
assertEquals("http://example.com/fhir/Patient?_tag=urn%3Afoo%7C123&_tag=urn%3Abar%7C456", capt.getValue().getURI().toString());
assertEquals("myHeaderValue", capt.getValue().getFirstHeader("myHeaderName").getValue());
}
@ -1169,10 +1181,12 @@ public class GenericClientTest {
Bundle response = client.search()
.forResource("Patient")
.where(Patient.IDENTIFIER.exactly().systemAndCode("http://example.com/fhir", "ZZZ"))
.withAdditionalHeader("myHeaderName", "myHeaderValue")
.returnBundle(Bundle.class)
.execute();
assertEquals("http://example.com/fhir/Patient?identifier=http%3A%2F%2Fexample.com%2Ffhir%7CZZZ", capt.getValue().getURI().toString());
assertEquals("myHeaderValue", capt.getValue().getFirstHeader("myHeaderName").getValue());
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
response = client.search()