Merge branch 'master' into 2024-11-gg-slice-validation
This commit is contained in:
commit
275df1aba6
|
@ -8,7 +8,9 @@
|
|||
* Fix special case r5 loading of terminology to fix validation error on ExampleScenario
|
||||
* Improve handling of JSON format errors
|
||||
* Fix bug where extension slices defined in other profiles are not found when processing slices based on extension
|
||||
* Validate slice expressions where possible
|
||||
* Validate fhirpath expression in slice discriminators
|
||||
* Fix slicing by type and profile to allow multiple options per slice
|
||||
* List measure choices when a match by version can't be found
|
||||
|
||||
## Other code changes
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package org.hl7.fhir.convertors.misc;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
|
@ -395,7 +394,7 @@ public class ICD11Generator {
|
|||
|
||||
|
||||
private JsonObject fetchJson(String source) throws IOException {
|
||||
HTTPResult res = ManagedWebAccess.builder().withAccept("application/json").withHeader("API-Version", "v2").withHeader("Accept-Language", "en").get(source);
|
||||
HTTPResult res = ManagedWebAccess.accessor().withHeader("API-Version", "v2").withHeader("Accept-Language", "en").get(source,"application/json");
|
||||
res.checkThrowException();
|
||||
return JsonParser.parseObject(res.getContent());
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.hl7.fhir.r4.terminologies.JurisdictionUtilities;
|
|||
import org.hl7.fhir.utilities.CSVReader;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.filesystem.ManagedFileAccess;
|
||||
import org.hl7.fhir.utilities.http.ManagedWebAccess;
|
||||
import org.hl7.fhir.utilities.json.model.JsonArray;
|
||||
import org.hl7.fhir.utilities.json.model.JsonObject;
|
||||
import org.hl7.fhir.utilities.json.model.JsonProperty;
|
||||
|
@ -45,17 +46,16 @@ public class VSACImporter extends OIDBasedValueSetImporter {
|
|||
|
||||
public static void main(String[] args) throws FHIRException, IOException, ParseException, URISyntaxException {
|
||||
VSACImporter self = new VSACImporter();
|
||||
self.process(args[0], args[1], args[2], "true".equals(args[3]), "true".equals(args[4]));
|
||||
self.process(args[0], args[1], "true".equals(args[2]), "true".equals(args[3]));
|
||||
}
|
||||
|
||||
private void process(String source, String dest, String apiKey, boolean onlyNew, boolean onlyActive) throws FHIRException, IOException, URISyntaxException {
|
||||
private void process(String source, String dest, boolean onlyNew, boolean onlyActive) throws FHIRException, IOException, URISyntaxException {
|
||||
CSVReader csv = new CSVReader(ManagedFileAccess.inStream(source));
|
||||
csv.readHeaders();
|
||||
Map<String, String> errs = new HashMap<>();
|
||||
|
||||
ManagedWebAccess.loadFromFHIRSettings();
|
||||
FHIRToolingClient fhirToolingClient = new FHIRToolingClient("https://cts.nlm.nih.gov/fhir", "fhir/vsac");
|
||||
fhirToolingClient.setUsername("apikey");
|
||||
fhirToolingClient.setPassword(apiKey);
|
||||
fhirToolingClient.setTimeoutNormal(30000);
|
||||
fhirToolingClient.setTimeoutExpand(30000);
|
||||
|
||||
|
@ -121,6 +121,7 @@ public class VSACImporter extends OIDBasedValueSetImporter {
|
|||
oo.addIssue().setSeverity(IssueSeverity.ERROR).setCode(IssueType.EXCEPTION).setDiagnostics(errs.get(oid)).addLocation(oid);
|
||||
}
|
||||
new JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(ManagedFileAccess.outStream(Utilities.path(dest, "other", "OperationOutcome-vsac-errors.json")), oo);
|
||||
System.out.println();
|
||||
System.out.println("Done. " + i + " ValueSets in "+Utilities.describeDuration(System.currentTimeMillis() - tt));
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,12 @@
|
|||
<artifactId>org.hl7.fhir.utilities</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- UCUM -->
|
||||
<dependency>
|
||||
<groupId>org.fhir</groupId>
|
||||
|
@ -81,6 +87,24 @@
|
|||
</dependency>
|
||||
|
||||
<!-- Test dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>mockwebserver</artifactId>
|
||||
<optional>true</optional>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okio</groupId>
|
||||
<artifactId>okio</artifactId>
|
||||
<optional>true</optional>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
|
|
|
@ -33,42 +33,21 @@ import java.io.ByteArrayOutputStream;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpEntityEnclosingRequest;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpDelete;
|
||||
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpOptions;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.methods.HttpPut;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.conn.params.ConnRoutePNames;
|
||||
import org.apache.http.entity.ByteArrayEntity;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.apache.http.params.HttpConnectionParams;
|
||||
import org.apache.http.params.HttpParams;
|
||||
|
||||
import org.hl7.fhir.dstu2.formats.IParser;
|
||||
import org.hl7.fhir.dstu2.formats.IParser.OutputStyle;
|
||||
import org.hl7.fhir.dstu2.formats.JsonParser;
|
||||
|
@ -84,8 +63,11 @@ import org.hl7.fhir.exceptions.FHIRException;
|
|||
import org.hl7.fhir.utilities.MimeType;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.http.*;
|
||||
import org.hl7.fhir.utilities.settings.FhirSettings;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Helper class handling lower level HTTP transport concerns. TODO Document
|
||||
* methods.
|
||||
|
@ -93,51 +75,35 @@ import org.hl7.fhir.utilities.settings.FhirSettings;
|
|||
* @author Claude Nanjo
|
||||
*/
|
||||
public class ClientUtils {
|
||||
|
||||
protected static final String LOCATION_HEADER = "location";
|
||||
protected static final String CONTENT_LOCATION_HEADER = "content-location";
|
||||
public static final String DEFAULT_CHARSET = "UTF-8";
|
||||
public static final String HEADER_LOCATION = "location";
|
||||
|
||||
private static boolean debugging = false;
|
||||
|
||||
private HttpHost proxy;
|
||||
@Getter
|
||||
@Setter
|
||||
private int timeout = 5000;
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
private ToolingClientLogger logger;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
private int retryCount;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private String userAgent;
|
||||
private String acceptLang;
|
||||
private String contentLang;
|
||||
@Setter
|
||||
private String acceptLanguage;
|
||||
@Setter
|
||||
private String contentLanguage;
|
||||
private final TimeUnit timeoutUnit = TimeUnit.MILLISECONDS;
|
||||
|
||||
public HttpHost getProxy() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
public void setProxy(HttpHost proxy) {
|
||||
this.proxy = proxy;
|
||||
}
|
||||
|
||||
public int getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public void setTimeout(int timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
protected ManagedFhirWebAccessor getManagedWebAccessor() {
|
||||
return ManagedWebAccess.fhirAccessor().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issueOptionsRequest(URI optionsUri, String resourceFormat,
|
||||
|
@ -146,8 +112,10 @@ public class ClientUtils {
|
|||
throw new FHIRException("Network Access is prohibited in this context");
|
||||
}
|
||||
|
||||
HttpOptions options = new HttpOptions(optionsUri);
|
||||
return issueResourceRequest(resourceFormat, options, timeoutLoading);
|
||||
HTTPRequest httpRequest = new HTTPRequest()
|
||||
.withMethod(HTTPRequest.HttpMethod.OPTIONS)
|
||||
.withUrl(optionsUri.toString());
|
||||
return issueResourceRequest(resourceFormat, httpRequest, timeoutLoading);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issueGetResourceRequest(URI resourceUri, String resourceFormat,
|
||||
|
@ -155,17 +123,23 @@ public class ClientUtils {
|
|||
if (FhirSettings.isProhibitNetworkAccess()) {
|
||||
throw new FHIRException("Network Access is prohibited in this context");
|
||||
}
|
||||
HttpGet httpget = new HttpGet(resourceUri);
|
||||
return issueResourceRequest(resourceFormat, httpget, timeoutLoading);
|
||||
|
||||
HTTPRequest httpRequest = new HTTPRequest()
|
||||
.withMethod(HTTPRequest.HttpMethod.GET)
|
||||
.withUrl(resourceUri.toString());
|
||||
return issueResourceRequest(resourceFormat, httpRequest, timeoutLoading);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri, byte[] payload, String resourceFormat,
|
||||
List<Header> headers, int timeoutLoading) {
|
||||
Iterable<HTTPHeader> headers, int timeoutLoading) {
|
||||
if (FhirSettings.isProhibitNetworkAccess()) {
|
||||
throw new FHIRException("Network Access is prohibited in this context");
|
||||
}
|
||||
HttpPut httpPut = new HttpPut(resourceUri);
|
||||
return issueResourceRequest(resourceFormat, httpPut, payload, headers, timeoutLoading);
|
||||
HTTPRequest httpRequest = new HTTPRequest()
|
||||
.withMethod(HTTPRequest.HttpMethod.PUT)
|
||||
.withUrl(resourceUri.toString())
|
||||
.withBody(payload);
|
||||
return issueResourceRequest(resourceFormat, httpRequest, headers, timeoutLoading);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri, byte[] payload, String resourceFormat,
|
||||
|
@ -173,17 +147,25 @@ public class ClientUtils {
|
|||
if (FhirSettings.isProhibitNetworkAccess()) {
|
||||
throw new FHIRException("Network Access is prohibited in this context");
|
||||
}
|
||||
HttpPut httpPut = new HttpPut(resourceUri);
|
||||
return issueResourceRequest(resourceFormat, httpPut, payload, null, timeoutLoading);
|
||||
|
||||
HTTPRequest httpRequest = new HTTPRequest()
|
||||
.withMethod(HTTPRequest.HttpMethod.PUT)
|
||||
.withUrl(resourceUri.toString())
|
||||
.withBody(payload);
|
||||
return issueResourceRequest(resourceFormat, httpRequest, timeoutLoading);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri, byte[] payload,
|
||||
String resourceFormat, List<Header> headers, int timeoutLoading) {
|
||||
String resourceFormat, Iterable<HTTPHeader> headers, int timeoutLoading) {
|
||||
if (FhirSettings.isProhibitNetworkAccess()) {
|
||||
throw new FHIRException("Network Access is prohibited in this context");
|
||||
}
|
||||
HttpPost httpPost = new HttpPost(resourceUri);
|
||||
return issueResourceRequest(resourceFormat, httpPost, payload, headers, timeoutLoading);
|
||||
|
||||
HTTPRequest httpRequest = new HTTPRequest()
|
||||
.withMethod(HTTPRequest.HttpMethod.POST)
|
||||
.withUrl(resourceUri.toString())
|
||||
.withBody(payload);
|
||||
return issueResourceRequest(resourceFormat, httpRequest, headers, timeoutLoading);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri, byte[] payload,
|
||||
|
@ -195,30 +177,26 @@ public class ClientUtils {
|
|||
if (FhirSettings.isProhibitNetworkAccess()) {
|
||||
throw new FHIRException("Network Access is prohibited in this context");
|
||||
}
|
||||
HttpGet httpget = new HttpGet(resourceUri);
|
||||
configureFhirRequest(httpget, resourceFormat);
|
||||
HttpResponse response = sendRequest(httpget);
|
||||
return unmarshalReference(response, resourceFormat);
|
||||
}
|
||||
|
||||
private void setAuth(HttpRequest httpget) {
|
||||
if (password != null) {
|
||||
try {
|
||||
byte[] b = Base64.encodeBase64((username + ":" + password).getBytes("ASCII"));
|
||||
String b64 = new String(b, StandardCharsets.US_ASCII);
|
||||
httpget.setHeader("Authorization", "Basic " + b64);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
}
|
||||
}
|
||||
HTTPRequest httpRequest = new HTTPRequest()
|
||||
.withMethod(HTTPRequest.HttpMethod.GET)
|
||||
.withUrl(resourceUri.toString());
|
||||
Iterable<HTTPHeader> headers = getFhirHeaders(httpRequest, resourceFormat);
|
||||
HTTPResult response = sendRequest(httpRequest.withHeaders(headers));
|
||||
return unmarshalReference(response, resourceFormat);
|
||||
}
|
||||
|
||||
public Bundle postBatchRequest(URI resourceUri, byte[] payload, String resourceFormat, int timeoutLoading) {
|
||||
if (FhirSettings.isProhibitNetworkAccess()) {
|
||||
throw new FHIRException("Network Access is prohibited in this context");
|
||||
}
|
||||
HttpPost httpPost = new HttpPost(resourceUri);
|
||||
configureFhirRequest(httpPost, resourceFormat);
|
||||
HttpResponse response = sendPayload(httpPost, payload, proxy, timeoutLoading);
|
||||
|
||||
HTTPRequest httpRequest = new HTTPRequest()
|
||||
.withMethod(HTTPRequest.HttpMethod.POST)
|
||||
.withUrl(resourceUri.toString())
|
||||
.withBody(payload);
|
||||
Iterable<HTTPHeader> headers = getFhirHeaders(httpRequest, resourceFormat);
|
||||
HTTPResult response = sendPayload(httpRequest.withHeaders(headers));
|
||||
return unmarshalFeed(response, resourceFormat);
|
||||
}
|
||||
|
||||
|
@ -226,9 +204,12 @@ public class ClientUtils {
|
|||
if (FhirSettings.isProhibitNetworkAccess()) {
|
||||
throw new FHIRException("Network Access is prohibited in this context");
|
||||
}
|
||||
HttpDelete deleteRequest = new HttpDelete(resourceUri);
|
||||
HttpResponse response = sendRequest(deleteRequest);
|
||||
int responseStatusCode = response.getStatusLine().getStatusCode();
|
||||
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withMethod(HTTPRequest.HttpMethod.DELETE)
|
||||
.withUrl(resourceUri.toString());
|
||||
HTTPResult response = sendRequest(request);
|
||||
int responseStatusCode = response.getCode();
|
||||
boolean deletionSuccessful = false;
|
||||
if (responseStatusCode == 204) {
|
||||
deletionSuccessful = true;
|
||||
|
@ -240,171 +221,120 @@ public class ClientUtils {
|
|||
* Request/Response Helper methods
|
||||
***********************************************************/
|
||||
|
||||
protected <T extends Resource> ResourceRequest<T> issueResourceRequest(String resourceFormat, HttpUriRequest request,
|
||||
protected <T extends Resource> ResourceRequest<T> issueResourceRequest(String resourceFormat, HTTPRequest request,
|
||||
int timeoutLoading) {
|
||||
return issueResourceRequest(resourceFormat, request, null, timeoutLoading);
|
||||
return issueResourceRequest(resourceFormat, request, Collections.emptyList(), timeoutLoading);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resourceFormat
|
||||
* @param options
|
||||
* @return
|
||||
* Issue a resource request.
|
||||
* @param resourceFormat the expected FHIR format
|
||||
* @param request the request to be sent
|
||||
* @param headers any additional headers to add
|
||||
*
|
||||
* @return A ResourceRequest object containing the requested resource
|
||||
*/
|
||||
protected <T extends Resource> ResourceRequest<T> issueResourceRequest(String resourceFormat, HttpUriRequest request,
|
||||
byte[] payload, int timeoutLoading) {
|
||||
return issueResourceRequest(resourceFormat, request, payload, null, timeoutLoading);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resourceFormat
|
||||
* @param options
|
||||
* @return
|
||||
*/
|
||||
protected <T extends Resource> ResourceRequest<T> issueResourceRequest(String resourceFormat, HttpUriRequest request,
|
||||
byte[] payload, List<Header> headers, int timeoutLoading) {
|
||||
protected <T extends Resource> ResourceRequest<T> issueResourceRequest(String resourceFormat, HTTPRequest request,
|
||||
@Nonnull Iterable<HTTPHeader> headers, int timeoutLoading) {
|
||||
if (FhirSettings.isProhibitNetworkAccess()) {
|
||||
throw new FHIRException("Network Access is prohibited in this context");
|
||||
}
|
||||
configureFhirRequest(request, resourceFormat, headers);
|
||||
HttpResponse response = null;
|
||||
if (request instanceof HttpEntityEnclosingRequest && payload != null) {
|
||||
response = sendPayload((HttpEntityEnclosingRequestBase) request, payload, proxy, timeoutLoading);
|
||||
} else if (request instanceof HttpEntityEnclosingRequest && payload == null) {
|
||||
throw new EFhirClientException("PUT and POST requests require a non-null payload");
|
||||
} else {
|
||||
response = sendRequest(request);
|
||||
}
|
||||
T resource = unmarshalReference(response, resourceFormat);
|
||||
return new ResourceRequest<T>(resource, response.getStatusLine().getStatusCode(), getLocationHeader(response));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method adds required request headers. TODO handle JSON request as well.
|
||||
*
|
||||
* @param request
|
||||
*/
|
||||
protected void configureFhirRequest(HttpRequest request, String format) {
|
||||
configureFhirRequest(request, format, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method adds required request headers. TODO handle JSON request as well.
|
||||
*
|
||||
* @param request
|
||||
*/
|
||||
protected void configureFhirRequest(HttpRequest request, String format, List<Header> headers) {
|
||||
if (!Utilities.noString(userAgent)) {
|
||||
request.addHeader("User-Agent", userAgent);
|
||||
}
|
||||
if (!Utilities.noString(acceptLang)) {
|
||||
request.addHeader("Accept-Language", acceptLang);
|
||||
}
|
||||
if (!Utilities.noString(contentLang)) {
|
||||
request.addHeader("Content-Language", acceptLang);
|
||||
}
|
||||
|
||||
if (format != null) {
|
||||
request.addHeader("Accept", format);
|
||||
request.addHeader("Content-Type", format + ";charset=" + DEFAULT_CHARSET);
|
||||
}
|
||||
if (headers != null) {
|
||||
for (Header header : headers) {
|
||||
request.addHeader(header);
|
||||
}
|
||||
}
|
||||
setAuth(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method posts request payload
|
||||
*
|
||||
* @param request
|
||||
* @param payload
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings({ "resource", "deprecation" })
|
||||
protected HttpResponse sendPayload(HttpEntityEnclosingRequestBase request, byte[] payload, HttpHost proxy,
|
||||
int timeoutLoading) {
|
||||
if (FhirSettings.isProhibitNetworkAccess()) {
|
||||
throw new FHIRException("Network Access is prohibited in this context");
|
||||
}
|
||||
HttpResponse response = null;
|
||||
boolean ok = false;
|
||||
long t = System.currentTimeMillis();
|
||||
int tryCount = 0;
|
||||
while (!ok) {
|
||||
try {
|
||||
tryCount++;
|
||||
HttpClient httpclient = new DefaultHttpClient();
|
||||
HttpParams params = httpclient.getParams();
|
||||
HttpConnectionParams.setConnectionTimeout(params, timeout);
|
||||
HttpConnectionParams.setSoTimeout(params, timeout * timeoutLoading);
|
||||
|
||||
if (proxy != null) {
|
||||
httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
|
||||
}
|
||||
request.setEntity(new ByteArrayEntity(payload));
|
||||
log(request);
|
||||
response = httpclient.execute(request);
|
||||
ok = true;
|
||||
} catch (IOException ioe) {
|
||||
System.out.println(ioe.getMessage() + " (" + (System.currentTimeMillis() - t) + "ms / "
|
||||
+ Utilities.describeSize(payload.length) + ")");
|
||||
if (tryCount <= retryCount || (tryCount < 3 && ioe instanceof org.apache.http.conn.ConnectTimeoutException)) {
|
||||
ok = false;
|
||||
} else {
|
||||
throw new EFhirClientException("Error sending HTTP Post/Put Payload to " + "??" + ": " + ioe.getMessage(),
|
||||
ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param request
|
||||
* @param payload
|
||||
* @return
|
||||
*/
|
||||
protected HttpResponse sendRequest(HttpUriRequest request) {
|
||||
if (FhirSettings.isProhibitNetworkAccess()) {
|
||||
throw new FHIRException("Network Access is prohibited in this context");
|
||||
}
|
||||
HttpResponse response = null;
|
||||
Iterable<HTTPHeader> configuredHeaders = getFhirHeaders(request, resourceFormat, headers);
|
||||
try {
|
||||
HttpClient httpclient = new DefaultHttpClient();
|
||||
log(request);
|
||||
HttpParams params = httpclient.getParams();
|
||||
HttpConnectionParams.setConnectionTimeout(params, timeout);
|
||||
HttpConnectionParams.setSoTimeout(params, timeout);
|
||||
if (proxy != null) {
|
||||
httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
|
||||
}
|
||||
response = httpclient.execute(request);
|
||||
|
||||
HTTPResult response = getManagedWebAccessor().httpCall(request.withHeaders(configuredHeaders));
|
||||
T resource = unmarshalReference(response, resourceFormat);
|
||||
return new ResourceRequest<T>(resource, response.getCode(), getLocationHeader(response.getHeaders()));
|
||||
} catch (IOException ioe) {
|
||||
throw new EFhirClientException("Error sending HTTP Post/Put Payload to " + "??" + ": " + ioe.getMessage(),
|
||||
ioe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get required headers for FHIR requests.
|
||||
*
|
||||
* @param httpRequest the request
|
||||
* @param format the expected format
|
||||
*/
|
||||
protected Iterable<HTTPHeader> getFhirHeaders(HTTPRequest httpRequest, String format) {
|
||||
return getFhirHeaders(httpRequest, format, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get required headers for FHIR requests.
|
||||
*
|
||||
* @param httpRequest the request
|
||||
* @param format the expected format
|
||||
* @param headers any additional headers to add
|
||||
*/
|
||||
protected Iterable<HTTPHeader> getFhirHeaders(HTTPRequest httpRequest, String format, Iterable<HTTPHeader> headers) {
|
||||
List<HTTPHeader> configuredHeaders = new ArrayList<>();
|
||||
if (!Utilities.noString(userAgent)) {
|
||||
configuredHeaders.add(new HTTPHeader("User-Agent", userAgent));
|
||||
}
|
||||
if (!Utilities.noString(acceptLanguage)) {
|
||||
configuredHeaders.add(new HTTPHeader("Accept-Language", acceptLanguage));
|
||||
}
|
||||
if (!Utilities.noString(contentLanguage)) {
|
||||
configuredHeaders.add(new HTTPHeader("Content-Language", acceptLanguage));
|
||||
}
|
||||
|
||||
Iterable<HTTPHeader> resourceFormatHeaders = getResourceFormatHeaders(httpRequest, format);
|
||||
resourceFormatHeaders.forEach(configuredHeaders::add);
|
||||
|
||||
if (headers != null) {
|
||||
headers.forEach(configuredHeaders::add);
|
||||
}
|
||||
return configuredHeaders;
|
||||
}
|
||||
|
||||
protected static List<HTTPHeader> getResourceFormatHeaders(HTTPRequest httpRequest, String format) {
|
||||
List<HTTPHeader> headers = new ArrayList<>();
|
||||
headers.add(new HTTPHeader("Accept", format));
|
||||
if (httpRequest.getMethod() == HTTPRequest.HttpMethod.PUT
|
||||
|| httpRequest.getMethod() == HTTPRequest.HttpMethod.POST
|
||||
|| httpRequest.getMethod() == HTTPRequest.HttpMethod.PATCH
|
||||
) {
|
||||
headers.add(new HTTPHeader("Content-Type", format + ";charset=" + DEFAULT_CHARSET));
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param request The request to be sent
|
||||
* @return The response from the server
|
||||
*/
|
||||
protected HTTPResult sendRequest(HTTPRequest request) {
|
||||
if (FhirSettings.isProhibitNetworkAccess()) {
|
||||
throw new FHIRException("Network Access is prohibited in this context");
|
||||
}
|
||||
HTTPResult response = null;
|
||||
try {
|
||||
|
||||
response = getManagedWebAccessor().httpCall(request);
|
||||
return response;
|
||||
} catch (IOException ioe) {
|
||||
if (ClientUtils.debugging) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
throw new EFhirClientException("Error sending Http Request: " + ioe.getMessage(), ioe);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmarshals a resource from the response stream.
|
||||
*
|
||||
* @param response
|
||||
* @return
|
||||
* @param response The response from the server
|
||||
* @return The unmarshalled resource
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T extends Resource> T unmarshalReference(HttpResponse response, String format) {
|
||||
protected <T extends Resource> T unmarshalReference(HTTPResult response, String format) {
|
||||
T resource = null;
|
||||
OperationOutcome error = null;
|
||||
byte[] cnt = log(response);
|
||||
if (cnt != null) {
|
||||
if (response.getContent() != null) {
|
||||
try {
|
||||
resource = (T) getParser(format).parse(cnt);
|
||||
resource = (T) getParser(format).parse(response.getContent());
|
||||
if (resource instanceof OperationOutcome && hasError((OperationOutcome) resource)) {
|
||||
error = (OperationOutcome) resource;
|
||||
}
|
||||
|
@ -423,18 +353,18 @@ public class ClientUtils {
|
|||
/**
|
||||
* Unmarshals Bundle from response stream.
|
||||
*
|
||||
* @param response
|
||||
* @return
|
||||
* @param response The response from the server
|
||||
* @return The unmarshalled Bundle
|
||||
*/
|
||||
protected Bundle unmarshalFeed(HttpResponse response, String format) {
|
||||
protected Bundle unmarshalFeed(HTTPResult response, String format) {
|
||||
Bundle feed = null;
|
||||
byte[] cnt = log(response);
|
||||
String contentType = response.getHeaders("Content-Type")[0].getValue();
|
||||
|
||||
String contentType = HTTPHeaderUtil.getSingleHeader(response.getHeaders(), "Content-Type");
|
||||
OperationOutcome error = null;
|
||||
try {
|
||||
if (cnt != null) {
|
||||
if (response.getContent() != null) {
|
||||
if (contentType.contains(ResourceFormat.RESOURCE_XML.getHeader()) || contentType.contains("text/xml+fhir")) {
|
||||
Resource rf = getParser(format).parse(cnt);
|
||||
Resource rf = getParser(format).parse(response.getContent());
|
||||
if (rf instanceof Bundle)
|
||||
feed = (Bundle) rf;
|
||||
else if (rf instanceof OperationOutcome && hasError((OperationOutcome) rf)) {
|
||||
|
@ -455,21 +385,20 @@ public class ClientUtils {
|
|||
return feed;
|
||||
}
|
||||
|
||||
private boolean hasError(OperationOutcome oo) {
|
||||
protected boolean hasError(OperationOutcome oo) {
|
||||
for (OperationOutcomeIssueComponent t : oo.getIssue())
|
||||
if (t.getSeverity() == IssueSeverity.ERROR || t.getSeverity() == IssueSeverity.FATAL)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected String getLocationHeader(HttpResponse response) {
|
||||
String location = null;
|
||||
if (response.getHeaders("location").length > 0) {// TODO Distinguish between both cases if necessary
|
||||
location = response.getHeaders("location")[0].getValue();
|
||||
} else if (response.getHeaders("content-location").length > 0) {
|
||||
location = response.getHeaders("content-location")[0].getValue();
|
||||
protected static String getLocationHeader(Iterable<HTTPHeader> headers) {
|
||||
String locationHeader = HTTPHeaderUtil.getSingleHeader(headers, LOCATION_HEADER);
|
||||
|
||||
if (locationHeader != null) {
|
||||
return locationHeader;
|
||||
}
|
||||
return location;
|
||||
return HTTPHeaderUtil.getSingleHeader(headers, CONTENT_LOCATION_HEADER);
|
||||
}
|
||||
|
||||
/*****************************************************************
|
||||
|
@ -586,12 +515,17 @@ public class ClientUtils {
|
|||
|
||||
public Bundle issuePostFeedRequest(URI resourceUri, Map<String, String> parameters, String resourceName,
|
||||
Resource resource, String resourceFormat) throws IOException {
|
||||
HttpPost httppost = new HttpPost(resourceUri);
|
||||
|
||||
HTTPRequest httpRequest = new HTTPRequest()
|
||||
.withMethod(HTTPRequest.HttpMethod.POST)
|
||||
.withUrl(resourceUri.toString());
|
||||
String boundary = "----WebKitFormBoundarykbMUo6H8QaUnYtRy";
|
||||
httppost.addHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
|
||||
httppost.addHeader("Accept", resourceFormat);
|
||||
configureFhirRequest(httppost, null);
|
||||
HttpResponse response = sendPayload(httppost, encodeFormSubmission(parameters, resourceName, resource, boundary));
|
||||
List<HTTPHeader> headers = new ArrayList<>();
|
||||
headers.add(new HTTPHeader("Content-Type", "multipart/form-data; boundary=" + boundary));
|
||||
headers.add(new HTTPHeader("Accept", resourceFormat));
|
||||
this.getFhirHeaders(httpRequest, null).forEach(headers::add);
|
||||
|
||||
HTTPResult response = sendPayload(httpRequest.withBody(encodeFormSubmission(parameters, resourceName, resource, boundary)).withHeaders(headers));
|
||||
return unmarshalFeed(response, resourceFormat);
|
||||
}
|
||||
|
||||
|
@ -622,80 +556,22 @@ public class ClientUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Method posts request payload
|
||||
* Send an HTTP Post/Put Payload
|
||||
*
|
||||
* @param request
|
||||
* @param payload
|
||||
* @return
|
||||
* @param request The request to be sent
|
||||
* @return The response from the server
|
||||
*/
|
||||
protected HttpResponse sendPayload(HttpEntityEnclosingRequestBase request, byte[] payload) {
|
||||
HttpResponse response = null;
|
||||
protected HTTPResult sendPayload(HTTPRequest request) {
|
||||
HTTPResult response = null;
|
||||
try {
|
||||
log(request);
|
||||
HttpClient httpclient = new DefaultHttpClient();
|
||||
request.setEntity(new ByteArrayEntity(payload));
|
||||
response = httpclient.execute(request);
|
||||
log(response);
|
||||
|
||||
response = getManagedWebAccessor().httpCall(request);
|
||||
} catch (IOException ioe) {
|
||||
throw new EFhirClientException("Error sending HTTP Post/Put Payload: " + ioe.getMessage(), ioe);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
private void log(HttpUriRequest request) {
|
||||
if (logger != null) {
|
||||
List<String> headers = new ArrayList<>();
|
||||
for (Header h : request.getAllHeaders()) {
|
||||
headers.add(h.toString());
|
||||
}
|
||||
logger.logRequest(request.getMethod(), request.getURI().toString(), headers, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void log(HttpEntityEnclosingRequestBase request) {
|
||||
if (logger != null) {
|
||||
List<String> headers = new ArrayList<>();
|
||||
for (Header h : request.getAllHeaders()) {
|
||||
headers.add(h.toString());
|
||||
}
|
||||
byte[] cnt = null;
|
||||
InputStream s;
|
||||
try {
|
||||
s = request.getEntity().getContent();
|
||||
cnt = IOUtils.toByteArray(s);
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
logger.logRequest(request.getMethod(), request.getURI().toString(), headers, cnt);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] log(HttpResponse response) {
|
||||
byte[] cnt = null;
|
||||
try {
|
||||
InputStream s = response.getEntity().getContent();
|
||||
cnt = IOUtils.toByteArray(s);
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
if (logger != null) {
|
||||
List<String> headers = new ArrayList<>();
|
||||
for (Header h : response.getAllHeaders()) {
|
||||
headers.add(h.toString());
|
||||
}
|
||||
logger.logResponse(response.getStatusLine().toString(), headers, cnt, 0);
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
|
||||
public ToolingClientLogger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
public void setLogger(ToolingClientLogger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for debugging
|
||||
*
|
||||
|
@ -714,26 +590,5 @@ public class ClientUtils {
|
|||
return value;
|
||||
}
|
||||
|
||||
public int getRetryCount() {
|
||||
return retryCount;
|
||||
}
|
||||
|
||||
public void setRetryCount(int retryCount) {
|
||||
this.retryCount = retryCount;
|
||||
}
|
||||
|
||||
public String getUserAgent() {
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
public void setUserAgent(String userAgent) {
|
||||
this.userAgent = userAgent;
|
||||
}
|
||||
|
||||
public void setAcceptLanguage(String language) {
|
||||
this.acceptLang = language;
|
||||
}
|
||||
public void setContentLanguage(String language) {
|
||||
this.contentLang = language;
|
||||
}
|
||||
}
|
|
@ -95,48 +95,21 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
private HttpHost proxy;
|
||||
private int maxResultSetSize = -1;// _count
|
||||
private Conformance conf;
|
||||
private ClientUtils utils = new ClientUtils();
|
||||
private ClientUtils utils = null;
|
||||
private int useCount;
|
||||
|
||||
protected ClientUtils getClientUtils() {
|
||||
return new ClientUtils();
|
||||
}
|
||||
|
||||
// Pass enpoint for client - URI
|
||||
public FHIRToolingClient(String baseServiceUrl, String userAgent) throws URISyntaxException {
|
||||
preferredResourceFormat = ResourceFormat.RESOURCE_XML;
|
||||
utils = getClientUtils();
|
||||
utils.setUserAgent(userAgent);
|
||||
detectProxy();
|
||||
initialize(baseServiceUrl);
|
||||
}
|
||||
|
||||
public FHIRToolingClient(String baseServiceUrl, String userAgent, String username, String password)
|
||||
throws URISyntaxException {
|
||||
preferredResourceFormat = ResourceFormat.RESOURCE_XML;
|
||||
utils.setUserAgent(userAgent);
|
||||
utils.setUsername(username);
|
||||
utils.setPassword(password);
|
||||
detectProxy();
|
||||
initialize(baseServiceUrl);
|
||||
}
|
||||
|
||||
public void configureProxy(String proxyHost, int proxyPort) {
|
||||
utils.setProxy(new HttpHost(proxyHost, proxyPort));
|
||||
}
|
||||
|
||||
public void detectProxy() {
|
||||
String host = System.getenv(hostKey);
|
||||
String port = System.getenv(portKey);
|
||||
|
||||
if (host == null) {
|
||||
host = System.getProperty(hostKey);
|
||||
}
|
||||
|
||||
if (port == null) {
|
||||
port = System.getProperty(portKey);
|
||||
}
|
||||
|
||||
if (host != null && port != null) {
|
||||
this.configureProxy(host, Integer.parseInt(port));
|
||||
}
|
||||
}
|
||||
|
||||
public void initialize(String baseServiceUrl) throws URISyntaxException {
|
||||
base = baseServiceUrl;
|
||||
resourceAddress = new ResourceAddress(baseServiceUrl);
|
||||
|
@ -286,11 +259,11 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
recordUse();
|
||||
ResourceRequest<Resource> result = null;
|
||||
try {
|
||||
List<Header> headers = null;
|
||||
|
||||
result = utils.issuePutRequest(
|
||||
resourceAddress.resolveGetUriFromResourceClassAndId(resource.getClass(), resource.getId()),
|
||||
utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())),
|
||||
withVer(getPreferredResourceFormat(), "1.0"), headers, timeoutOperation);
|
||||
withVer(getPreferredResourceFormat(), "1.0"), null, timeoutOperation);
|
||||
result.addErrorStatus(410);// gone
|
||||
result.addErrorStatus(404);// unknown
|
||||
result.addErrorStatus(405);
|
||||
|
@ -323,10 +296,12 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
recordUse();
|
||||
ResourceRequest<T> result = null;
|
||||
try {
|
||||
List<Header> headers = null;
|
||||
result = utils.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),
|
||||
result = utils.issuePutRequest(
|
||||
resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),
|
||||
utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())),
|
||||
withVer(getPreferredResourceFormat(), "1.0"), headers, timeoutOperation);
|
||||
withVer(getPreferredResourceFormat(), "1.0"),
|
||||
null,
|
||||
timeoutOperation);
|
||||
result.addErrorStatus(410);// gone
|
||||
result.addErrorStatus(404);// unknown
|
||||
result.addErrorStatus(405);
|
||||
|
@ -355,170 +330,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
return result.getPayload();
|
||||
}
|
||||
|
||||
//
|
||||
// public <T extends Resource> boolean delete(Class<T> resourceClass, String id) {
|
||||
// try {
|
||||
// return utils.issueDeleteRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id), proxy);
|
||||
// } catch(Exception e) {
|
||||
// throw new EFhirClientException("An error has occurred while trying to delete this resource", e);
|
||||
// }
|
||||
//
|
||||
// }
|
||||
|
||||
//
|
||||
// public <T extends Resource> OperationOutcome create(Class<T> resourceClass, T resource) {
|
||||
// ResourceRequest<T> resourceRequest = null;
|
||||
// try {
|
||||
// List<Header> headers = null;
|
||||
// resourceRequest = utils.issuePostRequest(resourceAddress.resolveGetUriFromResourceClass(resourceClass),utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())), withVer(getPreferredResourceFormat(), "1.0"), headers, proxy);
|
||||
// resourceRequest.addSuccessStatus(201);
|
||||
// if(resourceRequest.isUnsuccessfulRequest()) {
|
||||
// throw new EFhirClientException("Server responded with HTTP error code " + resourceRequest.getHttpStatus(), (OperationOutcome)resourceRequest.getPayload());
|
||||
// }
|
||||
// } catch(Exception e) {
|
||||
// handleException("An error has occurred while trying to create this resource", e);
|
||||
// }
|
||||
// OperationOutcome operationOutcome = null;;
|
||||
// try {
|
||||
// operationOutcome = (OperationOutcome)resourceRequest.getPayload();
|
||||
// ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier =
|
||||
// ResourceAddress.parseCreateLocation(resourceRequest.getLocation());
|
||||
// OperationOutcomeIssueComponent issue = operationOutcome.addIssue();
|
||||
// issue.setSeverity(IssueSeverity.INFORMATION);
|
||||
// issue.setUserData(ResourceAddress.ResourceVersionedIdentifier.class.toString(),
|
||||
// resVersionedIdentifier);
|
||||
// return operationOutcome;
|
||||
// } catch(ClassCastException e) {
|
||||
// // some server (e.g. grahams) returns the resource directly
|
||||
// operationOutcome = new OperationOutcome();
|
||||
// OperationOutcomeIssueComponent issue = operationOutcome.addIssue();
|
||||
// issue.setSeverity(IssueSeverity.INFORMATION);
|
||||
// issue.setUserData(ResourceRequest.class.toString(),
|
||||
// resourceRequest.getPayload());
|
||||
// return operationOutcome;
|
||||
// }
|
||||
// }
|
||||
|
||||
//
|
||||
// public <T extends Resource> Bundle history(Calendar lastUpdate, Class<T> resourceClass, String id) {
|
||||
// Bundle history = null;
|
||||
// try {
|
||||
// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceId(resourceClass, id, lastUpdate, maxResultSetSize), withVer(getPreferredResourceFormat(), "1.0"), proxy);
|
||||
// } catch (Exception e) {
|
||||
// handleException("An error has occurred while trying to retrieve history information for this resource", e);
|
||||
// }
|
||||
// return history;
|
||||
// }
|
||||
|
||||
//
|
||||
// public <T extends Resource> Bundle history(Date lastUpdate, Class<T> resourceClass, String id) {
|
||||
// Bundle history = null;
|
||||
// try {
|
||||
// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceId(resourceClass, id, lastUpdate, maxResultSetSize), withVer(getPreferredResourceFormat(), "1.0"), proxy);
|
||||
// } catch (Exception e) {
|
||||
// handleException("An error has occurred while trying to retrieve history information for this resource", e);
|
||||
// }
|
||||
// return history;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// public <T extends Resource> Bundle history(Calendar lastUpdate, Class<T> resourceClass) {
|
||||
// Bundle history = null;
|
||||
// try {
|
||||
// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceType(resourceClass, lastUpdate, maxResultSetSize), withVer(getPreferredResourceFormat(), "1.0"), proxy);
|
||||
// } catch (Exception e) {
|
||||
// handleException("An error has occurred while trying to retrieve history information for this resource type", e);
|
||||
// }
|
||||
// return history;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// public <T extends Resource> Bundle history(Date lastUpdate, Class<T> resourceClass) {
|
||||
// Bundle history = null;
|
||||
// try {
|
||||
// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceType(resourceClass, lastUpdate, maxResultSetSize), withVer(getPreferredResourceFormat(), "1.0"), proxy);
|
||||
// } catch (Exception e) {
|
||||
// handleException("An error has occurred while trying to retrieve history information for this resource type", e);
|
||||
// }
|
||||
// return history;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// public <T extends Resource> Bundle history(Class<T> resourceClass) {
|
||||
// Bundle history = null;
|
||||
// try {
|
||||
// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceType(resourceClass, maxResultSetSize), withVer(getPreferredResourceFormat(), "1.0"), proxy);
|
||||
// } catch (Exception e) {
|
||||
// handleException("An error has occurred while trying to retrieve history information for this resource type", e);
|
||||
// }
|
||||
// return history;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// public <T extends Resource> Bundle history(Class<T> resourceClass, String id) {
|
||||
// Bundle history = null;
|
||||
// try {
|
||||
// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceId(resourceClass, id, maxResultSetSize), withVer(getPreferredResourceFormat(), "1.0"), proxy);
|
||||
// } catch (Exception e) {
|
||||
// handleException("An error has occurred while trying to retrieve history information for this resource", e);
|
||||
// }
|
||||
// return history;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// public <T extends Resource> Bundle history(Date lastUpdate) {
|
||||
// Bundle history = null;
|
||||
// try {
|
||||
// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForAllResources(lastUpdate, maxResultSetSize), withVer(getPreferredResourceFormat(), "1.0"), proxy);
|
||||
// } catch (Exception e) {
|
||||
// handleException("An error has occurred while trying to retrieve history since last update",e);
|
||||
// }
|
||||
// return history;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// public <T extends Resource> Bundle history(Calendar lastUpdate) {
|
||||
// Bundle history = null;
|
||||
// try {
|
||||
// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForAllResources(lastUpdate, maxResultSetSize), withVer(getPreferredResourceFormat(), "1.0"), proxy);
|
||||
// } catch (Exception e) {
|
||||
// handleException("An error has occurred while trying to retrieve history since last update",e);
|
||||
// }
|
||||
// return history;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// public <T extends Resource> Bundle history() {
|
||||
// Bundle history = null;
|
||||
// try {
|
||||
// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForAllResources(maxResultSetSize), withVer(getPreferredResourceFormat(), "1.0"), proxy);
|
||||
// } catch (Exception e) {
|
||||
// handleException("An error has occurred while trying to retrieve history since last update",e);
|
||||
// }
|
||||
// return history;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// public <T extends Resource> Bundle search(Class<T> resourceClass, Map<String, String> parameters) {
|
||||
// Bundle searchResults = null;
|
||||
// try {
|
||||
// searchResults = utils.issueGetFeedRequest(resourceAddress.resolveSearchUri(resourceClass, parameters), withVer(getPreferredResourceFormat(), "1.0"), proxy);
|
||||
// } catch (Exception e) {
|
||||
// handleException("Error performing search with parameters " + parameters, e);
|
||||
// }
|
||||
// return searchResults;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// public <T extends Resource> Bundle searchPost(Class<T> resourceClass, T resource, Map<String, String> parameters) {
|
||||
// Bundle searchResults = null;
|
||||
// try {
|
||||
// searchResults = utils.issuePostFeedRequest(resourceAddress.resolveSearchUri(resourceClass, new HashMap<String, String>()), parameters, "src", resource, getPreferredResourceFormat());
|
||||
// } catch (Exception e) {
|
||||
// handleException("Error performing search with parameters " + parameters, e);
|
||||
// }
|
||||
// return searchResults;
|
||||
// }
|
||||
|
||||
public <T extends Resource> Parameters operateType(Class<T> resourceClass, String name, Parameters params) {
|
||||
recordUse();
|
||||
|
@ -588,107 +400,15 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
return (OperationOutcome) result.getPayload();
|
||||
}
|
||||
|
||||
/*
|
||||
* change to meta operations
|
||||
*
|
||||
* public List<Coding> getAllTags() { TagListRequest result = null; try { result
|
||||
* = utils.issueGetRequestForTagList(resourceAddress.resolveGetAllTags(),
|
||||
* withVer(getPreferredResourceFormat(), "1.0"), null, proxy); } catch (Exception e) {
|
||||
* handleException("An error has occurred while trying to retrieve all tags",
|
||||
* e); } return result.getPayload(); }
|
||||
*
|
||||
*
|
||||
* public <T extends Resource> List<Coding> getAllTagsForResourceType(Class<T>
|
||||
* resourceClass) { TagListRequest result = null; try { result =
|
||||
* utils.issueGetRequestForTagList(resourceAddress.
|
||||
* resolveGetAllTagsForResourceType(resourceClass),
|
||||
* withVer(getPreferredResourceFormat(), "1.0"), null, proxy); } catch (Exception e) {
|
||||
* handleException("An error has occurred while trying to retrieve tags for this resource type"
|
||||
* , e); } return result.getPayload(); }
|
||||
*
|
||||
*
|
||||
* public <T extends Resource> List<Coding> getTagsForReference(Class<T>
|
||||
* resource, String id) { TagListRequest result = null; try { result =
|
||||
* utils.issueGetRequestForTagList(resourceAddress.resolveGetTagsForReference(
|
||||
* resource, id), withVer(getPreferredResourceFormat(), "1.0"), null, proxy); } catch (Exception
|
||||
* e) {
|
||||
* handleException("An error has occurred while trying to retrieve tags for this resource"
|
||||
* , e); } return result.getPayload(); }
|
||||
*
|
||||
*
|
||||
* public <T extends Resource> List<Coding> getTagsForResourceVersion(Class<T>
|
||||
* resource, String id, String versionId) { TagListRequest result = null; try {
|
||||
* result = utils.issueGetRequestForTagList(resourceAddress.
|
||||
* resolveGetTagsForResourceVersion(resource, id, versionId),
|
||||
* withVer(getPreferredResourceFormat(), "1.0"), null, proxy); } catch (Exception e) {
|
||||
* handleException("An error has occurred while trying to retrieve tags for this resource version"
|
||||
* , e); } return result.getPayload(); }
|
||||
*
|
||||
* // // public <T extends Resource> boolean deleteTagsForReference(Class<T>
|
||||
* resourceClass, String id) { // try { // return
|
||||
* utils.issueDeleteRequest(resourceAddress.resolveGetTagsForReference(
|
||||
* resourceClass, id), proxy); // } catch(Exception e) { //
|
||||
* handleException("An error has occurred while trying to retrieve tags for this resource version"
|
||||
* , e); // throw new
|
||||
* EFhirClientException("An error has occurred while trying to delete this resource"
|
||||
* , e); // } // // } // // // public <T extends Resource> boolean
|
||||
* deleteTagsForResourceVersion(Class<T> resourceClass, String id, List<Coding>
|
||||
* tags, String version) { // try { // return
|
||||
* utils.issueDeleteRequest(resourceAddress.resolveGetTagsForResourceVersion(
|
||||
* resourceClass, id, version), proxy); // } catch(Exception e) { //
|
||||
* handleException("An error has occurred while trying to retrieve tags for this resource version"
|
||||
* , e); // throw new
|
||||
* EFhirClientException("An error has occurred while trying to delete this resource"
|
||||
* , e); // } // }
|
||||
*
|
||||
*
|
||||
* public <T extends Resource> List<Coding> createTags(List<Coding> tags,
|
||||
* Class<T> resourceClass, String id) { TagListRequest request = null; try {
|
||||
* request =
|
||||
* utils.issuePostRequestForTagList(resourceAddress.resolveGetTagsForReference(
|
||||
* resourceClass, id),utils.getTagListAsByteArray(tags, false,
|
||||
* isJson(getPreferredResourceFormat())), withVer(getPreferredResourceFormat(), "1.0"), null,
|
||||
* proxy); request.addSuccessStatus(201); request.addSuccessStatus(200);
|
||||
* if(request.isUnsuccessfulRequest()) { throw new
|
||||
* EFhirClientException("Server responded with HTTP error code " +
|
||||
* request.getHttpStatus()); } } catch(Exception e) {
|
||||
* handleException("An error has occurred while trying to set tags for this resource"
|
||||
* , e); } return request.getPayload(); }
|
||||
*
|
||||
*
|
||||
* public <T extends Resource> List<Coding> createTags(List<Coding> tags,
|
||||
* Class<T> resourceClass, String id, String version) { TagListRequest request =
|
||||
* null; try { request = utils.issuePostRequestForTagList(resourceAddress.
|
||||
* resolveGetTagsForResourceVersion(resourceClass, id,
|
||||
* version),utils.getTagListAsByteArray(tags, false,
|
||||
* isJson(getPreferredResourceFormat())), withVer(getPreferredResourceFormat(), "1.0"), null,
|
||||
* proxy); request.addSuccessStatus(201); request.addSuccessStatus(200);
|
||||
* if(request.isUnsuccessfulRequest()) { throw new
|
||||
* EFhirClientException("Server responded with HTTP error code " +
|
||||
* request.getHttpStatus()); } } catch(Exception e) {
|
||||
* handleException("An error has occurred while trying to set the tags for this resource version"
|
||||
* , e); } return request.getPayload(); }
|
||||
*
|
||||
*
|
||||
* public <T extends Resource> List<Coding> deleteTags(List<Coding> tags,
|
||||
* Class<T> resourceClass, String id, String version) { TagListRequest request =
|
||||
* null; try { request = utils.issuePostRequestForTagList(resourceAddress.
|
||||
* resolveDeleteTagsForResourceVersion(resourceClass, id,
|
||||
* version),utils.getTagListAsByteArray(tags, false,
|
||||
* isJson(getPreferredResourceFormat())), withVer(getPreferredResourceFormat(), "1.0"), null,
|
||||
* proxy); request.addSuccessStatus(201); request.addSuccessStatus(200);
|
||||
* if(request.isUnsuccessfulRequest()) { throw new
|
||||
* EFhirClientException("Server responded with HTTP error code " +
|
||||
* request.getHttpStatus()); } } catch(Exception e) {
|
||||
* handleException("An error has occurred while trying to delete the tags for this resource version"
|
||||
* , e); } return request.getPayload(); }
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helper method to prevent nesting of previously thrown EFhirClientExceptions
|
||||
*
|
||||
* @param e
|
||||
* @throws EFhirClientException
|
||||
* Helper method to prevent nesting of previously thrown EFhirClientExceptions. If the e param is an instance of
|
||||
* EFhirClientException, it will be rethrown. Otherwise, a new EFhirClientException will be thrown with e as the
|
||||
* cause.
|
||||
*
|
||||
* @param message The EFhirClientException message.
|
||||
* @param e The exception.
|
||||
* @throws EFhirClientException representing the exception.
|
||||
*/
|
||||
protected void handleException(String message, Exception e) throws EFhirClientException {
|
||||
if (e instanceof EFhirClientException) {
|
||||
|
@ -702,8 +422,8 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
* Helper method to determine whether desired resource representation is Json or
|
||||
* XML.
|
||||
*
|
||||
* @param format
|
||||
* @return
|
||||
* @param format the format to check
|
||||
* @return true if JSON, false if XML
|
||||
*/
|
||||
protected boolean isJson(String format) {
|
||||
boolean isJson = false;
|
||||
|
@ -784,13 +504,13 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
|
||||
public ValueSet expandValueset(ValueSet source, Parameters expParams) {
|
||||
recordUse();
|
||||
List<Header> headers = null;
|
||||
|
||||
Parameters p = expParams == null ? new Parameters() : expParams.copy();
|
||||
p.addParameter().setName("valueSet").setResource(source);
|
||||
ResourceRequest<Resource> result = utils.issuePostRequest(
|
||||
resourceAddress.resolveOperationUri(ValueSet.class, "expand"),
|
||||
utils.getResourceAsByteArray(p, false, isJson(getPreferredResourceFormat())), withVer(getPreferredResourceFormat(), "1.0"),
|
||||
headers, 4);
|
||||
null, 4);
|
||||
result.addErrorStatus(410); // gone
|
||||
result.addErrorStatus(404); // unknown
|
||||
result.addErrorStatus(405);
|
||||
|
@ -812,11 +532,11 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
recordUse();
|
||||
Parameters params = new Parameters();
|
||||
params.addParameter().setName("name").setValue(new StringType(name));
|
||||
List<Header> headers = null;
|
||||
|
||||
ResourceRequest<Resource> result = utils.issuePostRequest(
|
||||
resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
|
||||
utils.getResourceAsByteArray(params, false, isJson(getPreferredResourceFormat())), withVer(getPreferredResourceFormat(), "1.0"),
|
||||
headers, timeoutNormal);
|
||||
null, timeoutNormal);
|
||||
result.addErrorStatus(410);// gone
|
||||
result.addErrorStatus(404);// unknown
|
||||
result.addErrorStatus(405);
|
||||
|
@ -835,11 +555,11 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
Parameters params = new Parameters();
|
||||
params.addParameter().setName("name").setValue(new StringType(name));
|
||||
params.addParameter().setName("concept").setValue(coding);
|
||||
List<Header> headers = null;
|
||||
|
||||
ResourceRequest<Resource> result = utils.issuePostRequest(
|
||||
resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
|
||||
utils.getResourceAsByteArray(params, false, isJson(getPreferredResourceFormat())), withVer(getPreferredResourceFormat(), "1.0"),
|
||||
headers, timeoutOperation);
|
||||
null, timeoutOperation);
|
||||
result.addErrorStatus(410);// gone
|
||||
result.addErrorStatus(404);// unknown
|
||||
result.addErrorStatus(405);
|
||||
|
@ -861,22 +581,6 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
utils.setTimeout(timeout);
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return utils.getUsername();
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
utils.setUsername(username);
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return utils.getPassword();
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
utils.setPassword(password);
|
||||
}
|
||||
|
||||
public Parameters getTerminologyCapabilities() {
|
||||
return (Parameters) utils
|
||||
.issueGetResourceRequest(resourceAddress.resolveMetadataTxCaps(), withVer(getPreferredResourceFormat(), "1.0"), timeoutNormal)
|
||||
|
|
|
@ -233,9 +233,8 @@ public class ResourceAddress {
|
|||
/**
|
||||
* For now, assume this type of location header structure. Generalize later:
|
||||
* http://hl7connect.healthintersections.com.au/svc/fhir/318/_history/1
|
||||
*
|
||||
* @param serviceBase
|
||||
* @param locationHeader
|
||||
*
|
||||
* @param locationResponseHeader
|
||||
*/
|
||||
public static ResourceAddress.ResourceVersionedIdentifier parseCreateLocation(String locationResponseHeader) {
|
||||
Pattern pattern = Pattern.compile(REGEX_ID_WITH_HISTORY);
|
||||
|
|
|
@ -32,11 +32,15 @@ package org.hl7.fhir.dstu2.utils.client;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.hl7.fhir.dstu2.model.Resource;
|
||||
|
||||
public class ResourceRequest<T extends Resource> {
|
||||
@Getter
|
||||
private T payload;
|
||||
@Getter
|
||||
private int httpStatus = -1;
|
||||
@Getter
|
||||
private String location;
|
||||
private List<Integer> successfulStatuses = new ArrayList<Integer>();
|
||||
private List<Integer> errorStatuses = new ArrayList<Integer>();
|
||||
|
@ -67,14 +71,6 @@ public class ResourceRequest<T extends Resource> {
|
|||
this.location = location;
|
||||
}
|
||||
|
||||
public int getHttpStatus() {
|
||||
return httpStatus;
|
||||
}
|
||||
|
||||
public T getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
public T getReference() {
|
||||
T payloadResource = null;
|
||||
if (payload != null) {
|
||||
|
@ -99,7 +95,4 @@ public class ResourceRequest<T extends Resource> {
|
|||
this.errorStatuses.add(status);
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
return location;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,350 @@
|
|||
package org.hl7.fhir.dstu2.utils.client;
|
||||
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import org.hl7.fhir.dstu2.formats.JsonParser;
|
||||
import org.hl7.fhir.dstu2.model.*;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeader;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeaderUtil;
|
||||
import org.hl7.fhir.utilities.http.HTTPRequest;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class ClientUtilsTest {
|
||||
|
||||
public static final String DUMMY_LOCATION = "http://myhost/Patient/1";
|
||||
public static final int TIMEOUT = 5000;
|
||||
private MockWebServer server;
|
||||
private HttpUrl serverUrl;
|
||||
private ClientUtils clientUtils;
|
||||
|
||||
private final Address address = new Address()
|
||||
.setCity("Toronto")
|
||||
.setState("Ontario")
|
||||
.setCountry("Canada");
|
||||
private final HumanName humanName = new HumanName()
|
||||
.addGiven("Mark")
|
||||
.addFamily("Iantorno");
|
||||
private final Patient patient = new Patient()
|
||||
.addName(humanName)
|
||||
.addAddress(address)
|
||||
.setGender(Enumerations.AdministrativeGender.MALE);
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
setupMockServer();
|
||||
clientUtils = new ClientUtils();
|
||||
}
|
||||
|
||||
void setupMockServer() {
|
||||
server = new MockWebServer();
|
||||
serverUrl = server.url("/v1/endpoint");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test resource format headers are added correctly.")
|
||||
void addResourceFormatHeadersGET() {
|
||||
|
||||
String testFormat = "yaml";
|
||||
HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com").withMethod(HTTPRequest.HttpMethod.GET);
|
||||
|
||||
Iterable<HTTPHeader> headers = ClientUtils.getResourceFormatHeaders(request, testFormat);
|
||||
|
||||
Map<String, List<String>> headersMap = HTTPHeaderUtil.getMultimap(headers);
|
||||
Assertions.assertNotNull(headersMap.get("Accept"), "Accept header null.");
|
||||
Assertions.assertEquals(testFormat, headersMap.get("Accept").get(0),
|
||||
"Accept header not populated with expected value " + testFormat + ".");
|
||||
|
||||
Assertions.assertNull(headersMap.get("Content-Type"), "Content-Type header null.");
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test resource format headers are added correctly (POST).")
|
||||
void addResourceFormatHeadersPOST() {
|
||||
|
||||
String testFormat = "yaml";
|
||||
HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com").withMethod(HTTPRequest.HttpMethod.POST);
|
||||
|
||||
Iterable<HTTPHeader> headers = ClientUtils.getResourceFormatHeaders(request, testFormat);
|
||||
|
||||
Map<String, List<String>> headersMap = HTTPHeaderUtil.getMultimap(headers);
|
||||
Assertions.assertNotNull(headersMap.get("Accept"), "Accept header null.");
|
||||
Assertions.assertEquals(testFormat, headersMap.get("Accept").get(0),
|
||||
"Accept header not populated with expected value " + testFormat + ".");
|
||||
|
||||
Assertions.assertNotNull(headersMap.get("Content-Type"), "Content-Type header null.");
|
||||
Assertions.assertEquals(testFormat + ";charset=" + ClientUtils.DEFAULT_CHARSET, headersMap.get("Content-Type").get(0),
|
||||
"Content-Type header not populated with expected value \"" + testFormat + ";charset=" + ClientUtils.DEFAULT_CHARSET + "\".");
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResourceFormatHeaders_GET() throws IOException, InterruptedException {
|
||||
server.enqueue(
|
||||
new MockResponse()
|
||||
.setBody(new String(generateResourceBytes(patient)))
|
||||
.addHeader("Content-Type", ResourceFormat.RESOURCE_JSON)
|
||||
);
|
||||
ResourceRequest<Patient> resourceRequest = clientUtils.issueGetResourceRequest(serverUrl.uri(), "application/json+fhir", TIMEOUT);
|
||||
RecordedRequest recordedRequest = server.takeRequest();
|
||||
assertThat(recordedRequest.getHeader("Accept")).isEqualTo("application/json+fhir");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResourceFormatHeaders_POST() throws IOException, InterruptedException {
|
||||
byte[] payload = generateResourceBytes(patient);
|
||||
server.enqueue(
|
||||
new MockResponse()
|
||||
.setBody(new String(payload))
|
||||
.addHeader("Content-Type", "application/json+fhir")
|
||||
.setResponseCode(201)
|
||||
);
|
||||
ResourceRequest<Patient> resourceRequest = clientUtils.issuePostRequest(serverUrl.uri(), payload, "application/json+fhir", TIMEOUT);
|
||||
RecordedRequest recordedRequest = server.takeRequest();
|
||||
assertThat(recordedRequest.getHeader("Accept")).isEqualTo("application/json+fhir");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResourceRequest() {
|
||||
ResourceRequest<Patient> request = new ResourceRequest<>(new Patient(), 200, "location");
|
||||
request.addSuccessStatus(200);
|
||||
assertTrue(request.getPayload().equalsDeep(new Patient()));
|
||||
assertThat(request.getHttpStatus()).isEqualTo(200);
|
||||
assertThat(request.getLocation()).isEqualTo("location");
|
||||
assertTrue(request.isSuccessfulRequest());
|
||||
assertFalse(request.isUnsuccessfulRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIssueGetResourceRequest() throws IOException {
|
||||
server.enqueue(
|
||||
new MockResponse()
|
||||
.setBody(new String(generateResourceBytes(patient)))
|
||||
.addHeader("Content-Type", "application/json+fhir")
|
||||
.addHeader("Location", DUMMY_LOCATION)
|
||||
);
|
||||
ResourceRequest<Patient> resourceRequest = clientUtils.issueGetResourceRequest(serverUrl.uri(), "application/json+fhir", TIMEOUT);
|
||||
resourceRequest.addSuccessStatus(200);
|
||||
assertThat(resourceRequest.getLocation()).isEqualTo(DUMMY_LOCATION);
|
||||
assertTrue(resourceRequest.isSuccessfulRequest());
|
||||
assertTrue(patient.equalsDeep(resourceRequest.getPayload()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIssueGetResourceRequest_withContentLocation() throws IOException {
|
||||
server.enqueue(
|
||||
new MockResponse()
|
||||
.setBody(new String(generateResourceBytes(patient)))
|
||||
.addHeader("Content-Type", "application/json+fhir")
|
||||
.addHeader("Content-Location", DUMMY_LOCATION)
|
||||
);
|
||||
ResourceRequest<Patient> resourceRequest = clientUtils.issueGetResourceRequest(serverUrl.uri(), "application/json+fhir", TIMEOUT);
|
||||
|
||||
assertThat(resourceRequest.getLocation()).isEqualTo(DUMMY_LOCATION);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test that getLocationHeader returns 'location' header when both 'location' and 'content-location' are set.")
|
||||
void testIssueGetResourceRequest_ReturnsLocationHeaderWhenBothSet() throws IOException {
|
||||
server.enqueue(
|
||||
new MockResponse()
|
||||
.setBody(new String(generateResourceBytes(patient)))
|
||||
.addHeader("Content-Type", "application/json+fhir")
|
||||
.addHeader("Location", DUMMY_LOCATION)
|
||||
.addHeader("Content-Location", "Wrong wrong wrong")
|
||||
);
|
||||
ResourceRequest<Patient> resourceRequest = clientUtils.issueGetResourceRequest(serverUrl.uri(), "application/json+fhir", TIMEOUT);
|
||||
assertThat(resourceRequest.getLocation()).isEqualTo(DUMMY_LOCATION);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test that getLocationHeader returns 'null' header when neither 'location' or 'content-location' are set.")
|
||||
void testIssueGetResourceRequest_ReturnsNullWhenNoHeadersSet() throws IOException {
|
||||
server.enqueue(
|
||||
new MockResponse()
|
||||
.setBody(new String(generateResourceBytes(patient)))
|
||||
.addHeader("Content-Type", "application/json+fhir")
|
||||
);
|
||||
ResourceRequest<Patient> resourceRequest = clientUtils.issueGetResourceRequest(serverUrl.uri(), "application/json+fhir", TIMEOUT);
|
||||
assertThat(resourceRequest.getLocation()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIssuePostRequest() throws IOException, InterruptedException {
|
||||
byte[] payload = generateResourceBytes(patient);
|
||||
server.enqueue(
|
||||
new MockResponse()
|
||||
.setBody(new String(payload))
|
||||
.addHeader("Content-Type", "application/json+fhir")
|
||||
.addHeader("Location", DUMMY_LOCATION)
|
||||
.setResponseCode(201)
|
||||
);
|
||||
ResourceRequest<Patient> resourceRequest = clientUtils.issuePostRequest(serverUrl.uri(), payload, "application/json+fhir", TIMEOUT);
|
||||
resourceRequest.addSuccessStatus(201);
|
||||
|
||||
RecordedRequest recordedRequest = server.takeRequest();
|
||||
assertArrayEquals(payload, recordedRequest.getBody().readByteArray(),
|
||||
"PUT request payload does not match send data.");
|
||||
assertThat(resourceRequest.getLocation()).isEqualTo(DUMMY_LOCATION);
|
||||
assertTrue(resourceRequest.isSuccessfulRequest());
|
||||
assertTrue(patient.equalsDeep(resourceRequest.getPayload()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIssuePutRequest() throws IOException, InterruptedException {
|
||||
byte[] payload = generateResourceBytes(patient);
|
||||
server.enqueue(
|
||||
new MockResponse()
|
||||
.setBody(new String(payload))
|
||||
.addHeader("Content-Type", "application/json+fhir")
|
||||
.addHeader("Location", DUMMY_LOCATION)
|
||||
.setResponseCode(200)
|
||||
);
|
||||
ResourceRequest<Patient> resourceRequest = clientUtils.issuePutRequest(serverUrl.uri(), payload, "application/json+fhir", TIMEOUT);
|
||||
resourceRequest.addSuccessStatus(200);
|
||||
RecordedRequest recordedRequest = server.takeRequest();
|
||||
assertArrayEquals(payload, recordedRequest.getBody().readByteArray(),
|
||||
"PUT request payload does not match send data.");
|
||||
|
||||
assertThat(resourceRequest.getLocation()).isEqualTo(DUMMY_LOCATION);
|
||||
assertTrue(resourceRequest.isSuccessfulRequest());
|
||||
assertTrue(patient.equalsDeep(resourceRequest.getPayload()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIssueDeleteRequest() throws IOException {
|
||||
server.enqueue(
|
||||
new MockResponse().addHeader("Location", DUMMY_LOCATION).setResponseCode(204)
|
||||
);
|
||||
boolean success = clientUtils.issueDeleteRequest(serverUrl.uri());
|
||||
assertTrue(success);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIssueDeleteRequest_fail() throws IOException, InterruptedException {
|
||||
server.enqueue(
|
||||
new MockResponse().addHeader("Location", DUMMY_LOCATION).setResponseCode(500)
|
||||
);
|
||||
boolean success = clientUtils.issueDeleteRequest(serverUrl.uri());
|
||||
RecordedRequest recordedRequest = server.takeRequest();
|
||||
assertThat(recordedRequest.getMethod()).isEqualTo("DELETE");
|
||||
assertThat(recordedRequest.getRequestUrl().uri()).isEqualTo(serverUrl.uri());
|
||||
assertFalse(success);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("test logger works")
|
||||
public void testLogger() throws IOException, InterruptedException {
|
||||
byte[] payload = generateResourceBytes(patient);
|
||||
server.enqueue(
|
||||
new MockResponse()
|
||||
.setResponseCode(200)
|
||||
.setBody(new String(payload))
|
||||
);
|
||||
ToolingClientLogger mockLogger = Mockito.mock(ToolingClientLogger.class);
|
||||
clientUtils.setLogger(mockLogger);
|
||||
clientUtils.issuePostRequest(serverUrl.uri(), payload, "application/json+fhir"
|
||||
, null, TIMEOUT);
|
||||
server.takeRequest();
|
||||
Mockito.verify(mockLogger, Mockito.times(1))
|
||||
.logRequest(Mockito.anyString(), Mockito.anyString(), Mockito.anyList(), Mockito.any());
|
||||
Mockito.verify(mockLogger, Mockito.times(1))
|
||||
.logResponse(Mockito.anyString(), Mockito.anyList(), Mockito.any(), Mockito.anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test that FATAL issue severity triggers error.")
|
||||
void hasErrorTestFatal() {
|
||||
OperationOutcome outcome = new OperationOutcome();
|
||||
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.INFORMATION));
|
||||
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.NULL));
|
||||
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.WARNING));
|
||||
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.FATAL));
|
||||
Assertions.assertTrue(clientUtils.hasError(outcome), "Error check not triggered for FATAL issue severity.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test that ERROR issue severity triggers error.")
|
||||
void hasErrorTestError() {
|
||||
OperationOutcome outcome = new OperationOutcome();
|
||||
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.INFORMATION));
|
||||
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.NULL));
|
||||
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.WARNING));
|
||||
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.ERROR));
|
||||
Assertions.assertTrue(clientUtils.hasError(outcome), "Error check not triggered for ERROR issue severity.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test that no FATAL or ERROR issue severity does not trigger error.")
|
||||
void hasErrorTestNoErrors() {
|
||||
OperationOutcome outcome = new OperationOutcome();
|
||||
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.INFORMATION));
|
||||
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.NULL));
|
||||
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.WARNING));
|
||||
Assertions.assertFalse(clientUtils.hasError(outcome), "Error check triggered unexpectedly.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("GET request, test client retries after timeout failure.")
|
||||
void test_get_retries_with_timeout() throws IOException, URISyntaxException {
|
||||
int failedAttempts = new Random().nextInt(5) + 1;
|
||||
System.out.println("Simulating <" + failedAttempts + "> failed connections (timeouts) before success.");
|
||||
for (int i = 0; i < failedAttempts; i++) {
|
||||
server.enqueue(new MockResponse().setHeadersDelay(TIMEOUT * 10, TimeUnit.MILLISECONDS)
|
||||
.setBody(new String(generateResourceBytes(patient))));
|
||||
}
|
||||
server.enqueue(new MockResponse().setBody(new String(generateResourceBytes(patient))));
|
||||
clientUtils.setRetryCount(failedAttempts + 1);
|
||||
|
||||
ResourceRequest<Resource> resourceRequest = clientUtils.issueGetResourceRequest(serverUrl.uri(), "application/json+fhir"
|
||||
, TIMEOUT);
|
||||
resourceRequest.addSuccessStatus(200);
|
||||
Assertions.assertTrue(resourceRequest.isSuccessfulRequest());
|
||||
Assertions.assertTrue(patient.equalsDeep(resourceRequest.getPayload()),
|
||||
"GET request returned resource does not match expected.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("GET request, test client retries after bad response.")
|
||||
void test_get_retries_with_unsuccessful_response() throws IOException, URISyntaxException {
|
||||
int failedAttempts = new Random().nextInt(5) + 1;
|
||||
System.out.println("Simulating <" + failedAttempts + "> failed connections (bad response codes) before success.");
|
||||
for (int i = 0; i < failedAttempts; i++) {
|
||||
server.enqueue(new MockResponse().setResponseCode(400 + i).setBody(new String(generateResourceBytes(patient))));
|
||||
}
|
||||
server.enqueue(new MockResponse().setBody(new String(generateResourceBytes(patient))));
|
||||
clientUtils.setRetryCount(failedAttempts + 1);
|
||||
|
||||
ResourceRequest<Resource> resourceRequest = clientUtils.issueGetResourceRequest(serverUrl.uri(), "application/json+fhir", TIMEOUT);
|
||||
resourceRequest.addSuccessStatus(200);
|
||||
Assertions.assertTrue(resourceRequest.isSuccessfulRequest());
|
||||
Assertions.assertTrue(patient.equalsDeep(resourceRequest.getPayload()),
|
||||
"GET request returned resource does not match expected.");
|
||||
}
|
||||
|
||||
|
||||
byte[] generateResourceBytes(Resource resource) throws IOException {
|
||||
return new JsonParser().composeBytes(resource);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
package org.hl7.fhir.dstu2.utils.client;
|
||||
|
||||
import org.hl7.fhir.dstu2.model.*;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class FHIRToolingClientTest {
|
||||
|
||||
private final Address address = new Address()
|
||||
.setCity("Toronto")
|
||||
.setState("Ontario")
|
||||
.setCountry("Canada");
|
||||
private final HumanName humanName = new HumanName()
|
||||
.addGiven("Mark")
|
||||
.addFamily("Iantorno");
|
||||
private final Patient patient = new Patient()
|
||||
.addName(humanName)
|
||||
.addAddress(address)
|
||||
.setGender(Enumerations.AdministrativeGender.MALE);
|
||||
|
||||
private ClientUtils mockClientUtils;
|
||||
private FHIRToolingClient toolingClient;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<URI> uriArgumentCaptor;
|
||||
|
||||
@BeforeEach
|
||||
public void beforeEach() throws URISyntaxException {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
mockClientUtils = Mockito.mock(ClientUtils.class);
|
||||
|
||||
toolingClient = new FHIRToolingClient("http://dummy-base-url.com", "dummy-user-agent") {
|
||||
@Override
|
||||
protected ClientUtils getClientUtils() {
|
||||
return mockClientUtils;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Need to reset here. When initialized, the client makes a call to getConformanceStatementQuick, which messes with
|
||||
our expected calls.
|
||||
*/
|
||||
reset(mockClientUtils);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTerminologyCapabilities() throws URISyntaxException {
|
||||
|
||||
Parameters expectedCapabilities = new Parameters();
|
||||
expectedCapabilities.addParameter().setName("name").setValue(new StringType("dummyValue"));
|
||||
|
||||
when(mockClientUtils.issueGetResourceRequest(uriArgumentCaptor.capture(), Mockito.anyString(), Mockito.anyInt()))
|
||||
.thenReturn(new ResourceRequest<>(expectedCapabilities, 200, "location"));
|
||||
Parameters actualCapabilities = toolingClient.getTerminologyCapabilities();
|
||||
|
||||
Mockito.verify(mockClientUtils).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(), ArgumentMatchers.anyInt());
|
||||
assertThat(actualCapabilities).isEqualTo(expectedCapabilities);
|
||||
|
||||
assertThat(uriArgumentCaptor.getValue()).isEqualTo(new URI("http://dummy-base-url.com/metadata?mode=terminology"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetConformanceStatement() throws URISyntaxException {
|
||||
|
||||
Conformance expectedConformance = new Conformance();
|
||||
expectedConformance.setCopyright("dummyCopyright");
|
||||
when(mockClientUtils.issueGetResourceRequest(uriArgumentCaptor.capture(), Mockito.anyString(), Mockito.anyInt()))
|
||||
.thenReturn(new ResourceRequest<>(expectedConformance, 200, "location"));
|
||||
Conformance actualConformance = toolingClient.getConformanceStatement();
|
||||
|
||||
Mockito.verify(mockClientUtils).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(), ArgumentMatchers.anyInt());
|
||||
assertThat(actualConformance).isEqualTo(expectedConformance);
|
||||
|
||||
assertThat(uriArgumentCaptor.getValue()).isEqualTo(new URI("http://dummy-base-url.com/metadata"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetConformanceStatementQuick() throws URISyntaxException {
|
||||
|
||||
Conformance expectedConformance = new Conformance();
|
||||
expectedConformance.setCopyright("dummyCopyright");
|
||||
when(mockClientUtils.issueGetResourceRequest(uriArgumentCaptor.capture(), Mockito.anyString(), Mockito.anyInt()))
|
||||
.thenReturn(new ResourceRequest<>(expectedConformance, 200, "location"));
|
||||
Conformance actualConformance = toolingClient.getConformanceStatementQuick();
|
||||
|
||||
Mockito.verify(mockClientUtils).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(), ArgumentMatchers.anyInt());
|
||||
assertThat(actualConformance).isEqualTo(expectedConformance);
|
||||
|
||||
assertThat(uriArgumentCaptor.getValue()).isEqualTo(new URI("http://dummy-base-url.com/metadata?_summary=true"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRead() {
|
||||
when(mockClientUtils.issueGetResourceRequest(uriArgumentCaptor.capture(), Mockito.anyString(), Mockito.anyInt()))
|
||||
.thenReturn(new ResourceRequest<>(patient, 200, "location"));
|
||||
Patient actualPatient = toolingClient.read(Patient.class, "id");
|
||||
|
||||
Mockito.verify(mockClientUtils).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(), ArgumentMatchers.anyInt());
|
||||
assertThat(actualPatient).isEqualTo(patient);
|
||||
|
||||
assertThat(uriArgumentCaptor.getValue().toString()).isEqualTo("http://dummy-base-url.com/Patient/id");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testVRead() {
|
||||
when(mockClientUtils.issueGetResourceRequest(uriArgumentCaptor.capture(), Mockito.anyString(), Mockito.anyInt()))
|
||||
.thenReturn(new ResourceRequest<>(patient, 200, "location"));
|
||||
Patient actualPatient = toolingClient.vread(Patient.class, "id", "version");
|
||||
|
||||
Mockito.verify(mockClientUtils).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(), ArgumentMatchers.anyInt());
|
||||
assertThat(actualPatient).isEqualTo(patient);
|
||||
|
||||
assertThat(uriArgumentCaptor.getValue().toString()).isEqualTo("http://dummy-base-url.com/Patient/id/_history/version");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCanonical() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.addEntry().setResource(patient);
|
||||
when(mockClientUtils.issueGetResourceRequest(uriArgumentCaptor.capture(), Mockito.anyString(), Mockito.anyInt()))
|
||||
.thenReturn(new ResourceRequest<>(bundle, 200, "location"));
|
||||
Patient actualPatient = toolingClient.getCanonical(Patient.class, "canonicalURL");
|
||||
|
||||
Mockito.verify(mockClientUtils).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(), ArgumentMatchers.anyInt());
|
||||
assertThat(actualPatient).isEqualTo(patient);
|
||||
|
||||
assertThat(uriArgumentCaptor.getValue().toString()).isEqualTo("http://dummy-base-url.com/Patient?url=canonicalURL");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdate() {
|
||||
final byte[] dummyBytes = "dummyBytes".getBytes();
|
||||
when(mockClientUtils.getResourceAsByteArray(any(Patient.class), anyBoolean(), anyBoolean())).thenReturn(dummyBytes);
|
||||
when(mockClientUtils.issuePutRequest(uriArgumentCaptor.capture(), Mockito.any(byte[].class), Mockito.anyString(), Mockito.isNull(), Mockito.anyInt()))
|
||||
.thenReturn(new ResourceRequest<>(patient, 200, "location"));
|
||||
Patient actualPatient = toolingClient.update(Patient.class, patient, "id");
|
||||
|
||||
Mockito.verify(mockClientUtils).issuePutRequest(ArgumentMatchers.any(URI.class), Mockito.any(byte[].class), ArgumentMatchers.anyString(), ArgumentMatchers.isNull(),ArgumentMatchers.anyInt());
|
||||
assertThat(actualPatient).isEqualTo(patient);
|
||||
|
||||
assertThat(uriArgumentCaptor.getValue().toString()).isEqualTo("http://dummy-base-url.com/Patient/id");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidate() {
|
||||
final byte[] dummyBytes = "dummyBytes".getBytes();
|
||||
final OperationOutcome expectedOutcome = new OperationOutcome();
|
||||
OperationOutcome.OperationOutcomeIssueComponent issueComponent = expectedOutcome.addIssue();
|
||||
issueComponent.setSeverity(OperationOutcome.IssueSeverity.ERROR);
|
||||
when(mockClientUtils.getResourceAsByteArray(any(Patient.class), anyBoolean(), anyBoolean())).thenReturn(dummyBytes);
|
||||
when(mockClientUtils.issuePostRequest(uriArgumentCaptor.capture(), Mockito.any(byte[].class), Mockito.anyString(), Mockito.anyInt()))
|
||||
.thenReturn(new ResourceRequest<>(expectedOutcome, 200, "location"));
|
||||
|
||||
OperationOutcome actualOutcome = toolingClient.validate(Patient.class, patient, "id");
|
||||
assertThat(actualOutcome).isEqualTo(expectedOutcome);
|
||||
|
||||
assertThat(uriArgumentCaptor.getValue().toString()).isEqualTo("http://dummy-base-url.com/Patient/$validate/id");
|
||||
}
|
||||
}
|
|
@ -28,6 +28,12 @@
|
|||
<artifactId>hapi-fhir-base</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- UCUM -->
|
||||
<dependency>
|
||||
<groupId>org.fhir</groupId>
|
||||
|
@ -99,6 +105,13 @@
|
|||
</dependency>
|
||||
|
||||
<!-- Test dependencies -->
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>mockwebserver</artifactId>
|
||||
<optional>true</optional>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
|
|
|
@ -3,18 +3,15 @@ package org.hl7.fhir.dstu3.utils.client;
|
|||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
import org.hl7.fhir.dstu3.model.CapabilityStatement;
|
||||
import org.hl7.fhir.dstu3.model.CodeSystem;
|
||||
import org.hl7.fhir.dstu3.model.Coding;
|
||||
import org.hl7.fhir.dstu3.model.ConceptMap;
|
||||
import org.hl7.fhir.dstu3.model.ExpansionProfile;
|
||||
import org.hl7.fhir.dstu3.model.OperationOutcome;
|
||||
import org.hl7.fhir.dstu3.model.Parameters;
|
||||
import org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent;
|
||||
|
@ -31,8 +28,7 @@ import org.hl7.fhir.utilities.FhirPublication;
|
|||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.internal.http2.Header;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeader;
|
||||
|
||||
/**
|
||||
* Very Simple RESTful client. This is purely for use in the standalone
|
||||
|
@ -72,13 +68,17 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
private int maxResultSetSize = -1;//_count
|
||||
private CapabilityStatement capabilities;
|
||||
private Client client = new Client();
|
||||
private ArrayList<Header> headers = new ArrayList<>();
|
||||
private String username;
|
||||
private String password;
|
||||
private List<HTTPHeader> headers = new ArrayList<>();
|
||||
@Setter
|
||||
@Getter
|
||||
private String userAgent;
|
||||
private EnumSet<FhirPublication> allowedVersions;
|
||||
private String acceptLang;
|
||||
private String contentLang;
|
||||
@Setter
|
||||
@Getter
|
||||
private String acceptLanguage;
|
||||
@Setter
|
||||
private String contentLanguage;
|
||||
@Getter
|
||||
private int useCount;
|
||||
|
||||
//Pass endpoint for client - URI
|
||||
|
@ -150,7 +150,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
try {
|
||||
capabilities = (Parameters) client.issueGetResourceRequest(resourceAddress.resolveMetadataTxCaps(),
|
||||
withVer(getPreferredResourceFormat(), "3.0"),
|
||||
generateHeaders(),
|
||||
generateHeaders(false),
|
||||
"TerminologyCapabilities",
|
||||
timeoutNormal).getReference();
|
||||
} catch (Exception e) {
|
||||
|
@ -164,7 +164,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
try {
|
||||
conformance = (CapabilityStatement) client.issueGetResourceRequest(resourceAddress.resolveMetadataUri(false),
|
||||
withVer(getPreferredResourceFormat(), "3.0"),
|
||||
generateHeaders(),
|
||||
generateHeaders(false),
|
||||
"CapabilitiesStatement",
|
||||
timeoutNormal).getReference();
|
||||
} catch (Exception e) {
|
||||
|
@ -178,7 +178,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
try {
|
||||
capabilities = (CapabilityStatement) client.issueGetResourceRequest(resourceAddress.resolveMetadataUri(true),
|
||||
withVer(getPreferredResourceFormat(), "3.0"),
|
||||
generateHeaders(),
|
||||
generateHeaders(false),
|
||||
"CapabilitiesStatement-Quick",
|
||||
timeoutNormal).getReference();
|
||||
} catch (Exception e) {
|
||||
|
@ -193,7 +193,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
try {
|
||||
result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),
|
||||
withVer(getPreferredResourceFormat(), "3.0"),
|
||||
generateHeaders(),
|
||||
generateHeaders(false),
|
||||
"Read " + resourceClass.getName() + "/" + id,
|
||||
timeoutNormal);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
|
@ -211,7 +211,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
try {
|
||||
result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndIdAndVersion(resourceClass, id, version),
|
||||
withVer(getPreferredResourceFormat(), "3.0"),
|
||||
generateHeaders(),
|
||||
generateHeaders(false),
|
||||
"VRead " + resourceClass.getName() + "/" + id + "/?_history/" + version,
|
||||
timeoutNormal);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
|
@ -229,7 +229,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
try {
|
||||
result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndCanonical(resourceClass, canonicalURL),
|
||||
withVer(getPreferredResourceFormat(), "3.0"),
|
||||
generateHeaders(),
|
||||
generateHeaders(false),
|
||||
"Read " + resourceClass.getName() + "?url=" + canonicalURL,
|
||||
timeoutNormal);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
|
@ -253,7 +253,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
result = client.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resource.getClass(), resource.getId()),
|
||||
ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat()), false),
|
||||
withVer(getPreferredResourceFormat(), "3.0"),
|
||||
generateHeaders(),
|
||||
generateHeaders(true),
|
||||
"Update " + resource.fhirType() + "/" + resource.getId(),
|
||||
timeoutOperation);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
|
@ -281,7 +281,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
result = client.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),
|
||||
ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat()), false),
|
||||
withVer(getPreferredResourceFormat(), "3.0"),
|
||||
generateHeaders(),
|
||||
generateHeaders(true),
|
||||
"Update " + resource.fhirType() + "/" + id,
|
||||
timeoutOperation);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
|
@ -320,13 +320,13 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
if (client.getLogger() != null) {
|
||||
client.getLogger().logRequest("POST", url.toString(), null, body);
|
||||
}
|
||||
result = client.issuePostRequest(url, body, withVer(getPreferredResourceFormat(), "3.0"), generateHeaders(),
|
||||
result = client.issuePostRequest(url, body, withVer(getPreferredResourceFormat(), "3.0"), generateHeaders(true),
|
||||
"POST " + resourceClass.getName() + "/$" + name, timeoutLong);
|
||||
} else {
|
||||
if (client.getLogger() != null) {
|
||||
client.getLogger().logRequest("GET", url.toString(), null, null);
|
||||
}
|
||||
result = client.issueGetResourceRequest(url, withVer(getPreferredResourceFormat(), "3.0"), generateHeaders(), "GET " + resourceClass.getName() + "/$" + name, timeoutLong);
|
||||
result = client.issueGetResourceRequest(url, withVer(getPreferredResourceFormat(), "3.0"), generateHeaders(false), "GET " + resourceClass.getName() + "/$" + name, timeoutLong);
|
||||
}
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
|
||||
|
@ -363,7 +363,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
try {
|
||||
result = client.issuePostRequest(resourceAddress.resolveValidateUri(resourceClass, id),
|
||||
ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat()), false),
|
||||
withVer(getPreferredResourceFormat(), "3.0"), generateHeaders(),
|
||||
withVer(getPreferredResourceFormat(), "3.0"), generateHeaders(true),
|
||||
"POST " + resourceClass.getName() + (id != null ? "/" + id : "") + "/$validate", timeoutLong);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
|
||||
|
@ -375,10 +375,14 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
}
|
||||
|
||||
/**
|
||||
* Helper method to prevent nesting of previously thrown EFhirClientExceptions
|
||||
* Helper method to prevent nesting of previously thrown EFhirClientExceptions. If the e param is an instance of
|
||||
* EFhirClientException, it will be rethrown. Otherwise, a new EFhirClientException will be thrown with e as the
|
||||
* cause.
|
||||
*
|
||||
* @param e
|
||||
* @throws EFhirClientException
|
||||
|
||||
* @param message The EFhirClientException message.
|
||||
* @param e The exception
|
||||
* @throws EFhirClientException EFhirClientException representing the exception.
|
||||
*/
|
||||
protected void handleException(String message, Exception e) throws EFhirClientException {
|
||||
if (e instanceof EFhirClientException) {
|
||||
|
@ -389,11 +393,11 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
}
|
||||
|
||||
/**
|
||||
* Helper method to determine whether desired resource representation
|
||||
* is Json or XML.
|
||||
* Helper method to determine whether desired resource representation is Json or
|
||||
* XML.
|
||||
*
|
||||
* @param format
|
||||
* @return
|
||||
* @param format the format
|
||||
* @return true if the format is JSON, false otherwise
|
||||
*/
|
||||
protected boolean isJson(String format) {
|
||||
boolean isJson = false;
|
||||
|
@ -420,7 +424,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
try {
|
||||
result = client.issueGetResourceRequest(resourceAddress.resolveOperationUri(CodeSystem.class, "lookup", params),
|
||||
withVer(getPreferredResourceFormat(), "3.0"),
|
||||
generateHeaders(),
|
||||
generateHeaders(false),
|
||||
"CodeSystem/$lookup",
|
||||
timeoutNormal);
|
||||
} catch (IOException e) {
|
||||
|
@ -439,7 +443,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
result = client.issuePostRequest(resourceAddress.resolveOperationUri(CodeSystem.class, "lookup"),
|
||||
ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat()), true),
|
||||
withVer(getPreferredResourceFormat(), "3.0"),
|
||||
generateHeaders(),
|
||||
generateHeaders(true),
|
||||
"CodeSystem/$lookup",
|
||||
timeoutNormal);
|
||||
} catch (IOException e) {
|
||||
|
@ -458,7 +462,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
result = client.issuePostRequest(resourceAddress.resolveOperationUri(ConceptMap.class, "transform"),
|
||||
ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat()), true),
|
||||
withVer(getPreferredResourceFormat(), "3.0"),
|
||||
generateHeaders(),
|
||||
generateHeaders(true),
|
||||
"ConceptMap/$transform",
|
||||
timeoutNormal);
|
||||
} catch (IOException e) {
|
||||
|
@ -479,7 +483,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
result = client.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand"),
|
||||
ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat()), true),
|
||||
withVer(getPreferredResourceFormat(), "3.0"),
|
||||
generateHeaders(),
|
||||
generateHeaders(true),
|
||||
"ValueSet/$expand?url=" + source.getUrl(),
|
||||
timeoutExpand);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
|
@ -504,7 +508,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
result = client.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
|
||||
ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()), true),
|
||||
withVer(getPreferredResourceFormat(), "3.0"),
|
||||
generateHeaders(),
|
||||
generateHeaders(true),
|
||||
"Closure?name=" + name,
|
||||
timeoutNormal);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
|
@ -526,7 +530,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
result = client.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
|
||||
ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()), true),
|
||||
withVer(getPreferredResourceFormat(), "3.0"),
|
||||
generateHeaders(),
|
||||
generateHeaders(true),
|
||||
"UpdateClosure?name=" + name,
|
||||
timeoutOperation);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
|
@ -538,21 +542,6 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
return result == null ? null : (ConceptMap) result.getPayload();
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public long getTimeout() {
|
||||
return client.getTimeout();
|
||||
|
@ -578,48 +567,27 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
client.setRetryCount(retryCount);
|
||||
}
|
||||
|
||||
public void setClientHeaders(ArrayList<Header> headers) {
|
||||
this.headers = headers;
|
||||
public void setClientHeaders(Iterable<HTTPHeader> headers) {
|
||||
this.headers =new ArrayList<>();
|
||||
headers.forEach(this.headers::add);
|
||||
}
|
||||
|
||||
private Headers generateHeaders() {
|
||||
Headers.Builder builder = new Headers.Builder();
|
||||
// Add basic auth header if it exists
|
||||
if (basicAuthHeaderExists()) {
|
||||
builder.add(getAuthorizationHeader().toString());
|
||||
}
|
||||
private Iterable<HTTPHeader> generateHeaders(boolean hasBody) {
|
||||
// Add any other headers
|
||||
if(this.headers != null) {
|
||||
this.headers.forEach(header -> builder.add(header.toString()));
|
||||
}
|
||||
List<HTTPHeader> headers = new ArrayList<>(this.headers);
|
||||
if (!Utilities.noString(userAgent)) {
|
||||
builder.add("User-Agent: "+userAgent);
|
||||
}
|
||||
if (!Utilities.noString(acceptLang)) {
|
||||
builder.add("Accept-Language: "+acceptLang);
|
||||
headers.add(new HTTPHeader("User-Agent",userAgent));
|
||||
}
|
||||
if (!Utilities.noString(contentLang)) {
|
||||
builder.add("Content-Language: "+contentLang);
|
||||
|
||||
if (!Utilities.noString(acceptLanguage)) {
|
||||
headers.add(new HTTPHeader("Accept-Language", acceptLanguage));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public boolean basicAuthHeaderExists() {
|
||||
return (username != null) && (password != null);
|
||||
}
|
||||
if (hasBody && !Utilities.noString(contentLanguage)) {
|
||||
headers.add(new HTTPHeader("Content-Language",contentLanguage));
|
||||
}
|
||||
|
||||
public Header getAuthorizationHeader() {
|
||||
String usernamePassword = username + ":" + password;
|
||||
String base64usernamePassword = Base64.getEncoder().encodeToString(usernamePassword.getBytes());
|
||||
return new Header("Authorization", "Basic " + base64usernamePassword);
|
||||
}
|
||||
|
||||
public String getUserAgent() {
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
public void setUserAgent(String userAgent) {
|
||||
this.userAgent = userAgent;
|
||||
return headers;
|
||||
}
|
||||
|
||||
public String getServerVersion() {
|
||||
|
@ -627,17 +595,6 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
return capabilities == null ? null : capabilities.getSoftware().getVersion();
|
||||
}
|
||||
|
||||
public void setAcceptLanguage(String lang) {
|
||||
this.acceptLang = lang;
|
||||
}
|
||||
public void setContentLanguage(String lang) {
|
||||
this.contentLang = lang;
|
||||
}
|
||||
|
||||
public int getUseCount() {
|
||||
return useCount;
|
||||
}
|
||||
|
||||
private void recordUse() {
|
||||
useCount++;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.hl7.fhir.dstu3.utils.client.network;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
@ -10,10 +11,8 @@ import org.hl7.fhir.dstu3.model.Resource;
|
|||
import org.hl7.fhir.dstu3.utils.client.EFhirClientException;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeader;
|
||||
import org.hl7.fhir.utilities.http.HTTPRequest;
|
||||
|
||||
public class Client {
|
||||
|
||||
|
@ -61,21 +60,22 @@ public class Client {
|
|||
String resourceFormat,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
Request.Builder request = new Request.Builder()
|
||||
.method("OPTIONS", null)
|
||||
.url(optionsUri.toURL());
|
||||
|
||||
return executeFhirRequest(request, resourceFormat, new Headers.Builder().build(), message, retryCount, timeout);
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(optionsUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.OPTIONS);
|
||||
|
||||
return executeFhirRequest(request, resourceFormat, Collections.emptyList(), message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issueGetResourceRequest(URI resourceUri,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
Iterable<HTTPHeader> headers,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL());
|
||||
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.GET);
|
||||
return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout);
|
||||
}
|
||||
|
||||
|
@ -87,21 +87,21 @@ public class Client {
|
|||
String resourceFormat,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
return issuePutRequest(resourceUri, payload, resourceFormat, new Headers.Builder().build(), message, timeout);
|
||||
return issuePutRequest(resourceUri, payload, resourceFormat, Collections.emptyList(), message, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri,
|
||||
byte[] payload,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
Iterable<HTTPHeader> headers,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
if (payload == null) throw new EFhirClientException("PUT requests require a non-null payload");
|
||||
RequestBody body = RequestBody.create(payload);
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL())
|
||||
.put(body);
|
||||
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.PUT)
|
||||
.withBody(payload);
|
||||
return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout);
|
||||
}
|
||||
|
||||
|
@ -110,36 +110,36 @@ public class Client {
|
|||
String resourceFormat,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
return issuePostRequest(resourceUri, payload, resourceFormat, new Headers.Builder().build(), message, timeout);
|
||||
return issuePostRequest(resourceUri, payload, resourceFormat, Collections.emptyList(), message, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri,
|
||||
byte[] payload,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
Iterable<HTTPHeader> headers,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
if (payload == null) throw new EFhirClientException("POST requests require a non-null payload");
|
||||
RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL())
|
||||
.post(body);
|
||||
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.POST)
|
||||
.withBody(payload);
|
||||
return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public boolean issueDeleteRequest(URI resourceUri) throws IOException {
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL())
|
||||
.delete();
|
||||
return executeFhirRequest(request, null, new Headers.Builder().build(), null, retryCount, timeout).isSuccessfulRequest();
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.DELETE);
|
||||
return executeFhirRequest(request, null, Collections.emptyList(), null, retryCount, timeout).isSuccessfulRequest();
|
||||
}
|
||||
|
||||
public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat) throws IOException {
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL());
|
||||
|
||||
return executeBundleRequest(request, resourceFormat, new Headers.Builder().build(), null, retryCount, timeout);
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.GET);
|
||||
return executeBundleRequest(request, resourceFormat, Collections.emptyList(), null, retryCount, timeout);
|
||||
}
|
||||
|
||||
public Bundle issuePostFeedRequest(URI resourceUri,
|
||||
|
@ -149,12 +149,13 @@ public class Client {
|
|||
String resourceFormat) throws IOException {
|
||||
String boundary = "----WebKitFormBoundarykbMUo6H8QaUnYtRy";
|
||||
byte[] payload = ByteUtils.encodeFormSubmission(parameters, resourceName, resource, boundary);
|
||||
RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL())
|
||||
.post(body);
|
||||
|
||||
return executeBundleRequest(request, resourceFormat, new Headers.Builder().build(), null, retryCount, timeout);
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.POST)
|
||||
.withBody(payload);
|
||||
|
||||
return executeBundleRequest(request, resourceFormat, Collections.emptyList(), null, retryCount, timeout);
|
||||
}
|
||||
|
||||
public Bundle postBatchRequest(URI resourceUri,
|
||||
|
@ -163,17 +164,17 @@ public class Client {
|
|||
String message,
|
||||
int timeout) throws IOException {
|
||||
if (payload == null) throw new EFhirClientException("POST requests require a non-null payload");
|
||||
RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL())
|
||||
.post(body);
|
||||
|
||||
return executeBundleRequest(request, resourceFormat, new Headers.Builder().build(), message, retryCount, timeout);
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.POST)
|
||||
.withBody(payload);
|
||||
return executeBundleRequest(request, resourceFormat, Collections.emptyList(), message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> Bundle executeBundleRequest(Request.Builder request,
|
||||
public <T extends Resource> Bundle executeBundleRequest(HTTPRequest request,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
Iterable<HTTPHeader> headers,
|
||||
String message,
|
||||
int retryCount,
|
||||
long timeout) throws IOException {
|
||||
|
@ -182,23 +183,23 @@ public class Client {
|
|||
.withResourceFormat(resourceFormat)
|
||||
.withRetryCount(retryCount)
|
||||
.withMessage(message)
|
||||
.withHeaders(headers == null ? new Headers.Builder().build() : headers)
|
||||
.withHeaders(headers == null ? Collections.emptyList() : headers)
|
||||
.withTimeout(timeout, TimeUnit.MILLISECONDS)
|
||||
.executeAsBatch();
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> executeFhirRequest(Request.Builder request,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
String message,
|
||||
int retryCount,
|
||||
long timeout) throws IOException {
|
||||
public <T extends Resource> ResourceRequest<T> executeFhirRequest(HTTPRequest request,
|
||||
String resourceFormat,
|
||||
Iterable<HTTPHeader> headers,
|
||||
String message,
|
||||
int retryCount,
|
||||
long timeout) throws IOException {
|
||||
return new FhirRequestBuilder(request, base)
|
||||
.withLogger(logger)
|
||||
.withResourceFormat(resourceFormat)
|
||||
.withRetryCount(retryCount)
|
||||
.withMessage(message)
|
||||
.withHeaders(headers == null ? new Headers.Builder().build() : headers)
|
||||
.withHeaders(headers == null ? Collections.emptyList() : headers)
|
||||
.withTimeout(timeout, TimeUnit.MILLISECONDS)
|
||||
.execute();
|
||||
}
|
||||
|
|
|
@ -5,8 +5,7 @@ import java.util.List;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
|
||||
import okhttp3.internal.http2.Header;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeader;
|
||||
|
||||
/**
|
||||
* Generic Implementation of Client Headers.
|
||||
|
@ -16,30 +15,30 @@ import okhttp3.internal.http2.Header;
|
|||
*/
|
||||
public class ClientHeaders {
|
||||
|
||||
private final ArrayList<Header> headers;
|
||||
private final ArrayList<HTTPHeader> headers;
|
||||
|
||||
public ClientHeaders() {
|
||||
this.headers = new ArrayList<>();
|
||||
}
|
||||
|
||||
public ClientHeaders(ArrayList<Header> headers) {
|
||||
public ClientHeaders(ArrayList<HTTPHeader> headers) {
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public ArrayList<Header> headers() {
|
||||
public ArrayList<HTTPHeader> headers() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a header to the list of stored headers for network operations.
|
||||
*
|
||||
* @param header {@link Header} to add to the list.
|
||||
* @param header {@link HTTPHeader} to add to the list.
|
||||
* @throws FHIRException if the header being added is a duplicate.
|
||||
*/
|
||||
public ClientHeaders addHeader(Header header) throws FHIRException {
|
||||
public ClientHeaders addHeader(HTTPHeader header) throws FHIRException {
|
||||
if (headers.contains(header)) {
|
||||
throw new FHIRException("Attempting to add duplicate header, <" + header.name + ", "
|
||||
+ header.value + ">.");
|
||||
throw new FHIRException("Attempting to add duplicate header, <" + header.getName() + ", "
|
||||
+ header.getValue() + ">.");
|
||||
}
|
||||
headers.add(header);
|
||||
return this;
|
||||
|
@ -48,39 +47,39 @@ public class ClientHeaders {
|
|||
/**
|
||||
* Add a header to the list of stored headers for network operations.
|
||||
*
|
||||
* @param headerList {@link List} of {@link Header} to add.
|
||||
* @param headerList {@link List} of {@link HTTPHeader} to add.
|
||||
* @throws FHIRException if any of the headers being added is a duplicate.
|
||||
*/
|
||||
public ClientHeaders addHeaders(List<Header> headerList) throws FHIRException {
|
||||
public ClientHeaders addHeaders(List<HTTPHeader> headerList) throws FHIRException {
|
||||
headerList.forEach(this::addHeader);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the passed in header from the list of stored headers.
|
||||
* @param header {@link Header} to remove from the list.
|
||||
* @param header {@link HTTPHeader} to remove from the list.
|
||||
* @throws FHIRException if the header passed in does not exist within the stored list.
|
||||
*/
|
||||
public ClientHeaders removeHeader(Header header) throws FHIRException {
|
||||
public ClientHeaders removeHeader(HTTPHeader header) throws FHIRException {
|
||||
if (!headers.remove(header)) {
|
||||
throw new FHIRException("Attempting to remove header, <" + header.name + ", "
|
||||
+ header.value + ">, from GenericClientHeaders that is not currently stored.");
|
||||
throw new FHIRException("Attempting to remove header, <" + header.getName() + ", "
|
||||
+ header.getValue() + ">, from GenericClientHeaders that is not currently stored.");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the passed in headers from the list of stored headers.
|
||||
* @param headerList {@link List} of {@link Header} to remove.
|
||||
* @param headerList {@link List} of {@link HTTPHeader} to remove.
|
||||
* @throws FHIRException if any of the headers passed in does not exist within the stored list.
|
||||
*/
|
||||
public ClientHeaders removeHeaders(List<Header> headerList) throws FHIRException {
|
||||
public ClientHeaders removeHeaders(List<HTTPHeader> headerList) throws FHIRException {
|
||||
headerList.forEach(this::removeHeader);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all stored {@link Header}.
|
||||
* Clears all stored {@link HTTPHeader}.
|
||||
*/
|
||||
public ClientHeaders clearHeaders() {
|
||||
headers.clear();
|
||||
|
@ -90,7 +89,7 @@ public class ClientHeaders {
|
|||
@Override
|
||||
public String toString() {
|
||||
return this.headers.stream()
|
||||
.map(header -> "\t" + header.name + ":" + header.value)
|
||||
.map(header -> "\t" + header.getName() + ":" + header.getValue())
|
||||
.collect(Collectors.joining(",\n", "{\n", "\n}"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,12 +2,11 @@ package org.hl7.fhir.dstu3.utils.client.network;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.dstu3.formats.IParser;
|
||||
|
@ -19,33 +18,20 @@ import org.hl7.fhir.dstu3.model.Resource;
|
|||
import org.hl7.fhir.dstu3.utils.ResourceUtilities;
|
||||
import org.hl7.fhir.dstu3.utils.client.EFhirClientException;
|
||||
import org.hl7.fhir.dstu3.utils.client.ResourceFormat;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.utilities.MimeType;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.hl7.fhir.utilities.settings.FhirSettings;
|
||||
import org.hl7.fhir.utilities.http.*;
|
||||
|
||||
import okhttp3.Authenticator;
|
||||
import okhttp3.Credentials;
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class FhirRequestBuilder {
|
||||
|
||||
protected static final String HTTP_PROXY_USER = "http.proxyUser";
|
||||
protected static final String HTTP_PROXY_PASS = "http.proxyPassword";
|
||||
protected static final String HEADER_PROXY_AUTH = "Proxy-Authorization";
|
||||
protected static final String LOCATION_HEADER = "location";
|
||||
protected static final String CONTENT_LOCATION_HEADER = "content-location";
|
||||
protected static final String DEFAULT_CHARSET = "UTF-8";
|
||||
/**
|
||||
* The singleton instance of the HttpClient, used for all requests.
|
||||
*/
|
||||
private static OkHttpClient okHttpClient;
|
||||
private final Request.Builder httpRequest;
|
||||
|
||||
private final HTTPRequest httpRequest;
|
||||
private String resourceFormat = null;
|
||||
private Headers headers = null;
|
||||
private Iterable<HTTPHeader> headers = null;
|
||||
private String message = null;
|
||||
private int retryCount = 1;
|
||||
/**
|
||||
|
@ -59,60 +45,41 @@ public class FhirRequestBuilder {
|
|||
/**
|
||||
* {@link ToolingClientLogger} for log output.
|
||||
*/
|
||||
@Getter @Setter
|
||||
private ToolingClientLogger logger = null;
|
||||
private String source;
|
||||
|
||||
public FhirRequestBuilder(Request.Builder httpRequest, String source) {
|
||||
public FhirRequestBuilder(HTTPRequest httpRequest, String source) {
|
||||
this.httpRequest = httpRequest;
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds necessary default headers, formatting headers, and any passed in {@link Headers} to the passed in
|
||||
* {@link okhttp3.Request.Builder}
|
||||
* Adds necessary default headers, formatting headers, and any passed in {@link HTTPHeader}s to the passed in
|
||||
* {@link HTTPRequest}
|
||||
*
|
||||
* @param request {@link okhttp3.Request.Builder} to add headers to.
|
||||
* @param request {@link HTTPRequest} to add headers to.
|
||||
* @param format Expected {@link Resource} format.
|
||||
* @param headers Any additional {@link Headers} to add to the request.
|
||||
* @param headers Any additional {@link HTTPHeader}s to add to the request.
|
||||
*/
|
||||
protected static void formatHeaders(Request.Builder request, String format, Headers headers) {
|
||||
addDefaultHeaders(request, headers);
|
||||
if (format != null) addResourceFormatHeaders(request, format);
|
||||
if (headers != null) addHeaders(request, headers);
|
||||
}
|
||||
protected static HTTPRequest formatHeaders(HTTPRequest request, String format, Iterable<HTTPHeader> headers) {
|
||||
List<HTTPHeader> allHeaders = new ArrayList<>();
|
||||
request.getHeaders().forEach(allHeaders::add);
|
||||
|
||||
/**
|
||||
* Adds necessary headers for all REST requests.
|
||||
* <li>User-Agent : hapi-fhir-tooling-client</li>
|
||||
*
|
||||
* @param request {@link Request.Builder} to add default headers to.
|
||||
*/
|
||||
protected static void addDefaultHeaders(Request.Builder request, Headers headers) {
|
||||
if (headers == null || !headers.names().contains("User-Agent")) {
|
||||
request.addHeader("User-Agent", "hapi-fhir-tooling-client");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds necessary headers for the given resource format provided.
|
||||
*
|
||||
* @param request {@link Request.Builder} to add default headers to.
|
||||
*/
|
||||
protected static void addResourceFormatHeaders(Request.Builder request, String format) {
|
||||
request.addHeader("Accept", format);
|
||||
request.addHeader("Content-Type", format + ";charset=" + DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through the passed in {@link Headers} and adds them to the provided {@link Request.Builder}.
|
||||
*
|
||||
* @param request {@link Request.Builder} to add headers to.
|
||||
* @param headers {@link Headers} to add to request.
|
||||
*/
|
||||
protected static void addHeaders(Request.Builder request, Headers headers) {
|
||||
if (headers != null) {
|
||||
headers.forEach(header -> request.addHeader(header.getFirst(), header.getSecond()));
|
||||
if (format != null) getResourceFormatHeaders(request, format).forEach(allHeaders::add);
|
||||
if (headers != null) headers.forEach(allHeaders::add);
|
||||
return request.withHeaders(allHeaders);
|
||||
}
|
||||
protected static Iterable<HTTPHeader> getResourceFormatHeaders(HTTPRequest httpRequest, String format) {
|
||||
List<HTTPHeader> headers = new ArrayList<>();
|
||||
headers.add(new HTTPHeader("Accept", format));
|
||||
if (httpRequest.getMethod() == HTTPRequest.HttpMethod.PUT
|
||||
|| httpRequest.getMethod() == HTTPRequest.HttpMethod.POST
|
||||
|| httpRequest.getMethod() == HTTPRequest.HttpMethod.PATCH
|
||||
) {
|
||||
headers.add(new HTTPHeader("Content-Type", format + ";charset=" + DEFAULT_CHARSET));
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -131,69 +98,24 @@ public class FhirRequestBuilder {
|
|||
}
|
||||
|
||||
/**
|
||||
* Extracts the 'location' header from the passes in {@link Headers}. If no value for 'location' exists, the
|
||||
* value for 'content-location' is returned. If neither header exists, we return null.
|
||||
* Extracts the 'location' header from the passed {@link Iterable<HTTPHeader>}. If no
|
||||
* value for 'location' exists, the value for 'content-location' is returned. If
|
||||
* neither header exists, we return null.
|
||||
*
|
||||
* @param headers {@link Headers} to evaluate
|
||||
* @param headers {@link HTTPHeader} to evaluate
|
||||
* @return {@link String} header value, or null if no location headers are set.
|
||||
*/
|
||||
protected static String getLocationHeader(Headers headers) {
|
||||
Map<String, List<String>> headerMap = headers.toMultimap();
|
||||
if (headerMap.containsKey(LOCATION_HEADER)) {
|
||||
return headerMap.get(LOCATION_HEADER).get(0);
|
||||
} else if (headerMap.containsKey(CONTENT_LOCATION_HEADER)) {
|
||||
return headerMap.get(CONTENT_LOCATION_HEADER).get(0);
|
||||
} else {
|
||||
return null;
|
||||
protected static String getLocationHeader(Iterable<HTTPHeader> headers) {
|
||||
String locationHeader = HTTPHeaderUtil.getSingleHeader(headers, LOCATION_HEADER);
|
||||
|
||||
if (locationHeader != null) {
|
||||
return locationHeader;
|
||||
}
|
||||
return HTTPHeaderUtil.getSingleHeader(headers, CONTENT_LOCATION_HEADER);
|
||||
}
|
||||
|
||||
/**
|
||||
* We only ever want to have one copy of the HttpClient kicking around at any given time. If we need to make changes
|
||||
* to any configuration, such as proxy settings, timeout, caches, etc, we can do a per-call configuration through
|
||||
* the {@link OkHttpClient#newBuilder()} method. That will return a builder that shares the same connection pool,
|
||||
* dispatcher, and configuration with the original client.
|
||||
* </p>
|
||||
* The {@link OkHttpClient} uses the proxy auth properties set in the current system properties. The reason we don't
|
||||
* set the proxy address and authentication explicitly, is due to the fact that this class is often used in conjunction
|
||||
* with other http client tools which rely on the system.properties settings to determine proxy settings. It's easier
|
||||
* to keep the method consistent across the board. ...for now.
|
||||
*
|
||||
* @return {@link OkHttpClient} instance
|
||||
*/
|
||||
protected OkHttpClient getHttpClient() {
|
||||
if (FhirSettings.isProhibitNetworkAccess()) {
|
||||
throw new FHIRException("Network Access is prohibited in this context");
|
||||
}
|
||||
|
||||
if (okHttpClient == null) {
|
||||
okHttpClient = new OkHttpClient();
|
||||
}
|
||||
|
||||
Authenticator proxyAuthenticator = getAuthenticator();
|
||||
|
||||
return okHttpClient.newBuilder()
|
||||
.addInterceptor(new RetryInterceptor(retryCount))
|
||||
.connectTimeout(timeout, timeoutUnit)
|
||||
.writeTimeout(timeout, timeoutUnit)
|
||||
.readTimeout(timeout, timeoutUnit)
|
||||
.proxyAuthenticator(proxyAuthenticator)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private static Authenticator getAuthenticator() {
|
||||
return (route, response) -> {
|
||||
final String httpProxyUser = System.getProperty(HTTP_PROXY_USER);
|
||||
final String httpProxyPass = System.getProperty(HTTP_PROXY_PASS);
|
||||
if (httpProxyUser != null && httpProxyPass != null) {
|
||||
String credential = Credentials.basic(httpProxyUser, httpProxyPass);
|
||||
return response.request().newBuilder()
|
||||
.header(HEADER_PROXY_AUTH, credential)
|
||||
.build();
|
||||
}
|
||||
return response.request().newBuilder().build();
|
||||
};
|
||||
protected ManagedFhirWebAccessor getManagedWebAccessor() {
|
||||
return ManagedWebAccess.fhirAccessor().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger);
|
||||
}
|
||||
|
||||
public FhirRequestBuilder withResourceFormat(String resourceFormat) {
|
||||
|
@ -201,7 +123,7 @@ public class FhirRequestBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public FhirRequestBuilder withHeaders(Headers headers) {
|
||||
public FhirRequestBuilder withHeaders(Iterable<HTTPHeader> headers) {
|
||||
this.headers = headers;
|
||||
return this;
|
||||
}
|
||||
|
@ -227,25 +149,16 @@ public class FhirRequestBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
protected Request buildRequest() {
|
||||
return httpRequest.build();
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> execute() throws IOException {
|
||||
formatHeaders(httpRequest, resourceFormat, headers);
|
||||
final Request request = httpRequest.build();
|
||||
log(request.method(), request.url().toString(), request.headers(), request.body() != null ? request.body().toString().getBytes() : null);
|
||||
Response response = getHttpClient().newCall(request).execute();
|
||||
HTTPRequest requestWithHeaders = formatHeaders(httpRequest, resourceFormat, headers);
|
||||
HTTPResult response = getManagedWebAccessor().httpCall(requestWithHeaders);
|
||||
T resource = unmarshalReference(response, resourceFormat);
|
||||
return new ResourceRequest<T>(resource, response.code(), getLocationHeader(response.headers()));
|
||||
return new ResourceRequest<T>(resource, response.getCode(), getLocationHeader(response.getHeaders()));
|
||||
}
|
||||
|
||||
public Bundle executeAsBatch() throws IOException {
|
||||
formatHeaders(httpRequest, resourceFormat, null);
|
||||
final Request request = httpRequest.build();
|
||||
log(request.method(), request.url().toString(), request.headers(), request.body() != null ? request.body().toString().getBytes() : null);
|
||||
|
||||
Response response = getHttpClient().newCall(request).execute();
|
||||
HTTPRequest requestWithHeaders = formatHeaders(httpRequest, resourceFormat, null);
|
||||
HTTPResult response = getManagedWebAccessor().httpCall(requestWithHeaders);
|
||||
return unmarshalFeed(response, resourceFormat);
|
||||
}
|
||||
|
||||
|
@ -253,14 +166,14 @@ public class FhirRequestBuilder {
|
|||
* Unmarshalls a resource from the response stream.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T extends Resource> T unmarshalReference(Response response, String format) {
|
||||
protected <T extends Resource> T unmarshalReference(HTTPResult response, String format) {
|
||||
T resource = null;
|
||||
OperationOutcome error = null;
|
||||
|
||||
if (response.body() != null) {
|
||||
if (response.getContent() != null) {
|
||||
try {
|
||||
byte[] body = response.body().bytes();
|
||||
log(response.code(), response.headers(), body);
|
||||
byte[] body = response.getContent();
|
||||
|
||||
resource = (T) getParser(format).parse(body);
|
||||
if (resource instanceof OperationOutcome && hasError((OperationOutcome) resource)) {
|
||||
error = (OperationOutcome) resource;
|
||||
|
@ -282,13 +195,13 @@ public class FhirRequestBuilder {
|
|||
/**
|
||||
* Unmarshalls Bundle from response stream.
|
||||
*/
|
||||
protected Bundle unmarshalFeed(Response response, String format) {
|
||||
protected Bundle unmarshalFeed(HTTPResult response, String format) {
|
||||
Bundle feed = null;
|
||||
OperationOutcome error = null;
|
||||
try {
|
||||
byte[] body = response.body().bytes();
|
||||
log(response.code(), response.headers(), body);
|
||||
String contentType = response.header("Content-Type");
|
||||
byte[] body = response.getContent();
|
||||
|
||||
String contentType = HTTPHeaderUtil.getSingleHeader(response.getHeaders(), "Content-Type");
|
||||
if (body != null) {
|
||||
if (contentType.contains(ResourceFormat.RESOURCE_XML.getHeader()) || contentType.contains(ResourceFormat.RESOURCE_JSON.getHeader()) || contentType.contains("text/xml+fhir")) {
|
||||
Resource rf = getParser(format).parse(body);
|
||||
|
@ -315,7 +228,7 @@ public class FhirRequestBuilder {
|
|||
/**
|
||||
* Returns the appropriate parser based on the format type passed in. Defaults to XML parser if a blank format is
|
||||
* provided...because reasons.
|
||||
* <p>
|
||||
* <p/>
|
||||
* Currently supports only "json" and "xml" formats.
|
||||
*
|
||||
* @param format One of "json" or "xml".
|
||||
|
@ -334,52 +247,4 @@ public class FhirRequestBuilder {
|
|||
throw new EFhirClientException("Invalid format: " + format);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the given {@link Request}, using the current {@link ToolingClientLogger}. If the current
|
||||
* {@link FhirRequestBuilder#logger} is null, no action is taken.
|
||||
*
|
||||
* @param method HTTP request method
|
||||
* @param url request URL
|
||||
* @param requestHeaders {@link Headers} for request
|
||||
* @param requestBody Byte array request
|
||||
*/
|
||||
protected void log(String method, String url, Headers requestHeaders, byte[] requestBody) {
|
||||
if (logger != null) {
|
||||
List<String> headerList = new ArrayList<>(Collections.emptyList());
|
||||
Map<String, List<String>> headerMap = requestHeaders.toMultimap();
|
||||
headerMap.keySet().forEach(key -> headerMap.get(key).forEach(value -> headerList.add(key + ":" + value)));
|
||||
|
||||
logger.logRequest(method, url, headerList, requestBody);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the given {@link Response}, using the current {@link ToolingClientLogger}. If the current
|
||||
* {@link FhirRequestBuilder#logger} is null, no action is taken.
|
||||
*
|
||||
* @param responseCode HTTP response code
|
||||
* @param responseHeaders {@link Headers} from response
|
||||
* @param responseBody Byte array response
|
||||
*/
|
||||
protected void log(int responseCode, Headers responseHeaders, byte[] responseBody) {
|
||||
if (logger != null) {
|
||||
List<String> headerList = new ArrayList<>(Collections.emptyList());
|
||||
Map<String, List<String>> headerMap = responseHeaders.toMultimap();
|
||||
headerMap.keySet().forEach(key -> headerMap.get(key).forEach(value -> headerList.add(key + ":" + value)));
|
||||
|
||||
try {
|
||||
if (logger != null) {
|
||||
logger.logResponse(Integer.toString(responseCode), headerList, responseBody, 0);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println("Error parsing response body passed in to logger ->\n" + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
// else { // TODO fix logs
|
||||
// System.out.println("Call to log HTTP response with null ToolingClientLogger set... are you forgetting to " +
|
||||
// "initialize your logger?");
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,225 @@
|
|||
package org.hl7.fhir.dstu3.utils.client;
|
||||
|
||||
import org.hl7.fhir.dstu3.model.*;
|
||||
import org.hl7.fhir.dstu3.utils.client.network.Client;
|
||||
import org.hl7.fhir.dstu3.utils.client.network.ResourceRequest;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeader;
|
||||
import org.hl7.fhir.utilities.http.HTTPRequest;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class FhirToolingClientTest {
|
||||
String TX_ADDR = "http://tx.fhir.org";
|
||||
|
||||
HTTPHeader h1 = new HTTPHeader("header1", "value1");
|
||||
HTTPHeader h2 = new HTTPHeader("header2", "value2");
|
||||
HTTPHeader h3 = new HTTPHeader("header3", "value3");
|
||||
|
||||
HTTPHeader agentHeader = new HTTPHeader("User-Agent", "fhir/test-cases");
|
||||
|
||||
private Client mockClient;
|
||||
private FHIRToolingClient toolingClient;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<Iterable<HTTPHeader>> headersArgumentCaptor;
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException, URISyntaxException {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
mockClient = Mockito.mock(Client.class);
|
||||
ResourceRequest<Resource> resourceResourceRequest = new ResourceRequest<>(generateBundle(), 200, "");
|
||||
|
||||
// GET
|
||||
Mockito.when(mockClient.issueGetResourceRequest(Mockito.any(URI.class), Mockito.anyString(),
|
||||
ArgumentMatchers.any(), Mockito.anyString(), Mockito.anyLong())).thenReturn(resourceResourceRequest);
|
||||
Mockito
|
||||
.when(mockClient.issueGetResourceRequest(Mockito.any(URI.class), Mockito.anyString(),
|
||||
ArgumentMatchers.any(), Mockito.eq("TerminologyCapabilities"), Mockito.anyLong()))
|
||||
.thenReturn(new ResourceRequest<>(new Parameters(), 200, "location"));
|
||||
Mockito
|
||||
.when(mockClient.issueGetResourceRequest(Mockito.any(URI.class), Mockito.anyString(),
|
||||
ArgumentMatchers.any(), Mockito.eq("CapabilitiesStatement"), Mockito.anyLong()))
|
||||
.thenReturn(new ResourceRequest<>(new CapabilityStatement(), 200, "location"));
|
||||
Mockito
|
||||
.when(mockClient.issueGetResourceRequest(Mockito.any(URI.class), Mockito.anyString(),
|
||||
ArgumentMatchers.any(), Mockito.eq("CapabilitiesStatement-Quick"), Mockito.anyLong()))
|
||||
.thenReturn(new ResourceRequest<>(new CapabilityStatement(), 200, "location"));
|
||||
|
||||
// PUT
|
||||
Mockito.when(mockClient.issuePutRequest(Mockito.any(URI.class), Mockito.any(byte[].class), Mockito.anyString(),
|
||||
ArgumentMatchers.any(), Mockito.anyString(), Mockito.anyLong())).thenReturn(resourceResourceRequest);
|
||||
// POST
|
||||
Mockito.when(mockClient.issuePostRequest(Mockito.any(URI.class), Mockito.any(byte[].class), Mockito.anyString(),
|
||||
ArgumentMatchers.any(), Mockito.anyString(), Mockito.anyLong())).thenReturn(resourceResourceRequest);
|
||||
Mockito
|
||||
.when(mockClient.issuePostRequest(Mockito.any(URI.class), Mockito.any(byte[].class), Mockito.anyString(),
|
||||
ArgumentMatchers.any(), Mockito.contains("validate"), Mockito.anyLong()))
|
||||
.thenReturn(new ResourceRequest<>(new OperationOutcome(), 200, "location"));
|
||||
// BUNDLE REQ
|
||||
Mockito
|
||||
.when(mockClient.executeBundleRequest(Mockito.any(HTTPRequest.class), Mockito.anyString(),
|
||||
ArgumentMatchers.any(), Mockito.anyString(), Mockito.anyInt(), Mockito.anyLong()))
|
||||
.thenReturn(generateBundle());
|
||||
toolingClient = new FHIRToolingClient(TX_ADDR, "fhir/test-cases");
|
||||
toolingClient.setClient(mockClient);
|
||||
}
|
||||
|
||||
private List<HTTPHeader> getHeaders() {
|
||||
return new ArrayList<>(Arrays.asList(h1, h2, h3));
|
||||
}
|
||||
|
||||
private List<HTTPHeader> getHeadersWithAgent() {
|
||||
return new ArrayList<>(Arrays.asList(h1, h2, h3, agentHeader));
|
||||
}
|
||||
|
||||
private Bundle generateBundle() {
|
||||
Patient patient = generatePatient();
|
||||
Observation observation = generateObservation();
|
||||
|
||||
// The observation refers to the patient using the ID, which is already
|
||||
// set to a temporary UUID
|
||||
observation.setSubject(new Reference(patient.getIdElement().getValue()));
|
||||
|
||||
// Create a bundle that will be used as a transaction
|
||||
Bundle bundle = new Bundle();
|
||||
|
||||
// Add the patient as an entry.
|
||||
bundle.addEntry().setFullUrl(patient.getIdElement().getValue()).setResource(patient).getRequest().setUrl("Patient")
|
||||
.setIfNoneExist("identifier=http://acme.org/mrns|12345").setMethod(Bundle.HTTPVerb.POST);
|
||||
|
||||
return bundle;
|
||||
}
|
||||
|
||||
private Patient generatePatient() {
|
||||
// Create a patient object
|
||||
Patient patient = new Patient();
|
||||
patient.addIdentifier().setSystem("http://acme.org/mrns").setValue("12345");
|
||||
patient.addName().setFamily("Jameson").addGiven("J").addGiven("Jonah");
|
||||
patient.setGender(Enumerations.AdministrativeGender.MALE);
|
||||
|
||||
// Give the patient a temporary UUID so that other resources in
|
||||
// the transaction can refer to it
|
||||
patient.setId(IdType.newRandomUuid());
|
||||
return patient;
|
||||
}
|
||||
|
||||
private Observation generateObservation() {
|
||||
// Create an observation object
|
||||
Observation observation = new Observation();
|
||||
observation.getCode().addCoding().setSystem("http://loinc.org").setCode("789-8")
|
||||
.setDisplay("Erythrocytes [#/volume] in Blood by Automated count");
|
||||
observation.setValue(new Quantity().setValue(4.12).setUnit("10 trillion/L").setSystem("http://unitsofmeasure.org")
|
||||
.setCode("10*12/L"));
|
||||
return observation;
|
||||
}
|
||||
|
||||
private void checkHeaders(Iterable<HTTPHeader> argumentCaptorValue) {
|
||||
List<HTTPHeader> capturedHeaders = new ArrayList<>();
|
||||
argumentCaptorValue.forEach(capturedHeaders::add);
|
||||
|
||||
getHeadersWithAgent().forEach(header -> {
|
||||
assertTrue(capturedHeaders.contains(header));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTerminologyCapabilities() throws IOException {
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.getTerminologyCapabilities();
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCapabilitiesStatement() throws IOException {
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.getCapabilitiesStatement();
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCapabilitiesStatementQuick() throws IOException {
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.getCapabilitiesStatementQuick();
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void read() throws IOException {
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.read(Patient.class, "id");
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void vread() throws IOException {
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.vread(Patient.class, "id", "version");
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCanonical() throws IOException {
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.getCanonical(Patient.class, "canonicalURL");
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void update() throws IOException {
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.update(generatePatient());
|
||||
Mockito.verify(mockClient).issuePutRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.any(byte[].class),
|
||||
ArgumentMatchers.anyString(), headersArgumentCaptor.capture(), ArgumentMatchers.anyString(),
|
||||
ArgumentMatchers.anyLong());
|
||||
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate() throws IOException {
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.validate(Patient.class, generatePatient(), "id");
|
||||
Mockito.verify(mockClient).issuePostRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.any(byte[].class),
|
||||
ArgumentMatchers.anyString(), headersArgumentCaptor.capture(), ArgumentMatchers.anyString(),
|
||||
ArgumentMatchers.anyLong());
|
||||
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
package org.hl7.fhir.dstu3.utils.client.network;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import org.hl7.fhir.dstu3.formats.JsonParser;
|
||||
import org.hl7.fhir.dstu3.model.*;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ClientTest {
|
||||
private static final long TIMEOUT = 5000;
|
||||
|
||||
private MockWebServer server;
|
||||
private HttpUrl serverUrl;
|
||||
private Client client;
|
||||
|
||||
private final Address address = new Address()
|
||||
.setCity("Toronto")
|
||||
.setState("Ontario")
|
||||
.setCountry("Canada");
|
||||
private final HumanName humanName = new HumanName()
|
||||
.addGiven("Mark")
|
||||
.setFamily("Iantorno");
|
||||
private final Patient patient = new Patient()
|
||||
.addName(humanName)
|
||||
.addAddress(address)
|
||||
.setGender(Enumerations.AdministrativeGender.MALE);
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
setupMockServer();
|
||||
client = new Client();
|
||||
}
|
||||
|
||||
void setupMockServer() {
|
||||
server = new MockWebServer();
|
||||
serverUrl = server.url("/v1/endpoint");
|
||||
}
|
||||
|
||||
byte[] generateResourceBytes(Resource resource) throws IOException {
|
||||
return new JsonParser().composeBytes(resource);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("GET request, happy path.")
|
||||
void test_get_happy_path() throws IOException, URISyntaxException {
|
||||
server.enqueue(
|
||||
new MockResponse()
|
||||
.setBody(new String(generateResourceBytes(patient)))
|
||||
);
|
||||
ResourceRequest<Resource> resourceRequest = client.issueGetResourceRequest(new URI(serverUrl.toString()),
|
||||
"json", null, null, TIMEOUT);
|
||||
Assertions.assertTrue(resourceRequest.isSuccessfulRequest());
|
||||
Assertions.assertTrue(patient.equalsDeep(resourceRequest.getPayload()),
|
||||
"GET request returned resource does not match expected.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("GET request, test client retries after timeout failure.")
|
||||
void test_get_retries_with_timeout() throws IOException, URISyntaxException {
|
||||
int failedAttempts = new Random().nextInt(5) + 1;
|
||||
System.out.println("Simulating <" + failedAttempts + "> failed connections (timeouts) before success.");
|
||||
for (int i = 0; i < failedAttempts; i++) {
|
||||
server.enqueue(
|
||||
new MockResponse()
|
||||
.setHeadersDelay(TIMEOUT * 10, TimeUnit.MILLISECONDS)
|
||||
.setBody(new String(generateResourceBytes(patient)))
|
||||
);
|
||||
}
|
||||
server.enqueue(new MockResponse().setBody(new String(generateResourceBytes(patient))));
|
||||
client.setRetryCount(failedAttempts + 1);
|
||||
|
||||
ResourceRequest<Resource> resourceRequest = client.issueGetResourceRequest(new URI(serverUrl.toString()),
|
||||
"json", null, null, TIMEOUT);
|
||||
Assertions.assertTrue(resourceRequest.isSuccessfulRequest());
|
||||
Assertions.assertTrue(patient.equalsDeep(resourceRequest.getPayload()),
|
||||
"GET request returned resource does not match expected.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("GET request, test client retries after bad response.")
|
||||
void test_get_retries_with_unsuccessful_response() throws IOException, URISyntaxException {
|
||||
int failedAttempts = new Random().nextInt(5) + 1;
|
||||
System.out.println("Simulating <" + failedAttempts + "> failed connections (bad response codes) before success.");
|
||||
for (int i = 0; i < failedAttempts; i++) {
|
||||
server.enqueue(
|
||||
new MockResponse()
|
||||
.setResponseCode(400 + i)
|
||||
.setBody(new String(generateResourceBytes(patient)))
|
||||
);
|
||||
}
|
||||
server.enqueue(new MockResponse().setBody(new String(generateResourceBytes(patient))));
|
||||
client.setRetryCount(failedAttempts + 1);
|
||||
|
||||
ResourceRequest<Resource> resourceRequest = client.issueGetResourceRequest(new URI(serverUrl.toString()),
|
||||
"json", null, null, TIMEOUT);
|
||||
Assertions.assertTrue(resourceRequest.isSuccessfulRequest());
|
||||
Assertions.assertTrue(patient.equalsDeep(resourceRequest.getPayload()),
|
||||
"GET request returned resource does not match expected.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("PUT request, test payload received by server matches sent.")
|
||||
void test_put() throws IOException, URISyntaxException, InterruptedException {
|
||||
byte[] payload = ByteUtils.resourceToByteArray(patient, true, false, false);
|
||||
// Mock server response of 200, with the same resource payload returned that we included in the PUT request
|
||||
server.enqueue(
|
||||
new MockResponse()
|
||||
.setResponseCode(200)
|
||||
.setBody(new String(payload))
|
||||
);
|
||||
|
||||
ResourceRequest<Resource> request = client.issuePutRequest(new URI(serverUrl.toString()), payload,
|
||||
"xml", null, TIMEOUT);
|
||||
RecordedRequest recordedRequest = server.takeRequest();
|
||||
Assertions.assertArrayEquals(payload, recordedRequest.getBody().readByteArray(),
|
||||
"PUT request payload does not match send data.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("POST request, test payload received by server matches sent.")
|
||||
void test_post() throws IOException, URISyntaxException, InterruptedException {
|
||||
byte[] payload = ByteUtils.resourceToByteArray(patient, true, false, false);
|
||||
// Mock server response of 200, with the same resource payload returned that we included in the PUT request
|
||||
server.enqueue(
|
||||
new MockResponse()
|
||||
.setResponseCode(200)
|
||||
.setBody(new String(payload))
|
||||
);
|
||||
|
||||
ResourceRequest<Resource> request = client.issuePostRequest(new URI(serverUrl.toString()), payload,
|
||||
"xml", null, TIMEOUT);
|
||||
RecordedRequest recordedRequest = server.takeRequest();
|
||||
Assertions.assertArrayEquals(payload, recordedRequest.getBody().readByteArray(),
|
||||
"POST request payload does not match send data.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Testing the logger works.")
|
||||
void test_logger() throws IOException, URISyntaxException, InterruptedException {
|
||||
byte[] payload = ByteUtils.resourceToByteArray(patient, true, false, false);
|
||||
server.enqueue(
|
||||
new MockResponse()
|
||||
.setResponseCode(200)
|
||||
.setBody(new String(payload))
|
||||
);
|
||||
ToolingClientLogger mockLogger = Mockito.mock(ToolingClientLogger.class);
|
||||
client.setLogger(mockLogger);
|
||||
client.issuePostRequest(new URI(serverUrl.toString()), payload,
|
||||
"xml", null, TIMEOUT);
|
||||
server.takeRequest();
|
||||
Mockito.verify(mockLogger, Mockito.times(1))
|
||||
.logRequest(Mockito.anyString(), Mockito.anyString(), Mockito.anyList(), Mockito.any());
|
||||
Mockito.verify(mockLogger, Mockito.times(1))
|
||||
.logResponse(Mockito.anyString(), Mockito.anyList(), Mockito.any(), Mockito.anyLong());
|
||||
}
|
||||
}
|
|
@ -1,107 +1,126 @@
|
|||
package org.hl7.fhir.dstu3.utils.client.network;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hl7.fhir.dstu3.formats.IParser;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.hl7.fhir.dstu3.model.OperationOutcome;
|
||||
import org.hl7.fhir.utilities.http.*;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.AdditionalMatchers;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Protocol;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class FhirRequestBuilderTests {
|
||||
|
||||
private static final String DUMMY_URL = "https://some-url.com/";
|
||||
@Test
|
||||
@DisplayName("Test resource format headers are added correctly (GET).")
|
||||
void addResourceFormatHeadersGET() {
|
||||
String testFormat = "yaml";
|
||||
HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com").withMethod(HTTPRequest.HttpMethod.GET);
|
||||
|
||||
Request mockRequest = new Request.Builder()
|
||||
.url(DUMMY_URL)
|
||||
.build();
|
||||
Iterable<HTTPHeader> headers = FhirRequestBuilder.getResourceFormatHeaders(request, testFormat);
|
||||
|
||||
final String RESPONSE_BODY_STRING = "{}";
|
||||
|
||||
Response response = new Response.Builder()
|
||||
.request(mockRequest)
|
||||
.protocol(Protocol.HTTP_2)
|
||||
.code(200) // status code
|
||||
.message("")
|
||||
.body(ResponseBody.create(RESPONSE_BODY_STRING,
|
||||
MediaType.get("application/json; charset=utf-8")
|
||||
))
|
||||
.addHeader("Content-Type", "")
|
||||
.build();
|
||||
|
||||
final Request.Builder requestBuilder = new Request.Builder()
|
||||
.url(DUMMY_URL);
|
||||
|
||||
final FhirRequestBuilder fhirRequestBuilder = Mockito.spy(new FhirRequestBuilder(requestBuilder, "http://local/local"));
|
||||
|
||||
@Mock
|
||||
OkHttpClient client;
|
||||
|
||||
@Mock
|
||||
Call mockCall;
|
||||
|
||||
@Mock
|
||||
ToolingClientLogger logger;
|
||||
|
||||
@BeforeEach
|
||||
public void beforeEach() {
|
||||
Mockito.doReturn(client).when(fhirRequestBuilder).getHttpClient();
|
||||
fhirRequestBuilder.withLogger(logger);
|
||||
}
|
||||
|
||||
@Nested
|
||||
class RequestLoggingTests {
|
||||
|
||||
@BeforeEach
|
||||
public void beforeEach() throws IOException {
|
||||
Mockito.doReturn(response).when(mockCall).execute();
|
||||
Mockito.doReturn(mockCall).when(client).newCall(ArgumentMatchers.any());
|
||||
|
||||
Mockito.doReturn(null).when(fhirRequestBuilder).unmarshalReference(ArgumentMatchers.any(), ArgumentMatchers.isNull());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteLogging() throws IOException {
|
||||
fhirRequestBuilder.execute();
|
||||
Mockito.verify(logger).logRequest(ArgumentMatchers.eq("GET"), ArgumentMatchers.eq(DUMMY_URL), ArgumentMatchers.anyList(), ArgumentMatchers.isNull());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteBatchLogging() throws IOException {
|
||||
fhirRequestBuilder.executeAsBatch();
|
||||
Mockito.verify(logger).logRequest(ArgumentMatchers.eq("GET"), ArgumentMatchers.eq(DUMMY_URL), ArgumentMatchers.anyList(), ArgumentMatchers.isNull());
|
||||
}
|
||||
Map<String, List<String>> headersMap = HTTPHeaderUtil.getMultimap(headers);
|
||||
Assertions.assertNotNull(headersMap.get("Accept"), "Accept header null.");
|
||||
Assertions.assertEquals(testFormat, headersMap.get("Accept").get(0),
|
||||
"Accept header not populated with expected value " + testFormat + ".");
|
||||
|
||||
Assertions.assertNull(headersMap.get("Content-Type"), "Content-Type header not null.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnmarshallReferenceLogging() {
|
||||
IParser parser = Mockito.mock(IParser.class);
|
||||
Mockito.doReturn(parser).when(fhirRequestBuilder).getParser(ArgumentMatchers.eq("json"));
|
||||
@DisplayName("Test resource format headers are added correctly (POST).")
|
||||
void addResourceFormatHeadersPOST() {
|
||||
String testFormat = "yaml";
|
||||
HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com").withMethod(HTTPRequest.HttpMethod.POST);
|
||||
|
||||
fhirRequestBuilder.unmarshalReference(response, "json");
|
||||
Mockito.verify(logger).logResponse(ArgumentMatchers.eq("200"), ArgumentMatchers.anyList(), AdditionalMatchers.aryEq(RESPONSE_BODY_STRING.getBytes()), ArgumentMatchers.anyLong());
|
||||
Iterable<HTTPHeader> headers = FhirRequestBuilder.getResourceFormatHeaders(request, testFormat);
|
||||
|
||||
Map<String, List<String>> headersMap = HTTPHeaderUtil.getMultimap(headers);
|
||||
Assertions.assertNotNull(headersMap.get("Accept"), "Accept header null.");
|
||||
Assertions.assertEquals(testFormat, headersMap.get("Accept").get(0),
|
||||
"Accept header not populated with expected value " + testFormat + ".");
|
||||
|
||||
Assertions.assertNotNull(headersMap.get("Content-Type"), "Content-Type header null.");
|
||||
Assertions.assertEquals(testFormat + ";charset=" + FhirRequestBuilder.DEFAULT_CHARSET, headersMap.get("Content-Type").get(0),
|
||||
"Content-Type header not populated with expected value \"" + testFormat + ";charset=" + FhirRequestBuilder.DEFAULT_CHARSET + "\".");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnmarshallFeedLogging() {
|
||||
fhirRequestBuilder.unmarshalFeed(response, "application/json");
|
||||
Mockito.verify(logger).logResponse(ArgumentMatchers.eq("200"), ArgumentMatchers.anyList(), AdditionalMatchers.aryEq(RESPONSE_BODY_STRING.getBytes()), ArgumentMatchers.anyLong());
|
||||
@DisplayName("Test that FATAL issue severity triggers error.")
|
||||
void hasErrorTestFatal() {
|
||||
OperationOutcome outcome = new OperationOutcome();
|
||||
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.INFORMATION));
|
||||
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.NULL));
|
||||
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.WARNING));
|
||||
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.FATAL));
|
||||
Assertions.assertTrue(FhirRequestBuilder.hasError(outcome), "Error check not triggered for FATAL issue severity.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test that ERROR issue severity triggers error.")
|
||||
void hasErrorTestError() {
|
||||
OperationOutcome outcome = new OperationOutcome();
|
||||
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.INFORMATION));
|
||||
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.NULL));
|
||||
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.WARNING));
|
||||
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.ERROR));
|
||||
Assertions.assertTrue(FhirRequestBuilder.hasError(outcome), "Error check not triggered for ERROR issue severity.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test that no FATAL or ERROR issue severity does not trigger error.")
|
||||
void hasErrorTestNoErrors() {
|
||||
OperationOutcome outcome = new OperationOutcome();
|
||||
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.INFORMATION));
|
||||
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.NULL));
|
||||
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.WARNING));
|
||||
Assertions.assertFalse(FhirRequestBuilder.hasError(outcome), "Error check triggered unexpectedly.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test that getLocationHeader returns header for 'location'.")
|
||||
void getLocationHeaderWhenOnlyLocationIsSet() {
|
||||
final String expectedLocationHeader = "location_header_value";
|
||||
HTTPResult result = new HTTPResult("source",
|
||||
200,
|
||||
"message",
|
||||
"contentType",
|
||||
new byte[0],
|
||||
List.of(new HTTPHeader(FhirRequestBuilder.LOCATION_HEADER, expectedLocationHeader)));
|
||||
|
||||
Assertions.assertEquals(expectedLocationHeader, FhirRequestBuilder.getLocationHeader(result.getHeaders()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test that getLocationHeader returns header for 'content-location'.")
|
||||
void getLocationHeaderWhenOnlyContentLocationIsSet() {
|
||||
final String expectedContentLocationHeader = "content_location_header_value";
|
||||
Iterable<HTTPHeader> headers = List.of(new HTTPHeader(FhirRequestBuilder.CONTENT_LOCATION_HEADER, expectedContentLocationHeader));
|
||||
|
||||
Assertions.assertEquals(expectedContentLocationHeader, FhirRequestBuilder.getLocationHeader(headers));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test that getLocationHeader returns 'location' header when both 'location' and 'content-location' are set.")
|
||||
void getLocationHeaderWhenLocationAndContentLocationAreSet() {
|
||||
final String expectedLocationHeader = "location_header_value";
|
||||
final String expectedContentLocationHeader = "content_location_header_value";
|
||||
|
||||
Iterable<HTTPHeader> headers = List.of(
|
||||
new HTTPHeader(FhirRequestBuilder.LOCATION_HEADER, expectedLocationHeader),
|
||||
new HTTPHeader(FhirRequestBuilder.CONTENT_LOCATION_HEADER, expectedContentLocationHeader)
|
||||
);
|
||||
|
||||
Assertions.assertEquals(expectedLocationHeader, FhirRequestBuilder.getLocationHeader(headers));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test that getLocationHeader returns null when no location available.")
|
||||
void getLocationHeaderWhenNoLocationSet() {
|
||||
Assertions.assertNull(FhirRequestBuilder.getLocationHeader(Collections.emptyList()));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -35,6 +35,12 @@
|
|||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- UCUM -->
|
||||
<dependency>
|
||||
<groupId>org.fhir</groupId>
|
||||
|
@ -78,7 +84,12 @@
|
|||
<artifactId>okio-jvm</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>mockwebserver</artifactId>
|
||||
<optional>true</optional>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hl7.fhir.testcases</groupId>
|
||||
<artifactId>fhir-test-cases</artifactId>
|
||||
|
|
|
@ -3,11 +3,10 @@ package org.hl7.fhir.r4.utils.client;
|
|||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.hl7.fhir.r4.model.CapabilityStatement;
|
||||
|
@ -29,8 +28,7 @@ import org.hl7.fhir.utilities.FHIRBaseToolingClient;
|
|||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.internal.http2.Header;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeader;
|
||||
|
||||
/**
|
||||
* Very Simple RESTful client. This is purely for use in the standalone tools
|
||||
|
@ -70,16 +68,22 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
|
||||
private String base;
|
||||
private ResourceAddress resourceAddress;
|
||||
@Setter
|
||||
private ResourceFormat preferredResourceFormat;
|
||||
private int maxResultSetSize = -1;// _count
|
||||
private CapabilityStatement capabilities;
|
||||
@Getter
|
||||
@Setter
|
||||
private Client client = new Client();
|
||||
private ArrayList<Header> headers = new ArrayList<>();
|
||||
private String username;
|
||||
private String password;
|
||||
private List<HTTPHeader> headers = new ArrayList<>();
|
||||
|
||||
@Getter @Setter
|
||||
private String userAgent;
|
||||
private String acceptLang;
|
||||
private String contentLang;
|
||||
@Setter
|
||||
private String acceptLanguage;
|
||||
|
||||
@Setter
|
||||
private String contentLanguage;
|
||||
private int useCount;
|
||||
|
||||
// Pass endpoint for client - URI
|
||||
|
@ -96,14 +100,6 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
this.maxResultSetSize = -1;
|
||||
}
|
||||
|
||||
public Client getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void setClient(Client client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
private void checkCapabilities() {
|
||||
try {
|
||||
capabilities = getCapabilitiesStatementQuick();
|
||||
|
@ -115,10 +111,6 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
return preferredResourceFormat.getHeader();
|
||||
}
|
||||
|
||||
public void setPreferredResourceFormat(ResourceFormat resourceFormat) {
|
||||
preferredResourceFormat = resourceFormat;
|
||||
}
|
||||
|
||||
public int getMaximumRecordCount() {
|
||||
return maxResultSetSize;
|
||||
}
|
||||
|
@ -131,7 +123,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
TerminologyCapabilities capabilities = null;
|
||||
try {
|
||||
capabilities = (TerminologyCapabilities) client.issueGetResourceRequest(resourceAddress.resolveMetadataTxCaps(),
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "TerminologyCapabilities", timeoutNormal).getReference();
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(false), "TerminologyCapabilities", timeoutNormal).getReference();
|
||||
} catch (Exception e) {
|
||||
throw new FHIRException("Error fetching the server's terminology capabilities", e);
|
||||
}
|
||||
|
@ -142,7 +134,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
CapabilityStatement conformance = null;
|
||||
try {
|
||||
conformance = (CapabilityStatement) client.issueGetResourceRequest(resourceAddress.resolveMetadataUri(false),
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "CapabilitiesStatement", timeoutNormal).getReference();
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(false), "CapabilitiesStatement", timeoutNormal).getReference();
|
||||
} catch (Exception e) {
|
||||
throw new FHIRException("Error fetching the server's conformance statement", e);
|
||||
}
|
||||
|
@ -154,7 +146,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
return capabilities;
|
||||
try {
|
||||
capabilities = (CapabilityStatement) client.issueGetResourceRequest(resourceAddress.resolveMetadataUri(true),
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "CapabilitiesStatement-Quick", timeoutNormal)
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(false), "CapabilitiesStatement-Quick", timeoutNormal)
|
||||
.getReference();
|
||||
} catch (Exception e) {
|
||||
throw new FHIRException("Error fetching the server's capability statement: " + e.getMessage(), e);
|
||||
|
@ -167,7 +159,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
ResourceRequest<Resource> result = null;
|
||||
try {
|
||||
result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "Read " + resourceClass + "/" + id,
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(false), "Read " + resourceClass + "/" + id,
|
||||
timeoutNormal);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
|
||||
|
@ -184,7 +176,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
ResourceRequest<T> result = null;
|
||||
try {
|
||||
result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "Read " + resourceClass.getName() + "/" + id,
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(false), "Read " + resourceClass.getName() + "/" + id,
|
||||
timeoutNormal);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
|
||||
|
@ -202,7 +194,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
try {
|
||||
result = client.issueGetResourceRequest(
|
||||
resourceAddress.resolveGetUriFromResourceClassAndIdAndVersion(resourceClass, id, version),
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(),
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(false),
|
||||
"VRead " + resourceClass.getName() + "/" + id + "/?_history/" + version, timeoutNormal);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
|
||||
|
@ -220,7 +212,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
try {
|
||||
result = client.issueGetResourceRequest(
|
||||
resourceAddress.resolveGetUriFromResourceClassAndCanonical(resourceClass, canonicalURL),
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "Read " + resourceClass.getName() + "?url=" + canonicalURL,
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(false), "Read " + resourceClass.getName() + "?url=" + canonicalURL,
|
||||
timeoutNormal);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
|
||||
|
@ -244,7 +236,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
result = client.issuePutRequest(
|
||||
resourceAddress.resolveGetUriFromResourceClassAndId(resource.getClass(), resource.getId()),
|
||||
ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat()), false),
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "Update " + resource.fhirType() + "/" + resource.getId(),
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(true), "Update " + resource.fhirType() + "/" + resource.getId(),
|
||||
timeoutOperation);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
|
||||
|
@ -274,7 +266,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
try {
|
||||
result = client.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),
|
||||
ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat()), false),
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "Update " + resource.fhirType() + "/" + id,
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(true), "Update " + resource.fhirType() + "/" + id,
|
||||
timeoutOperation);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
|
||||
|
@ -312,10 +304,10 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
URI url = resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps);
|
||||
if (complex) {
|
||||
byte[] body = ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()), true);
|
||||
result = client.issuePostRequest(url, body, withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(),
|
||||
result = client.issuePostRequest(url, body, withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(true),
|
||||
"POST " + resourceClass.getName() + "/$" + name, timeoutLong);
|
||||
} else {
|
||||
result = client.issueGetResourceRequest(url, withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(),
|
||||
result = client.issueGetResourceRequest(url, withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(false),
|
||||
"GET " + resourceClass.getName() + "/$" + name, timeoutLong);
|
||||
}
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
|
@ -351,7 +343,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
try {
|
||||
result = client.issuePostRequest(resourceAddress.resolveValidateUri(resourceClass, id),
|
||||
ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat()), false),
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(),
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(true),
|
||||
"POST " + resourceClass.getName() + (id != null ? "/" + id : "") + "/$validate", timeoutLong);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
|
||||
|
@ -364,10 +356,14 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
}
|
||||
|
||||
/**
|
||||
* Helper method to prevent nesting of previously thrown EFhirClientExceptions
|
||||
* Helper method to prevent nesting of previously thrown EFhirClientExceptions. If the e param is an instance of
|
||||
* EFhirClientException, it will be rethrown. Otherwise, a new EFhirClientException will be thrown with e as the
|
||||
* cause.
|
||||
*
|
||||
* @param e
|
||||
* @throws EFhirClientException
|
||||
* @param code The EFhirClientException code.
|
||||
* @param message The EFhirClientException message.
|
||||
* @param e The exception.
|
||||
* @throws EFhirClientException representing the exception.
|
||||
*/
|
||||
protected void handleException(int code, String message, Exception e) throws EFhirClientException {
|
||||
if (e instanceof EFhirClientException) {
|
||||
|
@ -378,11 +374,11 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
}
|
||||
|
||||
/**
|
||||
* Helper method to determine whether desired resource representation is Json or
|
||||
* XML.
|
||||
* Helper method to determine whether desired resource representation
|
||||
* is Json or XML.
|
||||
*
|
||||
* @param format
|
||||
* @return
|
||||
* @param format The format
|
||||
* @return true if the format is JSON, false otherwise
|
||||
*/
|
||||
protected boolean isJson(String format) {
|
||||
boolean isJson = false;
|
||||
|
@ -408,7 +404,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
org.hl7.fhir.r4.utils.client.network.ResourceRequest<Resource> result = null;
|
||||
try {
|
||||
result = client.issueGetResourceRequest(resourceAddress.resolveOperationUri(CodeSystem.class, "lookup", params),
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "CodeSystem/$lookup", timeoutNormal);
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(false), "CodeSystem/$lookup", timeoutNormal);
|
||||
} catch (IOException e) {
|
||||
throw new FHIRException(e);
|
||||
}
|
||||
|
@ -425,7 +421,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
try {
|
||||
result = client.issuePostRequest(resourceAddress.resolveOperationUri(CodeSystem.class, "lookup"),
|
||||
ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat()), true),
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "CodeSystem/$lookup", timeoutNormal);
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(true), "CodeSystem/$lookup", timeoutNormal);
|
||||
} catch (IOException e) {
|
||||
throw new FHIRException(e);
|
||||
}
|
||||
|
@ -442,7 +438,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
try {
|
||||
result = client.issuePostRequest(resourceAddress.resolveOperationUri(ConceptMap.class, "translate"),
|
||||
ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat()), true),
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "ConceptMap/$translate", timeoutNormal);
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(true), "ConceptMap/$translate", timeoutNormal);
|
||||
} catch (IOException e) {
|
||||
throw new FHIRException(e);
|
||||
}
|
||||
|
@ -463,7 +459,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
try {
|
||||
result = client.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand"),
|
||||
ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat()), true), withVer(getPreferredResourceFormat(), "4.0"),
|
||||
generateHeaders(), source == null ? "ValueSet/$expand" : "ValueSet/$expand?url=" + source.getUrl(),
|
||||
generateHeaders(true), source == null ? "ValueSet/$expand" : "ValueSet/$expand?url=" + source.getUrl(),
|
||||
timeoutExpand);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
|
||||
|
@ -494,7 +490,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
result = client.issuePostRequest(
|
||||
resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
|
||||
ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()), true),
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "Closure?name=" + name, timeoutNormal);
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(true), "Closure?name=" + name, timeoutNormal);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
|
||||
(OperationOutcome) result.getPayload());
|
||||
|
@ -515,7 +511,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
result = client.issuePostRequest(
|
||||
resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
|
||||
ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()), true),
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "UpdateClosure?name=" + name, timeoutOperation);
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(true), "UpdateClosure?name=" + name, timeoutOperation);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
|
||||
(OperationOutcome) result.getPayload());
|
||||
|
@ -526,22 +522,6 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
return result == null ? null : (ConceptMap) result.getPayload();
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public ToolingClientLogger getLogger() {
|
||||
return client.getLogger();
|
||||
}
|
||||
|
@ -558,50 +538,27 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
client.setRetryCount(retryCount);
|
||||
}
|
||||
|
||||
public void setClientHeaders(ArrayList<Header> headers) {
|
||||
this.headers = headers;
|
||||
public void setClientHeaders(Iterable<HTTPHeader> headers) {
|
||||
this.headers = new ArrayList<>();
|
||||
headers.forEach(this.headers::add);
|
||||
}
|
||||
|
||||
private Headers generateHeaders() {
|
||||
Headers.Builder builder = new Headers.Builder();
|
||||
// Add basic auth header if it exists
|
||||
if (basicAuthHeaderExists()) {
|
||||
builder.add(getAuthorizationHeader().toString());
|
||||
}
|
||||
private Iterable<HTTPHeader> generateHeaders(boolean hasBody) {
|
||||
// Add any other headers
|
||||
if (this.headers != null) {
|
||||
this.headers.forEach(header -> builder.add(header.toString()));
|
||||
}
|
||||
List<HTTPHeader> headers = new ArrayList<>(this.headers);
|
||||
if (!Utilities.noString(userAgent)) {
|
||||
builder.add("User-Agent: " + userAgent);
|
||||
headers.add(new HTTPHeader("User-Agent",userAgent));
|
||||
}
|
||||
|
||||
if (!Utilities.noString(acceptLang)) {
|
||||
builder.add("Accept-Language: "+acceptLang);
|
||||
if (!Utilities.noString(acceptLanguage)) {
|
||||
headers.add(new HTTPHeader("Accept-Language", acceptLanguage));
|
||||
}
|
||||
if (!Utilities.noString(contentLang)) {
|
||||
builder.add("Content-Language: "+contentLang);
|
||||
|
||||
if (hasBody && !Utilities.noString(contentLanguage)) {
|
||||
headers.add(new HTTPHeader("Content-Language",contentLanguage));
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public boolean basicAuthHeaderExists() {
|
||||
return (username != null) && (password != null);
|
||||
}
|
||||
|
||||
public Header getAuthorizationHeader() {
|
||||
String usernamePassword = username + ":" + password;
|
||||
String base64usernamePassword = Base64.getEncoder().encodeToString(usernamePassword.getBytes());
|
||||
return new Header("Authorization", "Basic " + base64usernamePassword);
|
||||
}
|
||||
|
||||
public String getUserAgent() {
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
public void setUserAgent(String userAgent) {
|
||||
this.userAgent = userAgent;
|
||||
return headers;
|
||||
}
|
||||
|
||||
public String getServerVersion() {
|
||||
|
@ -609,14 +566,6 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
return capabilities == null ? null : capabilities.getSoftware().getVersion();
|
||||
}
|
||||
|
||||
public void setAcceptLanguage(String lang) {
|
||||
this.acceptLang = lang;
|
||||
}
|
||||
|
||||
public void setContentLanguage(String lang) {
|
||||
this.acceptLang = lang;
|
||||
}
|
||||
|
||||
public Bundle search(String type, String criteria) {
|
||||
recordUse();
|
||||
return fetchFeed(Utilities.pathURL(base, type+criteria));
|
||||
|
@ -627,7 +576,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
org.hl7.fhir.r4.utils.client.network.ResourceRequest<Resource> result = null;
|
||||
try {
|
||||
result = client.issueGetResourceRequest(resourceAddress.resolveGetResource(resourceClass, id),
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), resourceClass.getName()+"/"+id, timeoutNormal);
|
||||
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(true), resourceClass.getName()+"/"+id, timeoutNormal);
|
||||
} catch (IOException e) {
|
||||
throw new FHIRException(e);
|
||||
}
|
||||
|
|
|
@ -2,148 +2,149 @@ package org.hl7.fhir.r4.utils.client.network;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.hl7.fhir.r4.model.Resource;
|
||||
import org.hl7.fhir.r4.utils.client.EFhirClientException;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeader;
|
||||
import org.hl7.fhir.utilities.http.HTTPRequest;
|
||||
|
||||
public class Client {
|
||||
|
||||
|
||||
|
||||
public static final String DEFAULT_CHARSET = "UTF-8";
|
||||
|
||||
@Getter @Setter
|
||||
private ToolingClientLogger logger;
|
||||
private FhirLoggingInterceptor fhirLoggingInterceptor;
|
||||
|
||||
@Getter @Setter
|
||||
private int retryCount;
|
||||
|
||||
@Getter @Setter
|
||||
private String base;
|
||||
|
||||
public String getBase() {
|
||||
return base;
|
||||
}
|
||||
|
||||
public void setBase(String base) {
|
||||
this.base = base;
|
||||
}
|
||||
|
||||
|
||||
public ToolingClientLogger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
public void setLogger(ToolingClientLogger logger) {
|
||||
this.logger = logger;
|
||||
this.fhirLoggingInterceptor = new FhirLoggingInterceptor(logger);
|
||||
}
|
||||
|
||||
public int getRetryCount() {
|
||||
return retryCount;
|
||||
}
|
||||
|
||||
public void setRetryCount(int retryCount) {
|
||||
this.retryCount = retryCount;
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issueOptionsRequest(URI optionsUri, String resourceFormat,
|
||||
String message, long timeout) throws IOException {
|
||||
Request.Builder request = new Request.Builder().method("OPTIONS", null).url(optionsUri.toURL());
|
||||
|
||||
return executeFhirRequest(request, resourceFormat, new Headers.Builder().build(), message, retryCount, timeout);
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(optionsUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.OPTIONS);
|
||||
return executeFhirRequest(request, resourceFormat, Collections.emptyList(), message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issueGetResourceRequest(URI resourceUri, String resourceFormat,
|
||||
Headers headers, String message, long timeout) throws IOException {
|
||||
Request.Builder request = new Request.Builder().url(resourceUri.toURL());
|
||||
|
||||
Iterable<HTTPHeader> headers, String message, long timeout) throws IOException {
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.GET);
|
||||
return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public int tester(int trytry) {
|
||||
return 5;
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri, byte[] payload, String resourceFormat,
|
||||
String message, long timeout) throws IOException {
|
||||
return issuePutRequest(resourceUri, payload, resourceFormat, new Headers.Builder().build(), message, timeout);
|
||||
return issuePutRequest(resourceUri, payload, resourceFormat, Collections.emptyList(), message, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri, byte[] payload, String resourceFormat,
|
||||
Headers headers, String message, long timeout) throws IOException {
|
||||
Iterable<HTTPHeader> headers, String message, long timeout) throws IOException {
|
||||
if (payload == null)
|
||||
throw new EFhirClientException("PUT requests require a non-null payload");
|
||||
RequestBody body = RequestBody.create(payload);
|
||||
Request.Builder request = new Request.Builder().url(resourceUri.toURL()).put(body);
|
||||
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.PUT)
|
||||
.withBody(payload)
|
||||
.withContentType(getContentTypeWithDefaultCharset(resourceFormat));
|
||||
|
||||
return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri, byte[] payload,
|
||||
String resourceFormat, String message, long timeout) throws IOException {
|
||||
return issuePostRequest(resourceUri, payload, resourceFormat, new Headers.Builder().build(), message, timeout);
|
||||
return issuePostRequest(resourceUri, payload, resourceFormat, Collections.emptyList(), message, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri, byte[] payload,
|
||||
String resourceFormat, Headers headers, String message, long timeout) throws IOException {
|
||||
String resourceFormat, Iterable<HTTPHeader> headers, String message, long timeout) throws IOException {
|
||||
if (payload == null)
|
||||
throw new EFhirClientException("POST requests require a non-null payload");
|
||||
RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
|
||||
Request.Builder request = new Request.Builder().url(resourceUri.toURL()).post(body);
|
||||
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.POST)
|
||||
.withBody(payload)
|
||||
.withContentType(getContentTypeWithDefaultCharset(resourceFormat));
|
||||
|
||||
return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public boolean issueDeleteRequest(URI resourceUri, int timeout) throws IOException {
|
||||
Request.Builder request = new Request.Builder().url(resourceUri.toURL()).delete();
|
||||
return executeFhirRequest(request, null, new Headers.Builder().build(), null, retryCount, timeout)
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.DELETE);
|
||||
return executeFhirRequest(request, null, Collections.emptyList(), null, retryCount, timeout)
|
||||
.isSuccessfulRequest();
|
||||
}
|
||||
|
||||
public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat, int timeout) throws IOException {
|
||||
Request.Builder request = new Request.Builder().url(resourceUri.toURL());
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.GET);
|
||||
|
||||
return executeBundleRequest(request, resourceFormat, new Headers.Builder().build(), null, retryCount, timeout);
|
||||
return executeBundleRequest(request, resourceFormat, Collections.emptyList(), null, retryCount, timeout);
|
||||
}
|
||||
|
||||
public Bundle issuePostFeedRequest(URI resourceUri, Map<String, String> parameters, String resourceName,
|
||||
Resource resource, String resourceFormat, int timeout) throws IOException {
|
||||
String boundary = "----WebKitFormBoundarykbMUo6H8QaUnYtRy";
|
||||
byte[] payload = ByteUtils.encodeFormSubmission(parameters, resourceName, resource, boundary);
|
||||
RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
|
||||
Request.Builder request = new Request.Builder().url(resourceUri.toURL()).post(body);
|
||||
|
||||
return executeBundleRequest(request, resourceFormat, new Headers.Builder().build(), null, retryCount, timeout);
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.POST)
|
||||
.withBody(payload)
|
||||
.withContentType(getContentTypeWithDefaultCharset(resourceFormat));
|
||||
|
||||
return executeBundleRequest(request, resourceFormat, Collections.emptyList(), null, retryCount, timeout);
|
||||
}
|
||||
|
||||
public Bundle postBatchRequest(URI resourceUri, byte[] payload, String resourceFormat, String message, int timeout)
|
||||
throws IOException {
|
||||
if (payload == null)
|
||||
throw new EFhirClientException("POST requests require a non-null payload");
|
||||
RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
|
||||
Request.Builder request = new Request.Builder().url(resourceUri.toURL()).post(body);
|
||||
|
||||
return executeBundleRequest(request, resourceFormat, new Headers.Builder().build(), message, retryCount, timeout);
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.POST)
|
||||
.withBody(payload)
|
||||
.withContentType(getContentTypeWithDefaultCharset(resourceFormat));
|
||||
|
||||
return executeBundleRequest(request, resourceFormat, Collections.emptyList(), message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> Bundle executeBundleRequest(Request.Builder request, String resourceFormat,
|
||||
Headers headers, String message, int retryCount, long timeout) throws IOException {
|
||||
return new FhirRequestBuilder(request, base).withLogger(fhirLoggingInterceptor).withResourceFormat(resourceFormat)
|
||||
private static String getContentTypeWithDefaultCharset(String resourceFormat) {
|
||||
return resourceFormat + ";charset=" + DEFAULT_CHARSET;
|
||||
}
|
||||
|
||||
public <T extends Resource> Bundle executeBundleRequest(HTTPRequest request, String resourceFormat,
|
||||
Iterable<HTTPHeader> headers, String message, int retryCount, long timeout) throws IOException {
|
||||
return new FhirRequestBuilder(request, base).withLogger(logger).withResourceFormat(resourceFormat)
|
||||
.withRetryCount(retryCount).withMessage(message)
|
||||
.withHeaders(headers == null ? new Headers.Builder().build() : headers)
|
||||
.withHeaders(headers == null ? Collections.emptyList() : headers)
|
||||
.withTimeout(timeout, TimeUnit.MILLISECONDS).executeAsBatch();
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> executeFhirRequest(Request.Builder request, String resourceFormat,
|
||||
Headers headers, String message, int retryCount, long timeout) throws IOException {
|
||||
return new FhirRequestBuilder(request, base).withLogger(fhirLoggingInterceptor).withResourceFormat(resourceFormat)
|
||||
public <T extends Resource> ResourceRequest<T> executeFhirRequest(HTTPRequest request, String resourceFormat,
|
||||
Iterable<HTTPHeader> headers, String message, int retryCount, long timeout) throws IOException {
|
||||
return new FhirRequestBuilder(request, base).withLogger(logger).withResourceFormat(resourceFormat)
|
||||
.withRetryCount(retryCount).withMessage(message)
|
||||
.withHeaders(headers == null ? new Headers.Builder().build() : headers)
|
||||
.withHeaders(headers == null ? Collections.emptyList() : headers)
|
||||
.withTimeout(timeout, TimeUnit.MILLISECONDS).execute();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import java.util.stream.Collectors;
|
|||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
|
||||
import okhttp3.internal.http2.Header;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeader;
|
||||
|
||||
/**
|
||||
* Generic Implementation of Client Headers.
|
||||
|
@ -16,29 +16,29 @@ import okhttp3.internal.http2.Header;
|
|||
*/
|
||||
public class ClientHeaders {
|
||||
|
||||
private final ArrayList<Header> headers;
|
||||
private final ArrayList<HTTPHeader> headers;
|
||||
|
||||
public ClientHeaders() {
|
||||
this.headers = new ArrayList<>();
|
||||
}
|
||||
|
||||
public ClientHeaders(ArrayList<Header> headers) {
|
||||
public ClientHeaders(ArrayList<HTTPHeader> headers) {
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public ArrayList<Header> headers() {
|
||||
public ArrayList<HTTPHeader> headers() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a header to the list of stored headers for network operations.
|
||||
*
|
||||
* @param header {@link Header} to add to the list.
|
||||
* @param header {@link HTTPHeader} to add to the list.
|
||||
* @throws FHIRException if the header being added is a duplicate.
|
||||
*/
|
||||
public ClientHeaders addHeader(Header header) throws FHIRException {
|
||||
public ClientHeaders addHeader(HTTPHeader header) throws FHIRException {
|
||||
if (headers.contains(header)) {
|
||||
throw new FHIRException("Attempting to add duplicate header, <" + header.name + ", " + header.value + ">.");
|
||||
throw new FHIRException("Attempting to add duplicate header, <" + header.getName() + ", " + header.getValue() + ">.");
|
||||
}
|
||||
headers.add(header);
|
||||
return this;
|
||||
|
@ -47,10 +47,10 @@ public class ClientHeaders {
|
|||
/**
|
||||
* Add a header to the list of stored headers for network operations.
|
||||
*
|
||||
* @param headerList {@link List} of {@link Header} to add.
|
||||
* @param headerList {@link List} of {@link HTTPHeader} to add.
|
||||
* @throws FHIRException if any of the headers being added is a duplicate.
|
||||
*/
|
||||
public ClientHeaders addHeaders(List<Header> headerList) throws FHIRException {
|
||||
public ClientHeaders addHeaders(List<HTTPHeader> headerList) throws FHIRException {
|
||||
headerList.forEach(this::addHeader);
|
||||
return this;
|
||||
}
|
||||
|
@ -58,13 +58,13 @@ public class ClientHeaders {
|
|||
/**
|
||||
* Removes the passed in header from the list of stored headers.
|
||||
*
|
||||
* @param header {@link Header} to remove from the list.
|
||||
* @param header {@link HTTPHeader} to remove from the list.
|
||||
* @throws FHIRException if the header passed in does not exist within the
|
||||
* stored list.
|
||||
*/
|
||||
public ClientHeaders removeHeader(Header header) throws FHIRException {
|
||||
public ClientHeaders removeHeader(HTTPHeader header) throws FHIRException {
|
||||
if (!headers.remove(header)) {
|
||||
throw new FHIRException("Attempting to remove header, <" + header.name + ", " + header.value
|
||||
throw new FHIRException("Attempting to remove header, <" + header.getName() + ", " + header.getValue()
|
||||
+ ">, from GenericClientHeaders that is not currently stored.");
|
||||
}
|
||||
return this;
|
||||
|
@ -73,17 +73,17 @@ public class ClientHeaders {
|
|||
/**
|
||||
* Removes the passed in headers from the list of stored headers.
|
||||
*
|
||||
* @param headerList {@link List} of {@link Header} to remove.
|
||||
* @param headerList {@link List} of {@link HTTPHeader} to remove.
|
||||
* @throws FHIRException if any of the headers passed in does not exist within
|
||||
* the stored list.
|
||||
*/
|
||||
public ClientHeaders removeHeaders(List<Header> headerList) throws FHIRException {
|
||||
public ClientHeaders removeHeaders(List<HTTPHeader> headerList) throws FHIRException {
|
||||
headerList.forEach(this::removeHeader);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all stored {@link Header}.
|
||||
* Clears all stored {@link HTTPHeader}.
|
||||
*/
|
||||
public ClientHeaders clearHeaders() {
|
||||
headers.clear();
|
||||
|
@ -92,7 +92,7 @@ public class ClientHeaders {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.headers.stream().map(header -> "\t" + header.name + ":" + header.value)
|
||||
return this.headers.stream().map(header -> "\t" + header.getName() + ":" + header.getValue())
|
||||
.collect(Collectors.joining(",\n", "{\n", "\n}"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
package org.hl7.fhir.r4.utils.client.network;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
import okio.Buffer;
|
||||
|
||||
public class FhirLoggingInterceptor implements Interceptor {
|
||||
|
||||
private ToolingClientLogger logger;
|
||||
|
||||
public FhirLoggingInterceptor(ToolingClientLogger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public FhirLoggingInterceptor setLogger(ToolingClientLogger logger) {
|
||||
this.logger = logger;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response intercept(@Nonnull Interceptor.Chain chain) throws IOException {
|
||||
// Log Request
|
||||
Request request = chain.request();
|
||||
List<String> hdrs = new ArrayList<>();
|
||||
for (String s : request.headers().toString().split("\\n")) {
|
||||
hdrs.add(s.trim());
|
||||
}
|
||||
byte[] cnt = null;
|
||||
if (request.body() != null) {
|
||||
Buffer buf = new Buffer();
|
||||
request.body().writeTo(buf);
|
||||
cnt = buf.readByteArray();
|
||||
}
|
||||
if (logger != null) {
|
||||
logger.logRequest(request.method(), request.url().toString(), hdrs, cnt);
|
||||
}
|
||||
|
||||
// Log Response
|
||||
Response response = null;
|
||||
response = chain.proceed(chain.request());
|
||||
|
||||
MediaType contentType = null;
|
||||
byte[] bodyBytes = null;
|
||||
if (response.body() != null) {
|
||||
contentType = response.body().contentType();
|
||||
bodyBytes = response.body().bytes();
|
||||
}
|
||||
|
||||
// Get Headers as List
|
||||
List<String> headerList = new ArrayList<>();
|
||||
Map<String, List<String>> headerMap = response.headers().toMultimap();
|
||||
headerMap.keySet().forEach(key -> headerMap.get(key).forEach(value -> headerList.add(key + ":" + value)));
|
||||
|
||||
if (logger != null) {
|
||||
logger.logResponse(Integer.toString(response.code()), headerList, bodyBytes, 0);
|
||||
}
|
||||
|
||||
// Reading byte[] clears body. Need to recreate.
|
||||
ResponseBody body = ResponseBody.create(bodyBytes, contentType);
|
||||
return response.newBuilder().body(body).build();
|
||||
}
|
||||
}
|
|
@ -1,16 +1,13 @@
|
|||
package org.hl7.fhir.r4.utils.client.network;
|
||||
|
||||
import static org.hl7.fhir.r4.utils.OperationOutcomeUtilities.outcomeFromTextError;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r4.formats.IParser;
|
||||
import org.hl7.fhir.r4.formats.JsonParser;
|
||||
import org.hl7.fhir.r4.formats.XmlParser;
|
||||
|
@ -22,31 +19,19 @@ import org.hl7.fhir.r4.utils.ResourceUtilities;
|
|||
import org.hl7.fhir.r4.utils.client.EFhirClientException;
|
||||
import org.hl7.fhir.r4.utils.client.ResourceFormat;
|
||||
import org.hl7.fhir.utilities.MimeType;
|
||||
import org.hl7.fhir.utilities.settings.FhirSettings;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.hl7.fhir.utilities.http.*;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlUtils;
|
||||
|
||||
import okhttp3.Authenticator;
|
||||
import okhttp3.Credentials;
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class FhirRequestBuilder {
|
||||
|
||||
protected static final String HTTP_PROXY_USER = "http.proxyUser";
|
||||
protected static final String HTTP_PROXY_PASS = "http.proxyPassword";
|
||||
protected static final String HEADER_PROXY_AUTH = "Proxy-Authorization";
|
||||
protected static final String LOCATION_HEADER = "location";
|
||||
protected static final String CONTENT_LOCATION_HEADER = "content-location";
|
||||
protected static final String DEFAULT_CHARSET = "UTF-8";
|
||||
/**
|
||||
* The singleton instance of the HttpClient, used for all requests.
|
||||
*/
|
||||
private static OkHttpClient okHttpClient;
|
||||
private final Request.Builder httpRequest;
|
||||
|
||||
private final HTTPRequest httpRequest;
|
||||
private String resourceFormat = null;
|
||||
private Headers headers = null;
|
||||
private Iterable<HTTPHeader> headers = null;
|
||||
private String message = null;
|
||||
private int retryCount = 1;
|
||||
/**
|
||||
|
@ -60,63 +45,46 @@ public class FhirRequestBuilder {
|
|||
private TimeUnit timeoutUnit = TimeUnit.MILLISECONDS;
|
||||
|
||||
/**
|
||||
* {@link FhirLoggingInterceptor} for log output.
|
||||
* {@link ToolingClientLogger} for log output.
|
||||
*/
|
||||
private FhirLoggingInterceptor logger = null;
|
||||
@Getter
|
||||
@Setter
|
||||
private ToolingClientLogger logger = null;
|
||||
|
||||
private String source;
|
||||
|
||||
public FhirRequestBuilder(Request.Builder httpRequest, String source) {
|
||||
this.httpRequest = httpRequest;
|
||||
public FhirRequestBuilder(HTTPRequest httpRequest, String source) {
|
||||
this.source = source;
|
||||
this.httpRequest = httpRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds necessary default headers, formatting headers, and any passed in
|
||||
* {@link Headers} to the passed in {@link okhttp3.Request.Builder}
|
||||
* {@link HTTPHeader}s to the passed in {@link HTTPRequest}
|
||||
*
|
||||
* @param request {@link okhttp3.Request.Builder} to add headers to.
|
||||
* @param request {@link HTTPRequest} to add headers to.
|
||||
* @param format Expected {@link Resource} format.
|
||||
* @param headers Any additional {@link Headers} to add to the request.
|
||||
* @param headers Any additional {@link HTTPHeader}s to add to the request.
|
||||
*/
|
||||
protected static void formatHeaders(Request.Builder request, String format, Headers headers) {
|
||||
addDefaultHeaders(request, headers);
|
||||
if (format != null)
|
||||
addResourceFormatHeaders(request, format);
|
||||
if (headers != null)
|
||||
addHeaders(request, headers);
|
||||
protected static HTTPRequest formatHeaders(HTTPRequest request, String format, Iterable<HTTPHeader> headers) {
|
||||
List<HTTPHeader> allHeaders = new ArrayList<>();
|
||||
request.getHeaders().forEach(allHeaders::add);
|
||||
|
||||
if (format != null) getResourceFormatHeaders(request, format).forEach(allHeaders::add);
|
||||
if (headers != null) headers.forEach(allHeaders::add);
|
||||
return request.withHeaders(allHeaders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds necessary headers for all REST requests.
|
||||
* <li>User-Agent : hapi-fhir-tooling-client</li>
|
||||
*
|
||||
* @param request {@link Request.Builder} to add default headers to.
|
||||
*/
|
||||
protected static void addDefaultHeaders(Request.Builder request, Headers headers) {
|
||||
if (headers == null || !headers.names().contains("User-Agent")) {
|
||||
request.addHeader("User-Agent", "hapi-fhir-tooling-client");
|
||||
protected static Iterable<HTTPHeader> getResourceFormatHeaders(HTTPRequest httpRequest, String format) {
|
||||
List<HTTPHeader> headers = new ArrayList<>();
|
||||
headers.add(new HTTPHeader("Accept", format));
|
||||
if (httpRequest.getMethod() == HTTPRequest.HttpMethod.PUT
|
||||
|| httpRequest.getMethod() == HTTPRequest.HttpMethod.POST
|
||||
|| httpRequest.getMethod() == HTTPRequest.HttpMethod.PATCH
|
||||
) {
|
||||
headers.add(new HTTPHeader("Content-Type", format + ";charset=" + DEFAULT_CHARSET));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds necessary headers for the given resource format provided.
|
||||
*
|
||||
* @param request {@link Request.Builder} to add default headers to.
|
||||
*/
|
||||
protected static void addResourceFormatHeaders(Request.Builder request, String format) {
|
||||
request.addHeader("Accept", format);
|
||||
request.addHeader("Content-Type", format + ";charset=" + DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through the passed in {@link Headers} and adds them to the provided
|
||||
* {@link Request.Builder}.
|
||||
*
|
||||
* @param request {@link Request.Builder} to add headers to.
|
||||
* @param headers {@link Headers} to add to request.
|
||||
*/
|
||||
protected static void addHeaders(Request.Builder request, Headers headers) {
|
||||
headers.forEach(header -> request.addHeader(header.getFirst(), header.getSecond()));
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -136,73 +104,24 @@ public class FhirRequestBuilder {
|
|||
}
|
||||
|
||||
/**
|
||||
* Extracts the 'location' header from the passes in {@link Headers}. If no
|
||||
* Extracts the 'location' header from the passed {@link Iterable<HTTPHeader>}. If no
|
||||
* value for 'location' exists, the value for 'content-location' is returned. If
|
||||
* neither header exists, we return null.
|
||||
*
|
||||
* @param headers {@link Headers} to evaluate
|
||||
* @param headers {@link HTTPHeader} to evaluate
|
||||
* @return {@link String} header value, or null if no location headers are set.
|
||||
*/
|
||||
protected static String getLocationHeader(Headers headers) {
|
||||
Map<String, List<String>> headerMap = headers.toMultimap();
|
||||
if (headerMap.containsKey(LOCATION_HEADER)) {
|
||||
return headerMap.get(LOCATION_HEADER).get(0);
|
||||
} else if (headerMap.containsKey(CONTENT_LOCATION_HEADER)) {
|
||||
return headerMap.get(CONTENT_LOCATION_HEADER).get(0);
|
||||
} else {
|
||||
return null;
|
||||
protected static String getLocationHeader(Iterable<HTTPHeader> headers) {
|
||||
String locationHeader = HTTPHeaderUtil.getSingleHeader(headers, LOCATION_HEADER);
|
||||
|
||||
if (locationHeader != null) {
|
||||
return locationHeader;
|
||||
}
|
||||
return HTTPHeaderUtil.getSingleHeader(headers, CONTENT_LOCATION_HEADER);
|
||||
}
|
||||
|
||||
/**
|
||||
* We only ever want to have one copy of the HttpClient kicking around at any
|
||||
* given time. If we need to make changes to any configuration, such as proxy
|
||||
* settings, timeout, caches, etc, we can do a per-call configuration through
|
||||
* the {@link OkHttpClient#newBuilder()} method. That will return a builder that
|
||||
* shares the same connection pool, dispatcher, and configuration with the
|
||||
* original client.
|
||||
* </p>
|
||||
* The {@link OkHttpClient} uses the proxy auth properties set in the current
|
||||
* system properties. The reason we don't set the proxy address and
|
||||
* authentication explicitly, is due to the fact that this class is often used
|
||||
* in conjunction with other http client tools which rely on the
|
||||
* system.properties settings to determine proxy settings. It's easier to keep
|
||||
* the method consistent across the board. ...for now.
|
||||
*
|
||||
* @return {@link OkHttpClient} instance
|
||||
*/
|
||||
protected OkHttpClient getHttpClient() {
|
||||
if (FhirSettings.isProhibitNetworkAccess()) {
|
||||
throw new FHIRException("Network Access is prohibited in this context");
|
||||
}
|
||||
|
||||
if (okHttpClient == null) {
|
||||
okHttpClient = new OkHttpClient();
|
||||
}
|
||||
|
||||
Authenticator proxyAuthenticator = getAuthenticator();
|
||||
|
||||
OkHttpClient.Builder builder = okHttpClient.newBuilder();
|
||||
if (logger != null)
|
||||
builder.addInterceptor(logger);
|
||||
builder.addInterceptor(new RetryInterceptor(retryCount));
|
||||
|
||||
return builder.connectTimeout(timeout, timeoutUnit).addInterceptor(new RetryInterceptor(retryCount))
|
||||
.connectTimeout(timeout, timeoutUnit).writeTimeout(timeout, timeoutUnit).readTimeout(timeout, timeoutUnit)
|
||||
.proxyAuthenticator(proxyAuthenticator).build();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private static Authenticator getAuthenticator() {
|
||||
return (route, response) -> {
|
||||
final String httpProxyUser = System.getProperty(HTTP_PROXY_USER);
|
||||
final String httpProxyPass = System.getProperty(HTTP_PROXY_PASS);
|
||||
if (httpProxyUser != null && httpProxyPass != null) {
|
||||
String credential = Credentials.basic(httpProxyUser, httpProxyPass);
|
||||
return response.request().newBuilder().header(HEADER_PROXY_AUTH, credential).build();
|
||||
}
|
||||
return response.request().newBuilder().build();
|
||||
};
|
||||
protected ManagedFhirWebAccessor getManagedWebAccessor() {
|
||||
return ManagedWebAccess.fhirAccessor().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger);
|
||||
}
|
||||
|
||||
public FhirRequestBuilder withResourceFormat(String resourceFormat) {
|
||||
|
@ -210,7 +129,7 @@ public class FhirRequestBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public FhirRequestBuilder withHeaders(Headers headers) {
|
||||
public FhirRequestBuilder withHeaders(Iterable<HTTPHeader> headers) {
|
||||
this.headers = headers;
|
||||
return this;
|
||||
}
|
||||
|
@ -225,7 +144,7 @@ public class FhirRequestBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public FhirRequestBuilder withLogger(FhirLoggingInterceptor logger) {
|
||||
public FhirRequestBuilder withLogger(ToolingClientLogger logger) {
|
||||
this.logger = logger;
|
||||
return this;
|
||||
}
|
||||
|
@ -236,33 +155,31 @@ public class FhirRequestBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
protected Request buildRequest() {
|
||||
return httpRequest.build();
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> execute() throws IOException {
|
||||
formatHeaders(httpRequest, resourceFormat, headers);
|
||||
Response response = getHttpClient().newCall(httpRequest.build()).execute();
|
||||
HTTPRequest requestWithHeaders = formatHeaders(httpRequest, resourceFormat, headers);
|
||||
HTTPResult response = getManagedWebAccessor().httpCall(requestWithHeaders);
|
||||
|
||||
T resource = unmarshalReference(response, resourceFormat, null);
|
||||
return new ResourceRequest<T>(resource, response.code(), getLocationHeader(response.headers()));
|
||||
return new ResourceRequest<T>(resource, response.getCode(), getLocationHeader(response.getHeaders()));
|
||||
}
|
||||
|
||||
public Bundle executeAsBatch() throws IOException {
|
||||
formatHeaders(httpRequest, resourceFormat, null);
|
||||
Response response = getHttpClient().newCall(httpRequest.build()).execute();
|
||||
return unmarshalFeed(response, resourceFormat);
|
||||
HTTPRequest requestWithHeaders = formatHeaders(httpRequest, resourceFormat, null);
|
||||
HTTPResult response = getManagedWebAccessor().httpCall(requestWithHeaders);
|
||||
return unmarshalFeed(response, resourceFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmarshalls a resource from the response stream.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T extends Resource> T unmarshalReference(Response response, String format, String resourceType) {
|
||||
int code = response.code();
|
||||
protected <T extends Resource> T unmarshalReference(HTTPResult response, String format, String resourceType) {
|
||||
int code = response.getCode();
|
||||
boolean ok = code >= 200 && code < 300;
|
||||
if (response.body() == null) {
|
||||
if (response.getContent() == null) {
|
||||
if (!ok) {
|
||||
throw new EFhirClientException(response.message());
|
||||
throw new EFhirClientException(response.getMessage());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@ -271,9 +188,9 @@ public class FhirRequestBuilder {
|
|||
|
||||
Resource resource = null;
|
||||
try {
|
||||
body = response.body().string();
|
||||
String ct = response.header("Content-Type");
|
||||
if (ct == null) {
|
||||
body = response.getContentAsString();
|
||||
String contentType = HTTPHeaderUtil.getSingleHeader(response.getHeaders(), "Content-Type");
|
||||
if (contentType == null) {
|
||||
if (ok) {
|
||||
resource = getParser(format).parse(body);
|
||||
} else {
|
||||
|
@ -282,10 +199,10 @@ public class FhirRequestBuilder {
|
|||
resource = OperationOutcomeUtilities.outcomeFromTextError(body);
|
||||
}
|
||||
} else {
|
||||
if (ct.contains(";")) {
|
||||
ct = ct.substring(0, ct.indexOf(";"));
|
||||
if (contentType.contains(";")) {
|
||||
contentType = contentType.substring(0, contentType.indexOf(";"));
|
||||
}
|
||||
switch (ct) {
|
||||
switch (contentType) {
|
||||
case "application/json":
|
||||
case "application/fhir+json":
|
||||
if (!format.contains("json")) {
|
||||
|
@ -299,16 +216,16 @@ public class FhirRequestBuilder {
|
|||
if (!format.contains("xml")) {
|
||||
System.out.println("Got xml response expecting "+format+" from "+source+" with status "+code);
|
||||
}
|
||||
resource = getParser(ResourceFormat.RESOURCE_XML.getHeader()).parse(response.body().bytes());
|
||||
resource = getParser(ResourceFormat.RESOURCE_XML.getHeader()).parse(response.getContent());
|
||||
break;
|
||||
case "text/plain":
|
||||
resource = OperationOutcomeUtilities.outcomeFromTextError(body);
|
||||
break;
|
||||
case "text/html" :
|
||||
resource = OperationOutcomeUtilities.outcomeFromTextError(XhtmlUtils.convertHtmlToText(response.body().string(), source));
|
||||
resource = OperationOutcomeUtilities.outcomeFromTextError(XhtmlUtils.convertHtmlToText(response.getContentAsString(), source));
|
||||
break;
|
||||
default: // not sure what else to do?
|
||||
System.out.println("Got content-type '"+ct+"' from "+source);
|
||||
System.out.println("Got content-type '"+contentType+"' from "+source);
|
||||
System.out.println(body);
|
||||
resource = OperationOutcomeUtilities.outcomeFromTextError(body);
|
||||
}
|
||||
|
@ -343,14 +260,14 @@ public class FhirRequestBuilder {
|
|||
/**
|
||||
* Unmarshalls Bundle from response stream.
|
||||
*/
|
||||
protected Bundle unmarshalFeed(Response response, String format) {
|
||||
protected Bundle unmarshalFeed(HTTPResult response, String format) {
|
||||
return unmarshalReference(response, format, "Bundle");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the appropriate parser based on the format type passed in. Defaults
|
||||
* to XML parser if a blank format is provided...because reasons.
|
||||
* <p>
|
||||
* <p/>
|
||||
* Currently supports only "json" and "xml" formats.
|
||||
*
|
||||
* @param format One of "json" or "xml".
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
package org.hl7.fhir.r4.utils.client.network;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
/**
|
||||
* An {@link Interceptor} for {@link okhttp3.OkHttpClient} that controls the
|
||||
* number of times we retry a to execute a given request, before reporting a
|
||||
* failure. This includes unsuccessful return codes and timeouts.
|
||||
*/
|
||||
public class RetryInterceptor implements Interceptor {
|
||||
|
||||
// Delay between retying failed requests, in millis
|
||||
private final long RETRY_TIME = 2000;
|
||||
|
||||
// Maximum number of times to retry the request before failing
|
||||
private final int maxRetry;
|
||||
|
||||
// Internal counter for tracking the number of times we've tried this request
|
||||
private int retryCounter = 0;
|
||||
|
||||
public RetryInterceptor(int maxRetry) {
|
||||
this.maxRetry = maxRetry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response intercept(Interceptor.Chain chain) throws IOException {
|
||||
Request request = chain.request();
|
||||
Response response = null;
|
||||
|
||||
do {
|
||||
try {
|
||||
// If we are retrying a failed request that failed due to a bad response from
|
||||
// the server, we must close it first
|
||||
if (response != null) {
|
||||
// System.out.println("Previous " + chain.request().method() + " attempt returned HTTP<" + (response.code())
|
||||
// + "> from url -> " + chain.request().url() + ".");
|
||||
response.close();
|
||||
}
|
||||
// System.out.println(chain.request().method() + " attempt <" + (retryCounter +
|
||||
// 1) + "> to url -> " + chain.request().url());
|
||||
response = chain.proceed(request);
|
||||
} catch (IOException e) {
|
||||
try {
|
||||
// Include a small break in between requests.
|
||||
Thread.sleep(RETRY_TIME);
|
||||
} catch (InterruptedException e1) {
|
||||
System.out.println(chain.request().method() + " to url -> " + chain.request().url() + " interrupted on try <"
|
||||
+ retryCounter + ">");
|
||||
}
|
||||
} finally {
|
||||
retryCounter++;
|
||||
}
|
||||
} while ((response == null || !response.isSuccessful()) && (retryCounter <= maxRetry + 1));
|
||||
|
||||
/*
|
||||
* if something has gone wrong, and we are unable to complete the request, we
|
||||
* still need to initialize the return response so we don't get a null pointer
|
||||
* exception.
|
||||
*/
|
||||
return response != null ? response : chain.proceed(request);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,224 @@
|
|||
package org.hl7.fhir.r4.utils.client;
|
||||
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
import org.hl7.fhir.r4.utils.client.network.Client;
|
||||
import org.hl7.fhir.r4.utils.client.network.ResourceRequest;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeader;
|
||||
import org.hl7.fhir.utilities.http.HTTPRequest;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class FhirToolingClientTest {
|
||||
String TX_ADDR = "http://tx.fhir.org";
|
||||
|
||||
HTTPHeader h1 = new HTTPHeader("header1", "value1");
|
||||
HTTPHeader h2 = new HTTPHeader("header2", "value2");
|
||||
HTTPHeader h3 = new HTTPHeader("header3", "value3");
|
||||
|
||||
HTTPHeader agentHeader = new HTTPHeader("User-Agent", "fhir/test-cases");
|
||||
|
||||
private Client mockClient;
|
||||
private FHIRToolingClient toolingClient;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<Iterable<HTTPHeader>> headersArgumentCaptor;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException, URISyntaxException {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
mockClient = Mockito.mock(Client.class);
|
||||
ResourceRequest<Resource> resourceResourceRequest = new ResourceRequest<>(generateBundle(), 200, "");
|
||||
|
||||
// GET
|
||||
Mockito.when(mockClient.issueGetResourceRequest(Mockito.any(URI.class), Mockito.anyString(),
|
||||
ArgumentMatchers.any(), Mockito.anyString(), Mockito.anyLong())).thenReturn(resourceResourceRequest);
|
||||
Mockito
|
||||
.when(mockClient.issueGetResourceRequest(Mockito.any(URI.class), Mockito.anyString(),
|
||||
ArgumentMatchers.any(), Mockito.eq("TerminologyCapabilities"), Mockito.anyLong()))
|
||||
.thenReturn(new ResourceRequest<>(new TerminologyCapabilities(), 200, "location"));
|
||||
Mockito
|
||||
.when(mockClient.issueGetResourceRequest(Mockito.any(URI.class), Mockito.anyString(),
|
||||
ArgumentMatchers.any(), Mockito.eq("CapabilitiesStatement"), Mockito.anyLong()))
|
||||
.thenReturn(new ResourceRequest<>(new CapabilityStatement(), 200, "location"));
|
||||
Mockito
|
||||
.when(mockClient.issueGetResourceRequest(Mockito.any(URI.class), Mockito.anyString(),
|
||||
ArgumentMatchers.any(), Mockito.eq("CapabilitiesStatement-Quick"), Mockito.anyLong()))
|
||||
.thenReturn(new ResourceRequest<>(new CapabilityStatement(), 200, "location"));
|
||||
|
||||
// PUT
|
||||
Mockito.when(mockClient.issuePutRequest(Mockito.any(URI.class), Mockito.any(byte[].class), Mockito.anyString(),
|
||||
ArgumentMatchers.any(), Mockito.anyString(), Mockito.anyLong())).thenReturn(resourceResourceRequest);
|
||||
// POST
|
||||
Mockito.when(mockClient.issuePostRequest(Mockito.any(URI.class), Mockito.any(byte[].class), Mockito.anyString(),
|
||||
ArgumentMatchers.any(), Mockito.anyString(), Mockito.anyLong())).thenReturn(resourceResourceRequest);
|
||||
Mockito
|
||||
.when(mockClient.issuePostRequest(Mockito.any(URI.class), Mockito.any(byte[].class), Mockito.anyString(),
|
||||
ArgumentMatchers.any(), Mockito.contains("validate"), Mockito.anyLong()))
|
||||
.thenReturn(new ResourceRequest<>(new OperationOutcome(), 200, "location"));
|
||||
// BUNDLE REQ
|
||||
Mockito
|
||||
.when(mockClient.executeBundleRequest(Mockito.any(HTTPRequest.class), Mockito.anyString(),
|
||||
ArgumentMatchers.any(), Mockito.anyString(), Mockito.anyInt(), Mockito.anyLong()))
|
||||
.thenReturn(generateBundle());
|
||||
toolingClient = new FHIRToolingClient(TX_ADDR, "fhir/test-cases");
|
||||
toolingClient.setClient(mockClient);
|
||||
}
|
||||
|
||||
private List<HTTPHeader> getHeaders() {
|
||||
return new ArrayList<>(Arrays.asList(h1, h2, h3));
|
||||
}
|
||||
|
||||
private List<HTTPHeader> getHeadersWithAgent() {
|
||||
return new ArrayList<>(Arrays.asList(h1, h2, h3, agentHeader));
|
||||
}
|
||||
|
||||
private Bundle generateBundle() {
|
||||
Patient patient = generatePatient();
|
||||
Observation observation = generateObservation();
|
||||
|
||||
// The observation refers to the patient using the ID, which is already
|
||||
// set to a temporary UUID
|
||||
observation.setSubject(new Reference(patient.getIdElement().getValue()));
|
||||
|
||||
// Create a bundle that will be used as a transaction
|
||||
Bundle bundle = new Bundle();
|
||||
|
||||
// Add the patient as an entry.
|
||||
bundle.addEntry().setFullUrl(patient.getIdElement().getValue()).setResource(patient).getRequest().setUrl("Patient")
|
||||
.setIfNoneExist("identifier=http://acme.org/mrns|12345").setMethod(Bundle.HTTPVerb.POST);
|
||||
|
||||
return bundle;
|
||||
}
|
||||
|
||||
private Patient generatePatient() {
|
||||
// Create a patient object
|
||||
Patient patient = new Patient();
|
||||
patient.addIdentifier().setSystem("http://acme.org/mrns").setValue("12345");
|
||||
patient.addName().setFamily("Jameson").addGiven("J").addGiven("Jonah");
|
||||
patient.setGender(Enumerations.AdministrativeGender.MALE);
|
||||
|
||||
// Give the patient a temporary UUID so that other resources in
|
||||
// the transaction can refer to it
|
||||
patient.setId(IdType.newRandomUuid());
|
||||
return patient;
|
||||
}
|
||||
|
||||
private Observation generateObservation() {
|
||||
// Create an observation object
|
||||
Observation observation = new Observation();
|
||||
observation.getCode().addCoding().setSystem("http://loinc.org").setCode("789-8")
|
||||
.setDisplay("Erythrocytes [#/volume] in Blood by Automated count");
|
||||
observation.setValue(new Quantity().setValue(4.12).setUnit("10 trillion/L").setSystem("http://unitsofmeasure.org")
|
||||
.setCode("10*12/L"));
|
||||
return observation;
|
||||
}
|
||||
|
||||
private void checkHeaders(Iterable<HTTPHeader> argumentCaptorValue) {
|
||||
List<HTTPHeader> capturedHeaders = new ArrayList<>();
|
||||
argumentCaptorValue.forEach(capturedHeaders::add);
|
||||
|
||||
getHeadersWithAgent().forEach(header -> {
|
||||
assertTrue(capturedHeaders.contains(header));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTerminologyCapabilities() throws IOException {
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.getTerminologyCapabilities();
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCapabilitiesStatement() throws IOException {
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.getCapabilitiesStatement();
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCapabilitiesStatementQuick() throws IOException {
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.getCapabilitiesStatementQuick();
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void read() throws IOException {
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.read(Patient.class, "id");
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void vread() throws IOException {
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.vread(Patient.class, "id", "version");
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCanonical() throws IOException {
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.getCanonical(Patient.class, "canonicalURL");
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void update() throws IOException {
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.update(generatePatient());
|
||||
Mockito.verify(mockClient).issuePutRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.any(byte[].class),
|
||||
ArgumentMatchers.anyString(), headersArgumentCaptor.capture(), ArgumentMatchers.anyString(),
|
||||
ArgumentMatchers.anyLong());
|
||||
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate() throws IOException {
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.validate(Patient.class, generatePatient(), "id");
|
||||
Mockito.verify(mockClient).issuePostRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.any(byte[].class),
|
||||
ArgumentMatchers.anyString(), headersArgumentCaptor.capture(), ArgumentMatchers.anyString(),
|
||||
ArgumentMatchers.anyLong());
|
||||
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
package org.hl7.fhir.r4.utils.client.network;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeader;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class ClientHeadersTest {
|
||||
ClientHeaders clientHeaders;
|
||||
|
||||
HTTPHeader h1 = new HTTPHeader("header1", "value1");
|
||||
HTTPHeader h2 = new HTTPHeader("header2", "value2");
|
||||
HTTPHeader h3 = new HTTPHeader("header3", "value3");
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
clientHeaders = new ClientHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Happy path add headers individually.")
|
||||
void addHeader() {
|
||||
clientHeaders.addHeader(h1);
|
||||
Assertions.assertEquals(1, clientHeaders.headers().size());
|
||||
clientHeaders.addHeader(h2);
|
||||
Assertions.assertEquals(2, clientHeaders.headers().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test duplicate header added individually throws FHIRException.")
|
||||
void addHeaderDuplicateAdd() {
|
||||
clientHeaders.addHeader(h1);
|
||||
Assertions.assertThrows(FHIRException.class, () -> clientHeaders.addHeader(h1));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Happy path add headers as list.")
|
||||
void addHeaders() {
|
||||
List<HTTPHeader> headersList = Arrays.asList(h1, h2, h3);
|
||||
clientHeaders.addHeaders(headersList);
|
||||
Assertions.assertEquals(3, clientHeaders.headers().size());
|
||||
Assertions.assertEquals(headersList, clientHeaders.headers());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Happy path add headers as list.")
|
||||
void addHeadersDuplicateAdd() {
|
||||
List<HTTPHeader> headersList = Arrays.asList(h1, h2, h1);
|
||||
Assertions.assertThrows(FHIRException.class, () -> clientHeaders.addHeaders(headersList));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Happy path remove existing header.")
|
||||
void removeHeader() {
|
||||
clientHeaders.addHeader(h1);
|
||||
clientHeaders.addHeader(h2);
|
||||
clientHeaders.addHeader(h3);
|
||||
clientHeaders.removeHeader(h2);
|
||||
Assertions.assertEquals(2, clientHeaders.headers().size());
|
||||
clientHeaders.removeHeader(new HTTPHeader("header3", "value3"));
|
||||
Assertions.assertEquals(1, clientHeaders.headers().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Remove header not contained in list.")
|
||||
void removeHeaderUnknown() {
|
||||
clientHeaders.addHeader(h1);
|
||||
clientHeaders.addHeader(h2);
|
||||
Assertions.assertThrows(FHIRException.class, () -> clientHeaders.removeHeader(h3));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Happy path remove list of existing headers.")
|
||||
void removeHeaders() {
|
||||
List<HTTPHeader> headersToAddList = Arrays.asList(h1, h2, h3);
|
||||
List<HTTPHeader> headersToRemoveList = Arrays.asList(h2, h3);
|
||||
clientHeaders.addHeaders(headersToAddList);
|
||||
clientHeaders.removeHeaders(headersToRemoveList);
|
||||
Assertions.assertEquals(1, clientHeaders.headers().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Remove list containing unknown header.")
|
||||
void removeHeadersUnknown() {
|
||||
List<HTTPHeader> headersToAddList = Arrays.asList(h1, h3);
|
||||
List<HTTPHeader> headersToRemoveList = Arrays.asList(h2, h3);
|
||||
clientHeaders.addHeaders(headersToAddList);
|
||||
Assertions.assertThrows(FHIRException.class, () -> clientHeaders.removeHeaders(headersToRemoveList));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clearHeaders() {
|
||||
List<HTTPHeader> headersToAddList = Arrays.asList(h1, h3);
|
||||
clientHeaders.addHeaders(headersToAddList);
|
||||
Assertions.assertEquals(2, clientHeaders.headers().size());
|
||||
clientHeaders.clearHeaders();
|
||||
Assertions.assertEquals(0, clientHeaders.headers().size());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
package org.hl7.fhir.r4.utils.client.network;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import org.hl7.fhir.r4.context.HTMLClientLogger;
|
||||
import org.hl7.fhir.r4.formats.JsonParser;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ClientTest {
|
||||
private static final long TIMEOUT = 5000;
|
||||
|
||||
private MockWebServer server;
|
||||
private HttpUrl serverUrl;
|
||||
private Client client;
|
||||
|
||||
private final Address address = new Address().setCity("Toronto").setState("Ontario").setCountry("Canada");
|
||||
private final HumanName humanName = new HumanName().addGiven("Mark").setFamily("Iantorno");
|
||||
private final Patient patient = new Patient().addName(humanName).addAddress(address)
|
||||
.setGender(Enumerations.AdministrativeGender.MALE);
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
setupMockServer();
|
||||
client = new Client();
|
||||
}
|
||||
|
||||
void setupMockServer() {
|
||||
server = new MockWebServer();
|
||||
serverUrl = server.url("/v1/endpoint");
|
||||
}
|
||||
|
||||
byte[] generateResourceBytes(Resource resource) throws IOException {
|
||||
return new JsonParser().composeBytes(resource);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("GET request, happy path.")
|
||||
void test_get_happy_path() throws IOException, URISyntaxException {
|
||||
server.enqueue(new MockResponse().setBody(new String(generateResourceBytes(patient))));
|
||||
ResourceRequest<Resource> resourceRequest = client.issueGetResourceRequest(new URI(serverUrl.toString()), "json",
|
||||
null, null, TIMEOUT);
|
||||
Assertions.assertTrue(resourceRequest.isSuccessfulRequest());
|
||||
Assertions.assertTrue(patient.equalsDeep(resourceRequest.getPayload()),
|
||||
"GET request returned resource does not match expected.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("GET request, test client retries after timeout failure.")
|
||||
void test_get_retries_with_timeout() throws IOException, URISyntaxException {
|
||||
int failedAttempts = new Random().nextInt(5) + 1;
|
||||
System.out.println("Simulating <" + failedAttempts + "> failed connections (timeouts) before success.");
|
||||
for (int i = 0; i < failedAttempts; i++) {
|
||||
server.enqueue(new MockResponse().setHeadersDelay(TIMEOUT * 10, TimeUnit.MILLISECONDS)
|
||||
.setBody(new String(generateResourceBytes(patient))));
|
||||
}
|
||||
server.enqueue(new MockResponse().setBody(new String(generateResourceBytes(patient))));
|
||||
client.setRetryCount(failedAttempts + 1);
|
||||
|
||||
ResourceRequest<Resource> resourceRequest = client.issueGetResourceRequest(new URI(serverUrl.toString()), "json",
|
||||
null, null, TIMEOUT);
|
||||
Assertions.assertTrue(resourceRequest.isSuccessfulRequest());
|
||||
Assertions.assertTrue(patient.equalsDeep(resourceRequest.getPayload()),
|
||||
"GET request returned resource does not match expected.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("GET request, test client retries after bad response.")
|
||||
void test_get_retries_with_unsuccessful_response() throws IOException, URISyntaxException {
|
||||
int failedAttempts = new Random().nextInt(5) + 1;
|
||||
System.out.println("Simulating <" + failedAttempts + "> failed connections (bad response codes) before success.");
|
||||
for (int i = 0; i < failedAttempts; i++) {
|
||||
server.enqueue(new MockResponse().setResponseCode(400 + i).setBody(new String(generateResourceBytes(patient))));
|
||||
}
|
||||
server.enqueue(new MockResponse().setBody(new String(generateResourceBytes(patient))));
|
||||
client.setRetryCount(failedAttempts + 1);
|
||||
|
||||
ResourceRequest<Resource> resourceRequest = client.issueGetResourceRequest(new URI(serverUrl.toString()), "json",
|
||||
null, null, TIMEOUT);
|
||||
Assertions.assertTrue(resourceRequest.isSuccessfulRequest());
|
||||
Assertions.assertTrue(patient.equalsDeep(resourceRequest.getPayload()),
|
||||
"GET request returned resource does not match expected.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("PUT request, test payload received by server matches sent.")
|
||||
void test_put() throws IOException, URISyntaxException, InterruptedException {
|
||||
byte[] payload = ByteUtils.resourceToByteArray(patient, true, false, false);
|
||||
// Mock server response of 200, with the same resource payload returned that we
|
||||
// included in the PUT request
|
||||
server.enqueue(new MockResponse().setResponseCode(200).setBody(new String(payload)));
|
||||
|
||||
ResourceRequest<Resource> request = client.issuePutRequest(new URI(serverUrl.toString()), payload, "xml", null,
|
||||
TIMEOUT);
|
||||
RecordedRequest recordedRequest = server.takeRequest();
|
||||
Assertions.assertArrayEquals(payload, recordedRequest.getBody().readByteArray(),
|
||||
"PUT request payload does not match send data.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("POST request, test payload received by server matches sent.")
|
||||
void test_post() throws IOException, URISyntaxException, InterruptedException {
|
||||
byte[] payload = ByteUtils.resourceToByteArray(patient, true, false, false);
|
||||
// Mock server response of 200, with the same resource payload returned that we
|
||||
// included in the PUT request
|
||||
server.enqueue(new MockResponse().setResponseCode(200).setBody(new String(payload)));
|
||||
|
||||
ResourceRequest<Resource> request = client.issuePostRequest(new URI(serverUrl.toString()), payload, "xml", null,
|
||||
TIMEOUT);
|
||||
RecordedRequest recordedRequest = server.takeRequest();
|
||||
Assertions.assertArrayEquals(payload, recordedRequest.getBody().readByteArray(),
|
||||
"POST request payload does not match send data.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Testing the logger works.")
|
||||
void test_logger() throws IOException, URISyntaxException, InterruptedException {
|
||||
byte[] payload = ByteUtils.resourceToByteArray(patient, true, false, false);
|
||||
server.enqueue(new MockResponse().setResponseCode(200).setBody(new String(payload)));
|
||||
HTMLClientLogger mockLogger = Mockito.mock(HTMLClientLogger.class);
|
||||
client.setLogger(mockLogger);
|
||||
client.issuePostRequest(new URI(serverUrl.toString()), payload, "xml", null, TIMEOUT);
|
||||
server.takeRequest();
|
||||
Mockito.verify(mockLogger, Mockito.times(1)).logRequest(Mockito.anyString(), Mockito.anyString(), Mockito.anyList(),
|
||||
Mockito.any());
|
||||
Mockito.verify(mockLogger, Mockito.times(1)).logResponse(Mockito.anyString(), Mockito.anyList(), Mockito.any(), Mockito.anyLong());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
package org.hl7.fhir.r4.utils.client.network;
|
||||
|
||||
import okhttp3.Request;
|
||||
import org.hl7.fhir.r4.model.OperationOutcome;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeader;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeaderUtil;
|
||||
import org.hl7.fhir.utilities.http.HTTPRequest;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class FhirRequestBuilderTest {
|
||||
@Test
|
||||
@DisplayName("Test resource format headers are added correctly.")
|
||||
void addResourceFormatHeadersGET() {
|
||||
String testFormat = "yaml";
|
||||
HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com").withMethod(HTTPRequest.HttpMethod.GET);
|
||||
|
||||
Iterable<HTTPHeader> headers = FhirRequestBuilder.getResourceFormatHeaders(request, testFormat);
|
||||
|
||||
Map<String, List<String>> headersMap = HTTPHeaderUtil.getMultimap(headers);
|
||||
Assertions.assertNotNull(headersMap.get("Accept"), "Accept header null.");
|
||||
Assertions.assertEquals(testFormat, headersMap.get("Accept").get(0),
|
||||
"Accept header not populated with expected value " + testFormat + ".");
|
||||
|
||||
Assertions.assertNull(headersMap.get("Content-Type"), "Content-Type header null.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test resource format headers are added correctly (POST).")
|
||||
void addResourceFormatHeadersPOST() {
|
||||
String testFormat = "yaml";
|
||||
HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com").withMethod(HTTPRequest.HttpMethod.POST);
|
||||
|
||||
Iterable<HTTPHeader> headers = FhirRequestBuilder.getResourceFormatHeaders(request, testFormat);
|
||||
|
||||
Map<String, List<String>> headersMap = HTTPHeaderUtil.getMultimap(headers);
|
||||
Assertions.assertNotNull(headersMap.get("Accept"), "Accept header null.");
|
||||
Assertions.assertEquals(testFormat, headersMap.get("Accept").get(0),
|
||||
"Accept header not populated with expected value " + testFormat + ".");
|
||||
|
||||
Assertions.assertNotNull(headersMap.get("Content-Type"), "Content-Type header null.");
|
||||
Assertions.assertEquals(testFormat + ";charset=" + FhirRequestBuilder.DEFAULT_CHARSET, headersMap.get("Content-Type").get(0),
|
||||
"Content-Type header not populated with expected value \"" + testFormat + ";charset=" + FhirRequestBuilder.DEFAULT_CHARSET + "\".");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test that FATAL issue severity triggers error.")
|
||||
void hasErrorTestFatal() {
|
||||
OperationOutcome outcome = new OperationOutcome();
|
||||
outcome.addIssue(
|
||||
new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.INFORMATION));
|
||||
outcome.addIssue(
|
||||
new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.NULL));
|
||||
outcome.addIssue(
|
||||
new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.WARNING));
|
||||
outcome.addIssue(
|
||||
new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.FATAL));
|
||||
Assertions.assertTrue(FhirRequestBuilder.hasError(outcome), "Error check not triggered for FATAL issue severity.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test that ERROR issue severity triggers error.")
|
||||
void hasErrorTestError() {
|
||||
OperationOutcome outcome = new OperationOutcome();
|
||||
outcome.addIssue(
|
||||
new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.INFORMATION));
|
||||
outcome.addIssue(
|
||||
new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.NULL));
|
||||
outcome.addIssue(
|
||||
new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.WARNING));
|
||||
outcome.addIssue(
|
||||
new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.ERROR));
|
||||
Assertions.assertTrue(FhirRequestBuilder.hasError(outcome), "Error check not triggered for ERROR issue severity.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test that no FATAL or ERROR issue severity does not trigger error.")
|
||||
void hasErrorTestNoErrors() {
|
||||
OperationOutcome outcome = new OperationOutcome();
|
||||
outcome.addIssue(
|
||||
new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.INFORMATION));
|
||||
outcome.addIssue(
|
||||
new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.NULL));
|
||||
outcome.addIssue(
|
||||
new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.WARNING));
|
||||
Assertions.assertFalse(FhirRequestBuilder.hasError(outcome), "Error check triggered unexpectedly.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test that getLocationHeader returns header for 'location'.")
|
||||
void getLocationHeaderWhenOnlyLocationIsSet() {
|
||||
final String expectedLocationHeader = "location_header_value";
|
||||
Iterable<HTTPHeader> headers = List.of(new HTTPHeader(FhirRequestBuilder.LOCATION_HEADER, expectedLocationHeader));
|
||||
Assertions.assertEquals(expectedLocationHeader, FhirRequestBuilder.getLocationHeader(headers));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test that getLocationHeader returns header for 'content-location'.")
|
||||
void getLocationHeaderWhenOnlyContentLocationIsSet() {
|
||||
final String expectedContentLocationHeader = "content_location_header_value";
|
||||
Iterable<HTTPHeader> headers = List.of(new HTTPHeader(FhirRequestBuilder.CONTENT_LOCATION_HEADER, expectedContentLocationHeader));
|
||||
Assertions.assertEquals(expectedContentLocationHeader, FhirRequestBuilder.getLocationHeader(headers));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test that getLocationHeader returns 'location' header when both 'location' and 'content-location' are set.")
|
||||
void getLocationHeaderWhenLocationAndContentLocationAreSet() {
|
||||
final String expectedLocationHeader = "location_header_value";
|
||||
final String expectedContentLocationHeader = "content_location_header_value";
|
||||
Iterable<HTTPHeader> headers = List.of(
|
||||
new HTTPHeader(FhirRequestBuilder.LOCATION_HEADER, expectedLocationHeader),
|
||||
new HTTPHeader(FhirRequestBuilder.CONTENT_LOCATION_HEADER, expectedContentLocationHeader)
|
||||
);
|
||||
Assertions.assertEquals(expectedLocationHeader, FhirRequestBuilder.getLocationHeader(headers));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test that getLocationHeader returns null when no location available.")
|
||||
void getLocationHeaderWhenNoLocationSet() {
|
||||
|
||||
Assertions.assertNull(FhirRequestBuilder.getLocationHeader(Collections.emptyList()));
|
||||
}
|
||||
}
|
|
@ -34,6 +34,12 @@
|
|||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
|
|
|
@ -1,22 +1,14 @@
|
|||
package org.hl7.fhir.r4b.terminologies;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
@ -156,9 +148,9 @@ public class TerminologyCacheManager {
|
|||
String url = "https://tx.fhir.org/post/tx-cache/" + ghOrg + "/" + ghRepo + "/" + ghBranch + ".zip";
|
||||
System.out.println("Sending tx-cache to " + url + " (" + Utilities.describeSize(bs.toByteArray().length) + ")");
|
||||
|
||||
HTTPResult res = ManagedWebAccess.builder()
|
||||
HTTPResult res = ManagedWebAccess.accessor()
|
||||
.withBasicAuth(token.substring(0, token.indexOf(':')), token.substring(token.indexOf(':') + 1))
|
||||
.withAccept("application/zip").put(url, bs.toByteArray(), null);
|
||||
.put(url, bs.toByteArray(), null, "application/zip");
|
||||
if (res.getCode() >= 300) {
|
||||
System.out.println("sending cache failed: " + res.getCode());
|
||||
} else {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
package org.hl7.fhir.r4b.utils.client;
|
||||
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.internal.http2.Header;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
|
||||
/*
|
||||
|
@ -37,14 +38,13 @@ import org.hl7.fhir.r4b.model.*;
|
|||
import org.hl7.fhir.r4b.model.Parameters.ParametersParameterComponent;
|
||||
import org.hl7.fhir.r4b.utils.client.network.ByteUtils;
|
||||
import org.hl7.fhir.r4b.utils.client.network.Client;
|
||||
import org.hl7.fhir.r4b.utils.client.network.ClientHeaders;
|
||||
import org.hl7.fhir.r4b.utils.client.network.ResourceRequest;
|
||||
import org.hl7.fhir.utilities.FHIRBaseToolingClient;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.*;
|
||||
|
@ -90,12 +90,21 @@ public class FHIRToolingClient extends FHIRBaseToolingClient{
|
|||
private ResourceFormat preferredResourceFormat;
|
||||
private int maxResultSetSize = -1;// _count
|
||||
private CapabilityStatement capabilities;
|
||||
@Getter
|
||||
@Setter
|
||||
private Client client = new Client();
|
||||
private ArrayList<Header> headers = new ArrayList<>();
|
||||
private String username;
|
||||
private String password;
|
||||
private List<HTTPHeader> headers = new ArrayList<>();
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
private String userAgent;
|
||||
|
||||
@Setter
|
||||
private String acceptLanguage;
|
||||
|
||||
@Setter
|
||||
private String contentLanguage;
|
||||
|
||||
// Pass endpoint for client - URI
|
||||
public FHIRToolingClient(String baseServiceUrl, String userAgent) throws URISyntaxException {
|
||||
preferredResourceFormat = ResourceFormat.RESOURCE_XML;
|
||||
|
@ -111,14 +120,6 @@ public class FHIRToolingClient extends FHIRBaseToolingClient{
|
|||
checkCapabilities();
|
||||
}
|
||||
|
||||
public Client getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void setClient(Client client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
private void checkCapabilities() {
|
||||
try {
|
||||
capabilities = getCapabilitiesStatementQuick();
|
||||
|
@ -146,7 +147,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient{
|
|||
TerminologyCapabilities capabilities = null;
|
||||
try {
|
||||
capabilities = (TerminologyCapabilities) client.issueGetResourceRequest(resourceAddress.resolveMetadataTxCaps(),
|
||||
getPreferredResourceFormat(), generateHeaders(), "TerminologyCapabilities", timeoutNormal).getReference();
|
||||
getPreferredResourceFormat(), generateHeaders(false), "TerminologyCapabilities", timeoutNormal).getReference();
|
||||
} catch (Exception e) {
|
||||
throw new FHIRException("Error fetching the server's terminology capabilities", e);
|
||||
}
|
||||
|
@ -157,7 +158,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient{
|
|||
CapabilityStatement conformance = null;
|
||||
try {
|
||||
conformance = (CapabilityStatement) client.issueGetResourceRequest(resourceAddress.resolveMetadataUri(false),
|
||||
getPreferredResourceFormat(), generateHeaders(), "CapabilitiesStatement", timeoutNormal).getReference();
|
||||
getPreferredResourceFormat(), generateHeaders(false), "CapabilitiesStatement", timeoutNormal).getReference();
|
||||
} catch (Exception e) {
|
||||
throw new FHIRException("Error fetching the server's conformance statement", e);
|
||||
}
|
||||
|
@ -169,7 +170,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient{
|
|||
return capabilities;
|
||||
try {
|
||||
capabilities = (CapabilityStatement) client.issueGetResourceRequest(resourceAddress.resolveMetadataUri(true),
|
||||
getPreferredResourceFormat(), generateHeaders(), "CapabilitiesStatement-Quick", timeoutNormal)
|
||||
getPreferredResourceFormat(), generateHeaders(false), "CapabilitiesStatement-Quick", timeoutNormal)
|
||||
.getReference();
|
||||
} catch (Exception e) {
|
||||
throw new FHIRException("Error fetching the server's capability statement: " + e.getMessage(), e);
|
||||
|
@ -181,7 +182,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient{
|
|||
ResourceRequest<T> result = null;
|
||||
try {
|
||||
result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),
|
||||
getPreferredResourceFormat(), generateHeaders(), "Read " + resourceClass.getName() + "/" + id,
|
||||
getPreferredResourceFormat(), generateHeaders(false), "Read " + resourceClass.getName() + "/" + id,
|
||||
timeoutNormal);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
|
||||
|
@ -198,7 +199,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient{
|
|||
try {
|
||||
result = client.issueGetResourceRequest(
|
||||
resourceAddress.resolveGetUriFromResourceClassAndIdAndVersion(resourceClass, id, version),
|
||||
getPreferredResourceFormat(), generateHeaders(),
|
||||
getPreferredResourceFormat(), generateHeaders(false),
|
||||
"VRead " + resourceClass.getName() + "/" + id + "/?_history/" + version, timeoutNormal);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
|
||||
|
@ -215,7 +216,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient{
|
|||
try {
|
||||
result = client.issueGetResourceRequest(
|
||||
resourceAddress.resolveGetUriFromResourceClassAndCanonical(resourceClass, canonicalURL),
|
||||
getPreferredResourceFormat(), generateHeaders(), "Read " + resourceClass.getName() + "?url=" + canonicalURL,
|
||||
getPreferredResourceFormat(), generateHeaders(false), "Read " + resourceClass.getName() + "?url=" + canonicalURL,
|
||||
timeoutNormal);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
|
||||
|
@ -238,7 +239,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient{
|
|||
result = client.issuePutRequest(
|
||||
resourceAddress.resolveGetUriFromResourceClassAndId(resource.getClass(), resource.getId()),
|
||||
ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat()), false),
|
||||
getPreferredResourceFormat(), generateHeaders(), "Update " + resource.fhirType() + "/" + resource.getId(),
|
||||
getPreferredResourceFormat(), generateHeaders(true), "Update " + resource.fhirType() + "/" + resource.getId(),
|
||||
timeoutOperation);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
|
||||
|
@ -267,7 +268,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient{
|
|||
try {
|
||||
result = client.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),
|
||||
ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat()), false),
|
||||
getPreferredResourceFormat(), generateHeaders(), "Update " + resource.fhirType() + "/" + id,
|
||||
getPreferredResourceFormat(), generateHeaders(true), "Update " + resource.fhirType() + "/" + id,
|
||||
timeoutOperation);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
|
||||
|
@ -305,10 +306,10 @@ public class FHIRToolingClient extends FHIRBaseToolingClient{
|
|||
URI url = resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps);
|
||||
if (complex) {
|
||||
byte[] body = ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()), true);
|
||||
result = client.issuePostRequest(url, body, getPreferredResourceFormat(), generateHeaders(),
|
||||
result = client.issuePostRequest(url, body, getPreferredResourceFormat(), generateHeaders(true),
|
||||
"POST " + resourceClass.getName() + "/$" + name, timeoutLong);
|
||||
} else {
|
||||
result = client.issueGetResourceRequest(url, getPreferredResourceFormat(), generateHeaders(),
|
||||
result = client.issueGetResourceRequest(url, getPreferredResourceFormat(), generateHeaders(false),
|
||||
"GET " + resourceClass.getName() + "/$" + name, timeoutLong);
|
||||
}
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
|
@ -334,7 +335,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient{
|
|||
try {
|
||||
transactionResult = client.postBatchRequest(resourceAddress.getBaseServiceUri(),
|
||||
ByteUtils.resourceToByteArray(batch, false, isJson(getPreferredResourceFormat()), false),
|
||||
getPreferredResourceFormat(), generateHeaders(), "transaction",
|
||||
getPreferredResourceFormat(), generateHeaders(true), "transaction",
|
||||
timeoutOperation + (timeoutEntry * batch.getEntry().size()));
|
||||
} catch (Exception e) {
|
||||
handleException("An error occurred trying to process this transaction request", e);
|
||||
|
@ -348,7 +349,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient{
|
|||
try {
|
||||
result = client.issuePostRequest(resourceAddress.resolveValidateUri(resourceClass, id),
|
||||
ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat()), false),
|
||||
getPreferredResourceFormat(), generateHeaders(),
|
||||
getPreferredResourceFormat(), generateHeaders(true),
|
||||
"POST " + resourceClass.getName() + (id != null ? "/" + id : "") + "/$validate", timeoutLong);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
|
||||
|
@ -361,10 +362,14 @@ public class FHIRToolingClient extends FHIRBaseToolingClient{
|
|||
}
|
||||
|
||||
/**
|
||||
* Helper method to prevent nesting of previously thrown EFhirClientExceptions
|
||||
* Helper method to prevent nesting of previously thrown EFhirClientExceptions. If the e param is an instance of
|
||||
* EFhirClientException, it will be rethrown. Otherwise, a new EFhirClientException will be thrown with e as the
|
||||
* cause.
|
||||
*
|
||||
* @param e
|
||||
* @throws EFhirClientException
|
||||
|
||||
* @param message The EFhirClientException message.
|
||||
* @param e The exception
|
||||
* @throws EFhirClientException EFhirClientException representing the exception.
|
||||
*/
|
||||
protected void handleException(String message, Exception e) throws EFhirClientException {
|
||||
if (e instanceof EFhirClientException) {
|
||||
|
@ -378,8 +383,8 @@ public class FHIRToolingClient extends FHIRBaseToolingClient{
|
|||
* Helper method to determine whether desired resource representation is Json or
|
||||
* XML.
|
||||
*
|
||||
* @param format
|
||||
* @return
|
||||
* @param format the format
|
||||
* @return true if the format is JSON, false otherwise
|
||||
*/
|
||||
protected boolean isJson(String format) {
|
||||
boolean isJson = false;
|
||||
|
@ -406,7 +411,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient{
|
|||
try {
|
||||
result = client.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand"),
|
||||
ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat()), true), getPreferredResourceFormat(),
|
||||
generateHeaders(), "ValueSet/$expand?url=" + source.getUrl(), timeoutExpand);
|
||||
generateHeaders(true), "ValueSet/$expand?url=" + source.getUrl(), timeoutExpand);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
|
||||
(OperationOutcome) result.getPayload());
|
||||
|
@ -421,7 +426,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient{
|
|||
org.hl7.fhir.r4b.utils.client.network.ResourceRequest<Resource> result = null;
|
||||
try {
|
||||
result = client.issueGetResourceRequest(resourceAddress.resolveOperationUri(CodeSystem.class, "lookup", params),
|
||||
getPreferredResourceFormat(), generateHeaders(), "CodeSystem/$lookup", timeoutNormal);
|
||||
getPreferredResourceFormat(), generateHeaders(false), "CodeSystem/$lookup", timeoutNormal);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
@ -443,7 +448,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient{
|
|||
|
||||
result = client.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand", params),
|
||||
ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat()), true), getPreferredResourceFormat(),
|
||||
generateHeaders(), "ValueSet/$expand?url=" + source.getUrl(), timeoutExpand);
|
||||
generateHeaders(true), "ValueSet/$expand?url=" + source.getUrl(), timeoutExpand);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
|
||||
(OperationOutcome) result.getPayload());
|
||||
|
@ -466,7 +471,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient{
|
|||
result = client.issuePostRequest(
|
||||
resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
|
||||
ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()), true),
|
||||
getPreferredResourceFormat(), generateHeaders(), "Closure?name=" + name, timeoutNormal);
|
||||
getPreferredResourceFormat(), generateHeaders(true), "Closure?name=" + name, timeoutNormal);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
|
||||
(OperationOutcome) result.getPayload());
|
||||
|
@ -486,7 +491,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient{
|
|||
result = client.issuePostRequest(
|
||||
resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
|
||||
ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()), true),
|
||||
getPreferredResourceFormat(), generateHeaders(), "UpdateClosure?name=" + name, timeoutOperation);
|
||||
getPreferredResourceFormat(), generateHeaders(true), "UpdateClosure?name=" + name, timeoutOperation);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
|
||||
(OperationOutcome) result.getPayload());
|
||||
|
@ -497,22 +502,6 @@ public class FHIRToolingClient extends FHIRBaseToolingClient{
|
|||
return result == null ? null : (ConceptMap) result.getPayload();
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public long getTimeout() {
|
||||
return client.getTimeout();
|
||||
}
|
||||
|
@ -537,42 +526,27 @@ public class FHIRToolingClient extends FHIRBaseToolingClient{
|
|||
client.setRetryCount(retryCount);
|
||||
}
|
||||
|
||||
public void setClientHeaders(ArrayList<Header> headers) {
|
||||
this.headers = headers;
|
||||
public void setClientHeaders(Iterable<HTTPHeader> headers) {
|
||||
this.headers = new ArrayList<>();
|
||||
headers.forEach(this.headers::add);
|
||||
}
|
||||
|
||||
private Headers generateHeaders() {
|
||||
Headers.Builder builder = new Headers.Builder();
|
||||
// Add basic auth header if it exists
|
||||
if (basicAuthHeaderExists()) {
|
||||
builder.add(getAuthorizationHeader().toString());
|
||||
}
|
||||
private Iterable<HTTPHeader> generateHeaders(boolean hasBody) {
|
||||
// Add any other headers
|
||||
if (this.headers != null) {
|
||||
this.headers.forEach(header -> builder.add(header.toString()));
|
||||
}
|
||||
List<HTTPHeader> headers = new ArrayList<>(this.headers);
|
||||
if (!Utilities.noString(userAgent)) {
|
||||
builder.add("User-Agent: " + userAgent);
|
||||
headers.add(new HTTPHeader("User-Agent",userAgent));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public boolean basicAuthHeaderExists() {
|
||||
return (username != null) && (password != null);
|
||||
}
|
||||
if (!Utilities.noString(acceptLanguage)) {
|
||||
headers.add(new HTTPHeader("Accept-Language", acceptLanguage));
|
||||
}
|
||||
|
||||
public Header getAuthorizationHeader() {
|
||||
String usernamePassword = username + ":" + password;
|
||||
String base64usernamePassword = Base64.getEncoder().encodeToString(usernamePassword.getBytes());
|
||||
return new Header("Authorization", "Basic " + base64usernamePassword);
|
||||
}
|
||||
if (hasBody && !Utilities.noString(contentLanguage)) {
|
||||
headers.add(new HTTPHeader("Content-Language",contentLanguage));
|
||||
}
|
||||
|
||||
public String getUserAgent() {
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
public void setUserAgent(String userAgent) {
|
||||
this.userAgent = userAgent;
|
||||
return headers;
|
||||
}
|
||||
|
||||
public String getServerVersion() {
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
package org.hl7.fhir.r4b.utils.client.network;
|
||||
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hl7.fhir.r4b.model.Bundle;
|
||||
import org.hl7.fhir.r4b.model.Resource;
|
||||
import org.hl7.fhir.r4b.utils.client.EFhirClientException;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.hl7.fhir.utilities.http.HTTPRequest;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
@ -18,144 +19,132 @@ public class Client {
|
|||
|
||||
public static final String DEFAULT_CHARSET = "UTF-8";
|
||||
private static final long DEFAULT_TIMEOUT = 5000;
|
||||
@Getter @Setter
|
||||
private ToolingClientLogger logger;
|
||||
private FhirLoggingInterceptor fhirLoggingInterceptor;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
private int retryCount;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
private long timeout = DEFAULT_TIMEOUT;
|
||||
private byte[] payload;
|
||||
|
||||
@Setter @Getter
|
||||
private String base;
|
||||
|
||||
public String getBase() {
|
||||
return base;
|
||||
}
|
||||
|
||||
public void setBase(String base) {
|
||||
this.base = base;
|
||||
}
|
||||
|
||||
|
||||
public ToolingClientLogger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
public void setLogger(ToolingClientLogger logger) {
|
||||
this.logger = logger;
|
||||
this.fhirLoggingInterceptor = new FhirLoggingInterceptor(logger);
|
||||
}
|
||||
|
||||
public int getRetryCount() {
|
||||
return retryCount;
|
||||
}
|
||||
|
||||
public void setRetryCount(int retryCount) {
|
||||
this.retryCount = retryCount;
|
||||
}
|
||||
|
||||
public long getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public void setTimeout(long timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issueOptionsRequest(URI optionsUri, String resourceFormat,
|
||||
String message, long timeout) throws IOException {
|
||||
this.payload = null;
|
||||
Request.Builder request = new Request.Builder().method("OPTIONS", null).url(optionsUri.toURL());
|
||||
|
||||
return executeFhirRequest(request, resourceFormat, new Headers.Builder().build(), message, retryCount, timeout);
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(optionsUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.OPTIONS);
|
||||
return executeFhirRequest(request, resourceFormat, Collections.emptyList(), message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issueGetResourceRequest(URI resourceUri, String resourceFormat,
|
||||
Headers headers, String message, long timeout) throws IOException {
|
||||
this.payload = null;
|
||||
Request.Builder request = new Request.Builder().url(resourceUri.toURL());
|
||||
|
||||
Iterable<HTTPHeader> headers, String message, long timeout) throws IOException {
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.GET);
|
||||
return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public int tester(int trytry) {
|
||||
return 5;
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri, byte[] payload, String resourceFormat,
|
||||
String message, long timeout) throws IOException {
|
||||
return issuePutRequest(resourceUri, payload, resourceFormat, new Headers.Builder().build(), message, timeout);
|
||||
return issuePutRequest(resourceUri, payload, resourceFormat, Collections.emptyList(), message, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri, byte[] payload, String resourceFormat,
|
||||
Headers headers, String message, long timeout) throws IOException {
|
||||
Iterable<HTTPHeader> headers, String message, long timeout) throws IOException {
|
||||
if (payload == null)
|
||||
throw new EFhirClientException("PUT requests require a non-null payload");
|
||||
this.payload = payload;
|
||||
RequestBody body = RequestBody.create(payload);
|
||||
Request.Builder request = new Request.Builder().url(resourceUri.toURL()).put(body);
|
||||
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.PUT)
|
||||
.withBody(payload)
|
||||
.withContentType(getContentTypeWithDefaultCharset(resourceFormat));
|
||||
|
||||
return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri, byte[] payload,
|
||||
String resourceFormat, String message, long timeout) throws IOException {
|
||||
return issuePostRequest(resourceUri, payload, resourceFormat, new Headers.Builder().build(), message, timeout);
|
||||
return issuePostRequest(resourceUri, payload, resourceFormat, Collections.emptyList(), message, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri, byte[] payload,
|
||||
String resourceFormat, Headers headers, String message, long timeout) throws IOException {
|
||||
String resourceFormat, Iterable<HTTPHeader> headers, String message, long timeout) throws IOException {
|
||||
if (payload == null)
|
||||
throw new EFhirClientException("POST requests require a non-null payload");
|
||||
this.payload = payload;
|
||||
RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
|
||||
Request.Builder request = new Request.Builder().url(resourceUri.toURL()).post(body);
|
||||
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.POST)
|
||||
.withBody(payload)
|
||||
.withContentType(getContentTypeWithDefaultCharset(resourceFormat));
|
||||
|
||||
return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public boolean issueDeleteRequest(URI resourceUri) throws IOException {
|
||||
Request.Builder request = new Request.Builder().url(resourceUri.toURL()).delete();
|
||||
return executeFhirRequest(request, null, new Headers.Builder().build(), null, retryCount, timeout)
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.DELETE);
|
||||
return executeFhirRequest(request, null, Collections.emptyList(), null, retryCount, timeout)
|
||||
.isSuccessfulRequest();
|
||||
}
|
||||
|
||||
public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat) throws IOException {
|
||||
Request.Builder request = new Request.Builder().url(resourceUri.toURL());
|
||||
|
||||
return executeBundleRequest(request, resourceFormat, new Headers.Builder().build(), null, retryCount, timeout);
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.GET);
|
||||
return executeBundleRequest(request, resourceFormat, Collections.emptyList(), null, retryCount, timeout);
|
||||
}
|
||||
|
||||
public Bundle issuePostFeedRequest(URI resourceUri, Map<String, String> parameters, String resourceName,
|
||||
Resource resource, String resourceFormat) throws IOException {
|
||||
String boundary = "----WebKitFormBoundarykbMUo6H8QaUnYtRy";
|
||||
byte[] payload = ByteUtils.encodeFormSubmission(parameters, resourceName, resource, boundary);
|
||||
RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
|
||||
Request.Builder request = new Request.Builder().url(resourceUri.toURL()).post(body);
|
||||
|
||||
return executeBundleRequest(request, resourceFormat, new Headers.Builder().build(), null, retryCount, timeout);
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.POST)
|
||||
.withBody(payload)
|
||||
.withContentType(getContentTypeWithDefaultCharset(resourceFormat));
|
||||
return executeBundleRequest(request, resourceFormat, Collections.emptyList(), null, retryCount, timeout);
|
||||
}
|
||||
|
||||
public Bundle postBatchRequest(URI resourceUri, byte[] payload, String resourceFormat, Headers headers,
|
||||
public Bundle postBatchRequest(URI resourceUri, byte[] payload, String resourceFormat, Iterable<HTTPHeader> headers,
|
||||
String message, int timeout) throws IOException {
|
||||
if (payload == null)
|
||||
throw new EFhirClientException("POST requests require a non-null payload");
|
||||
RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
|
||||
Request.Builder request = new Request.Builder().url(resourceUri.toURL()).post(body);
|
||||
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.POST)
|
||||
.withBody(payload)
|
||||
.withContentType(getContentTypeWithDefaultCharset(resourceFormat));
|
||||
return executeBundleRequest(request, resourceFormat, headers, message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> Bundle executeBundleRequest(Request.Builder request, String resourceFormat,
|
||||
Headers headers, String message, int retryCount, long timeout) throws IOException {
|
||||
return new FhirRequestBuilder(request, base).withLogger(fhirLoggingInterceptor).withResourceFormat(resourceFormat)
|
||||
private static String getContentTypeWithDefaultCharset(String resourceFormat) {
|
||||
return resourceFormat + ";charset=" + DEFAULT_CHARSET;
|
||||
}
|
||||
|
||||
public <T extends Resource> Bundle executeBundleRequest(HTTPRequest request, String resourceFormat,
|
||||
Iterable<HTTPHeader> headers, String message, int retryCount, long timeout) throws IOException {
|
||||
return new FhirRequestBuilder(request, base).withLogger(logger).withResourceFormat(resourceFormat)
|
||||
.withRetryCount(retryCount).withMessage(message)
|
||||
.withHeaders(headers == null ? new Headers.Builder().build() : headers)
|
||||
.withHeaders(headers == null ? Collections.emptyList() : headers)
|
||||
.withTimeout(timeout, TimeUnit.MILLISECONDS).executeAsBatch();
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> executeFhirRequest(Request.Builder request, String resourceFormat,
|
||||
Headers headers, String message, int retryCount, long timeout) throws IOException {
|
||||
return new FhirRequestBuilder(request, base).withLogger(fhirLoggingInterceptor).withResourceFormat(resourceFormat)
|
||||
public <T extends Resource> ResourceRequest<T> executeFhirRequest(HTTPRequest request, String resourceFormat,
|
||||
Iterable<HTTPHeader> headers, String message, int retryCount, long timeout) throws IOException {
|
||||
return new FhirRequestBuilder(request, base).withLogger(logger).withResourceFormat(resourceFormat)
|
||||
.withRetryCount(retryCount).withMessage(message)
|
||||
.withHeaders(headers == null ? new Headers.Builder().build() : headers)
|
||||
.withHeaders(headers == null ? Collections.emptyList() : headers)
|
||||
.withTimeout(timeout, TimeUnit.MILLISECONDS).execute();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
package org.hl7.fhir.r4b.utils.client.network;
|
||||
|
||||
import okhttp3.internal.http2.Header;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -9,35 +10,35 @@ import java.util.stream.Collectors;
|
|||
|
||||
/**
|
||||
* Generic Implementation of Client Headers.
|
||||
*
|
||||
* <p/>
|
||||
* Stores a list of headers for HTTP calls to the TX server. Users can implement
|
||||
* their own instance if they desire specific, custom behavior.
|
||||
*/
|
||||
public class ClientHeaders {
|
||||
|
||||
private final ArrayList<Header> headers;
|
||||
private final ArrayList<HTTPHeader> headers;
|
||||
|
||||
public ClientHeaders() {
|
||||
this.headers = new ArrayList<>();
|
||||
}
|
||||
|
||||
public ClientHeaders(ArrayList<Header> headers) {
|
||||
public ClientHeaders(ArrayList<HTTPHeader> headers) {
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public ArrayList<Header> headers() {
|
||||
public ArrayList<HTTPHeader> headers() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a header to the list of stored headers for network operations.
|
||||
*
|
||||
* @param header {@link Header} to add to the list.
|
||||
* @param header {@link HTTPHeader} to add to the list.
|
||||
* @throws FHIRException if the header being added is a duplicate.
|
||||
*/
|
||||
public ClientHeaders addHeader(Header header) throws FHIRException {
|
||||
public ClientHeaders addHeader(HTTPHeader header) throws FHIRException {
|
||||
if (headers.contains(header)) {
|
||||
throw new FHIRException("Attempting to add duplicate header, <" + header.name + ", " + header.value + ">.");
|
||||
throw new FHIRException("Attempting to add duplicate header, <" + header.getName() + ", " + header.getValue() + ">.");
|
||||
}
|
||||
headers.add(header);
|
||||
return this;
|
||||
|
@ -46,10 +47,10 @@ public class ClientHeaders {
|
|||
/**
|
||||
* Add a header to the list of stored headers for network operations.
|
||||
*
|
||||
* @param headerList {@link List} of {@link Header} to add.
|
||||
* @param headerList {@link List} of {@link HTTPHeader} to add.
|
||||
* @throws FHIRException if any of the headers being added is a duplicate.
|
||||
*/
|
||||
public ClientHeaders addHeaders(List<Header> headerList) throws FHIRException {
|
||||
public ClientHeaders addHeaders(List<HTTPHeader> headerList) throws FHIRException {
|
||||
headerList.forEach(this::addHeader);
|
||||
return this;
|
||||
}
|
||||
|
@ -57,13 +58,13 @@ public class ClientHeaders {
|
|||
/**
|
||||
* Removes the passed in header from the list of stored headers.
|
||||
*
|
||||
* @param header {@link Header} to remove from the list.
|
||||
* @param header {@link HTTPHeader} to remove from the list.
|
||||
* @throws FHIRException if the header passed in does not exist within the
|
||||
* stored list.
|
||||
*/
|
||||
public ClientHeaders removeHeader(Header header) throws FHIRException {
|
||||
public ClientHeaders removeHeader(HTTPHeader header) throws FHIRException {
|
||||
if (!headers.remove(header)) {
|
||||
throw new FHIRException("Attempting to remove header, <" + header.name + ", " + header.value
|
||||
throw new FHIRException("Attempting to remove header, <" + header.getName() + ", " + header.getValue()
|
||||
+ ">, from GenericClientHeaders that is not currently stored.");
|
||||
}
|
||||
return this;
|
||||
|
@ -72,17 +73,17 @@ public class ClientHeaders {
|
|||
/**
|
||||
* Removes the passed in headers from the list of stored headers.
|
||||
*
|
||||
* @param headerList {@link List} of {@link Header} to remove.
|
||||
* @param headerList {@link List} of {@link HTTPHeader} to remove.
|
||||
* @throws FHIRException if any of the headers passed in does not exist within
|
||||
* the stored list.
|
||||
*/
|
||||
public ClientHeaders removeHeaders(List<Header> headerList) throws FHIRException {
|
||||
public ClientHeaders removeHeaders(List<HTTPHeader> headerList) throws FHIRException {
|
||||
headerList.forEach(this::removeHeader);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all stored {@link Header}.
|
||||
* Clears all stored {@link HTTPHeader}.
|
||||
*/
|
||||
public ClientHeaders clearHeaders() {
|
||||
headers.clear();
|
||||
|
@ -91,7 +92,7 @@ public class ClientHeaders {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.headers.stream().map(header -> "\t" + header.name + ":" + header.value)
|
||||
return this.headers.stream().map(header -> "\t" + header.getName() + ":" + header.getValue())
|
||||
.collect(Collectors.joining(",\n", "{\n", "\n}"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
package org.hl7.fhir.r4b.utils.client.network;
|
||||
|
||||
import okhttp3.*;
|
||||
import okio.Buffer;
|
||||
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class FhirLoggingInterceptor implements Interceptor {
|
||||
|
||||
private ToolingClientLogger logger;
|
||||
|
||||
public FhirLoggingInterceptor(ToolingClientLogger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public FhirLoggingInterceptor setLogger(ToolingClientLogger logger) {
|
||||
this.logger = logger;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response intercept(@Nonnull Interceptor.Chain chain) throws IOException {
|
||||
// Log Request
|
||||
Request request = chain.request();
|
||||
List<String> hdrs = new ArrayList<>();
|
||||
for (String s : request.headers().toString().split("\\n")) {
|
||||
hdrs.add(s.trim());
|
||||
}
|
||||
byte[] cnt = null;
|
||||
if (request.body() != null) {
|
||||
Buffer buf = new Buffer();
|
||||
request.body().writeTo(buf);
|
||||
cnt = buf.readByteArray();
|
||||
}
|
||||
if (logger != null) {
|
||||
logger.logRequest(request.method(), request.url().toString(), hdrs, cnt);
|
||||
}
|
||||
|
||||
// Log Response
|
||||
Response response = null;
|
||||
response = chain.proceed(chain.request());
|
||||
|
||||
MediaType contentType = null;
|
||||
byte[] bodyBytes = null;
|
||||
if (response.body() != null) {
|
||||
contentType = response.body().contentType();
|
||||
bodyBytes = response.body().bytes();
|
||||
}
|
||||
|
||||
// Get Headers as List
|
||||
List<String> headerList = new ArrayList<>();
|
||||
Map<String, List<String>> headerMap = response.headers().toMultimap();
|
||||
headerMap.keySet().forEach(key -> headerMap.get(key).forEach(value -> headerList.add(key + ":" + value)));
|
||||
|
||||
if (logger != null) {
|
||||
logger.logResponse(Integer.toString(response.code()), headerList, bodyBytes, 0);
|
||||
}
|
||||
|
||||
// Reading byte[] clears body. Need to recreate.
|
||||
ResponseBody body = ResponseBody.create(bodyBytes, contentType);
|
||||
return response.newBuilder().body(body).build();
|
||||
}
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
package org.hl7.fhir.r4b.utils.client.network;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.r4b.formats.IParser;
|
||||
|
@ -18,15 +17,10 @@ import org.hl7.fhir.r4b.utils.OperationOutcomeUtilities;
|
|||
import org.hl7.fhir.r4b.utils.ResourceUtilities;
|
||||
import org.hl7.fhir.r4b.utils.client.EFhirClientException;
|
||||
import org.hl7.fhir.r4b.utils.client.ResourceFormat;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.hl7.fhir.utilities.http.*;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlUtils;
|
||||
|
||||
import okhttp3.Authenticator;
|
||||
import okhttp3.Credentials;
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class FhirRequestBuilder {
|
||||
|
||||
protected static final String HTTP_PROXY_USER = "http.proxyUser";
|
||||
|
@ -35,13 +29,10 @@ public class FhirRequestBuilder {
|
|||
protected static final String LOCATION_HEADER = "location";
|
||||
protected static final String CONTENT_LOCATION_HEADER = "content-location";
|
||||
protected static final String DEFAULT_CHARSET = "UTF-8";
|
||||
/**
|
||||
* The singleton instance of the HttpClient, used for all requests.
|
||||
*/
|
||||
private static OkHttpClient okHttpClient;
|
||||
private final Request.Builder httpRequest;
|
||||
|
||||
private final HTTPRequest httpRequest;
|
||||
private String resourceFormat = null;
|
||||
private Headers headers = null;
|
||||
private Iterable<HTTPHeader> headers = null;
|
||||
private String message = null;
|
||||
private int retryCount = 1;
|
||||
/**
|
||||
|
@ -55,63 +46,43 @@ public class FhirRequestBuilder {
|
|||
private TimeUnit timeoutUnit = TimeUnit.MILLISECONDS;
|
||||
|
||||
/**
|
||||
* {@link FhirLoggingInterceptor} for log output.
|
||||
* {@link ToolingClientLogger} for log output.
|
||||
*/
|
||||
private FhirLoggingInterceptor logger = null;
|
||||
private ToolingClientLogger logger = null;
|
||||
private String source;
|
||||
|
||||
public FhirRequestBuilder(Request.Builder httpRequest, String source) {
|
||||
this.httpRequest = httpRequest;
|
||||
public FhirRequestBuilder(HTTPRequest httpRequest, String source) {
|
||||
this.source = source;
|
||||
this.httpRequest = httpRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds necessary default headers, formatting headers, and any passed in
|
||||
* {@link Headers} to the passed in {@link okhttp3.Request.Builder}
|
||||
* {@link HTTPHeader} to the passed in {@link okhttp3.Request.Builder}
|
||||
*
|
||||
* @param request {@link okhttp3.Request.Builder} to add headers to.
|
||||
* @param format Expected {@link Resource} format.
|
||||
* @param headers Any additional {@link Headers} to add to the request.
|
||||
* @param headers Any additional {@link HTTPHeader} to add to the request.
|
||||
*/
|
||||
protected static void formatHeaders(Request.Builder request, String format, Headers headers) {
|
||||
addDefaultHeaders(request, headers);
|
||||
if (format != null)
|
||||
addResourceFormatHeaders(request, format);
|
||||
if (headers != null)
|
||||
addHeaders(request, headers);
|
||||
protected static HTTPRequest formatHeaders(HTTPRequest request, String format, Iterable<HTTPHeader> headers) {
|
||||
List<HTTPHeader> allHeaders = new ArrayList<>();
|
||||
request.getHeaders().forEach(allHeaders::add);
|
||||
|
||||
if (format != null) getResourceFormatHeaders(request, format).forEach(allHeaders::add);
|
||||
if (headers != null) headers.forEach(allHeaders::add);
|
||||
return request.withHeaders(allHeaders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds necessary headers for all REST requests.
|
||||
* <li>User-Agent : hapi-fhir-tooling-client</li>
|
||||
*
|
||||
* @param request {@link Request.Builder} to add default headers to.
|
||||
*/
|
||||
protected static void addDefaultHeaders(Request.Builder request, Headers headers) {
|
||||
if (headers == null || !headers.names().contains("User-Agent")) {
|
||||
request.addHeader("User-Agent", "hapi-fhir-tooling-client");
|
||||
protected static Iterable<HTTPHeader> getResourceFormatHeaders(HTTPRequest httpRequest, String format) {
|
||||
List<HTTPHeader> headers = new ArrayList<>();
|
||||
headers.add(new HTTPHeader("Accept", format));
|
||||
if (httpRequest.getMethod() == HTTPRequest.HttpMethod.PUT
|
||||
|| httpRequest.getMethod() == HTTPRequest.HttpMethod.POST
|
||||
|| httpRequest.getMethod() == HTTPRequest.HttpMethod.PATCH
|
||||
) {
|
||||
headers.add( new HTTPHeader("Content-Type", format + ";charset=" + DEFAULT_CHARSET));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds necessary headers for the given resource format provided.
|
||||
*
|
||||
* @param request {@link Request.Builder} to add default headers to.
|
||||
*/
|
||||
protected static void addResourceFormatHeaders(Request.Builder request, String format) {
|
||||
request.addHeader("Accept", format);
|
||||
request.addHeader("Content-Type", format + ";charset=" + DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through the passed in {@link Headers} and adds them to the provided
|
||||
* {@link Request.Builder}.
|
||||
*
|
||||
* @param request {@link Request.Builder} to add headers to.
|
||||
* @param headers {@link Headers} to add to request.
|
||||
*/
|
||||
protected static void addHeaders(Request.Builder request, Headers headers) {
|
||||
headers.forEach(header -> request.addHeader(header.getFirst(), header.getSecond()));
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -131,68 +102,24 @@ public class FhirRequestBuilder {
|
|||
}
|
||||
|
||||
/**
|
||||
* Extracts the 'location' header from the passes in {@link Headers}. If no
|
||||
* Extracts the 'location' header from the passed in {@link HTTPHeader}s. If no
|
||||
* value for 'location' exists, the value for 'content-location' is returned. If
|
||||
* neither header exists, we return null.
|
||||
*
|
||||
* @param headers {@link Headers} to evaluate
|
||||
* @param headers {@link HTTPHeader}s to evaluate
|
||||
* @return {@link String} header value, or null if no location headers are set.
|
||||
*/
|
||||
protected static String getLocationHeader(Headers headers) {
|
||||
Map<String, List<String>> headerMap = headers.toMultimap();
|
||||
if (headerMap.containsKey(LOCATION_HEADER)) {
|
||||
return headerMap.get(LOCATION_HEADER).get(0);
|
||||
} else if (headerMap.containsKey(CONTENT_LOCATION_HEADER)) {
|
||||
return headerMap.get(CONTENT_LOCATION_HEADER).get(0);
|
||||
} else {
|
||||
return null;
|
||||
protected static String getLocationHeader(Iterable<HTTPHeader> headers) {
|
||||
String locationHeader = HTTPHeaderUtil.getSingleHeader(headers, LOCATION_HEADER);
|
||||
|
||||
if (locationHeader != null) {
|
||||
return locationHeader;
|
||||
}
|
||||
return HTTPHeaderUtil.getSingleHeader(headers, CONTENT_LOCATION_HEADER);
|
||||
}
|
||||
|
||||
/**
|
||||
* We only ever want to have one copy of the HttpClient kicking around at any
|
||||
* given time. If we need to make changes to any configuration, such as proxy
|
||||
* settings, timeout, caches, etc, we can do a per-call configuration through
|
||||
* the {@link OkHttpClient#newBuilder()} method. That will return a builder that
|
||||
* shares the same connection pool, dispatcher, and configuration with the
|
||||
* original client.
|
||||
* </p>
|
||||
* The {@link OkHttpClient} uses the proxy auth properties set in the current
|
||||
* system properties. The reason we don't set the proxy address and
|
||||
* authentication explicitly, is due to the fact that this class is often used
|
||||
* in conjunction with other http client tools which rely on the
|
||||
* system.properties settings to determine proxy settings. It's easier to keep
|
||||
* the method consistent across the board. ...for now.
|
||||
*
|
||||
* @return {@link OkHttpClient} instance
|
||||
*/
|
||||
protected OkHttpClient getHttpClient() {
|
||||
if (okHttpClient == null) {
|
||||
okHttpClient = new OkHttpClient();
|
||||
}
|
||||
|
||||
Authenticator proxyAuthenticator = getAuthenticator();
|
||||
|
||||
OkHttpClient.Builder builder = okHttpClient.newBuilder();
|
||||
if (logger != null)
|
||||
builder.addInterceptor(logger);
|
||||
builder.addInterceptor(new RetryInterceptor(retryCount));
|
||||
|
||||
return builder.connectTimeout(timeout, timeoutUnit).writeTimeout(timeout, timeoutUnit)
|
||||
.readTimeout(timeout, timeoutUnit).proxyAuthenticator(proxyAuthenticator).build();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private static Authenticator getAuthenticator() {
|
||||
return (route, response) -> {
|
||||
final String httpProxyUser = System.getProperty(HTTP_PROXY_USER);
|
||||
final String httpProxyPass = System.getProperty(HTTP_PROXY_PASS);
|
||||
if (httpProxyUser != null && httpProxyPass != null) {
|
||||
String credential = Credentials.basic(httpProxyUser, httpProxyPass);
|
||||
return response.request().newBuilder().header(HEADER_PROXY_AUTH, credential).build();
|
||||
}
|
||||
return response.request().newBuilder().build();
|
||||
};
|
||||
protected ManagedFhirWebAccessor getManagedWebAccessor() {
|
||||
return ManagedWebAccess.fhirAccessor().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger);
|
||||
}
|
||||
|
||||
public FhirRequestBuilder withResourceFormat(String resourceFormat) {
|
||||
|
@ -200,7 +127,7 @@ public class FhirRequestBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public FhirRequestBuilder withHeaders(Headers headers) {
|
||||
public FhirRequestBuilder withHeaders(Iterable<HTTPHeader> headers) {
|
||||
this.headers = headers;
|
||||
return this;
|
||||
}
|
||||
|
@ -215,7 +142,7 @@ public class FhirRequestBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public FhirRequestBuilder withLogger(FhirLoggingInterceptor logger) {
|
||||
public FhirRequestBuilder withLogger(ToolingClientLogger logger) {
|
||||
this.logger = logger;
|
||||
return this;
|
||||
}
|
||||
|
@ -226,33 +153,28 @@ public class FhirRequestBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
protected Request buildRequest() {
|
||||
return httpRequest.build();
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> execute() throws IOException {
|
||||
formatHeaders(httpRequest, resourceFormat, headers);
|
||||
Response response = getHttpClient().newCall(httpRequest.build()).execute();
|
||||
HTTPRequest requestWithHeaders = formatHeaders(httpRequest, resourceFormat, headers);
|
||||
HTTPResult response = getManagedWebAccessor().httpCall(requestWithHeaders);
|
||||
T resource = unmarshalReference(response, resourceFormat, null);
|
||||
return new ResourceRequest<T>(resource, response.code(), getLocationHeader(response.headers()));
|
||||
return new ResourceRequest<T>(resource, response.getCode(), getLocationHeader(response.getHeaders()));
|
||||
}
|
||||
|
||||
public Bundle executeAsBatch() throws IOException {
|
||||
formatHeaders(httpRequest, resourceFormat, null);
|
||||
Response response = getHttpClient().newCall(httpRequest.build()).execute();
|
||||
return unmarshalFeed(response, resourceFormat);
|
||||
HTTPRequest requestWithHeaders = formatHeaders(httpRequest, resourceFormat, null);
|
||||
HTTPResult response = getManagedWebAccessor().httpCall(requestWithHeaders);return unmarshalFeed(response, resourceFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmarshalls a resource from the response stream.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T extends Resource> T unmarshalReference(Response response, String format, String resourceType) {
|
||||
int code = response.code();
|
||||
protected <T extends Resource> T unmarshalReference(HTTPResult response, String format, String resourceType) {
|
||||
int code = response.getCode();
|
||||
boolean ok = code >= 200 && code < 300;
|
||||
if (response.body() == null) {
|
||||
if (response.getContent() == null) {
|
||||
if (!ok) {
|
||||
throw new EFhirClientException(response.message());
|
||||
throw new EFhirClientException(response.getMessage());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@ -261,9 +183,9 @@ public class FhirRequestBuilder {
|
|||
|
||||
Resource resource = null;
|
||||
try {
|
||||
body = response.body().string();
|
||||
String ct = response.header("Content-Type");
|
||||
if (ct == null) {
|
||||
body = response.getContentAsString();
|
||||
String contentType = HTTPHeaderUtil.getSingleHeader(response.getHeaders(), "Content-Type");
|
||||
if (contentType == null) {
|
||||
if (ok) {
|
||||
resource = getParser(format).parse(body);
|
||||
} else {
|
||||
|
@ -272,10 +194,10 @@ public class FhirRequestBuilder {
|
|||
resource = OperationOutcomeUtilities.outcomeFromTextError(body);
|
||||
}
|
||||
} else {
|
||||
if (ct.contains(";")) {
|
||||
ct = ct.substring(0, ct.indexOf(";"));
|
||||
if (contentType.contains(";")) {
|
||||
contentType = contentType.substring(0, contentType.indexOf(";"));
|
||||
}
|
||||
switch (ct) {
|
||||
switch (contentType) {
|
||||
case "application/json":
|
||||
case "application/fhir+json":
|
||||
if (!format.contains("json")) {
|
||||
|
@ -295,10 +217,10 @@ public class FhirRequestBuilder {
|
|||
resource = OperationOutcomeUtilities.outcomeFromTextError(body);
|
||||
break;
|
||||
case "text/html" :
|
||||
resource = OperationOutcomeUtilities.outcomeFromTextError(XhtmlUtils.convertHtmlToText(response.body().string(), source));
|
||||
resource = OperationOutcomeUtilities.outcomeFromTextError(XhtmlUtils.convertHtmlToText(response.getContentAsString(), source));
|
||||
break;
|
||||
default: // not sure what else to do?
|
||||
System.out.println("Got content-type '"+ct+"' from "+source);
|
||||
System.out.println("Got content-type '"+contentType+"' from "+source);
|
||||
System.out.println(body);
|
||||
resource = OperationOutcomeUtilities.outcomeFromTextError(body);
|
||||
}
|
||||
|
@ -333,7 +255,7 @@ public class FhirRequestBuilder {
|
|||
/**
|
||||
* Unmarshalls Bundle from response stream.
|
||||
*/
|
||||
protected Bundle unmarshalFeed(Response response, String format) {
|
||||
protected Bundle unmarshalFeed(HTTPResult response, String format) {
|
||||
return unmarshalReference(response, format, "Bundle");
|
||||
}
|
||||
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
package org.hl7.fhir.r4b.utils.client.network;
|
||||
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* An {@link Interceptor} for {@link okhttp3.OkHttpClient} that controls the
|
||||
* number of times we retry a to execute a given request, before reporting a
|
||||
* failure. This includes unsuccessful return codes and timeouts.
|
||||
*/
|
||||
public class RetryInterceptor implements Interceptor {
|
||||
|
||||
// Delay between retying failed requests, in millis
|
||||
private final long RETRY_TIME = 2000;
|
||||
|
||||
// Maximum number of times to retry the request before failing
|
||||
private final int maxRetry;
|
||||
|
||||
// Internal counter for tracking the number of times we've tried this request
|
||||
private int retryCounter = 0;
|
||||
|
||||
public RetryInterceptor(int maxRetry) {
|
||||
this.maxRetry = maxRetry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response intercept(Interceptor.Chain chain) throws IOException {
|
||||
Request request = chain.request();
|
||||
Response response = null;
|
||||
|
||||
do {
|
||||
try {
|
||||
// If we are retrying a failed request that failed due to a bad response from
|
||||
// the server, we must close it first
|
||||
if (response != null) {
|
||||
// System.out.println("Previous " + chain.request().method() + " attempt returned HTTP<" + (response.code())
|
||||
// + "> from url -> " + chain.request().url() + ".");
|
||||
response.close();
|
||||
}
|
||||
// System.out.println(chain.request().method() + " attempt <" + (retryCounter +
|
||||
// 1) + "> to url -> " + chain.request().url());
|
||||
response = chain.proceed(request);
|
||||
} catch (IOException e) {
|
||||
try {
|
||||
// Include a small break in between requests.
|
||||
Thread.sleep(RETRY_TIME);
|
||||
} catch (InterruptedException e1) {
|
||||
System.out.println(chain.request().method() + " to url -> " + chain.request().url() + " interrupted on try <"
|
||||
+ retryCounter + ">");
|
||||
}
|
||||
} finally {
|
||||
retryCounter++;
|
||||
}
|
||||
} while ((response == null || !response.isSuccessful()) && (retryCounter <= maxRetry + 1));
|
||||
|
||||
/*
|
||||
* if something has gone wrong, and we are unable to complete the request, we
|
||||
* still need to initialize the return response so we don't get a null pointer
|
||||
* exception.
|
||||
*/
|
||||
return response != null ? response : chain.proceed(request);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,79 +1,89 @@
|
|||
package org.hl7.fhir.r4b.utils.client;
|
||||
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.internal.http2.Header;
|
||||
import org.hl7.fhir.r4b.model.*;
|
||||
import org.hl7.fhir.r4b.utils.client.network.Client;
|
||||
import org.hl7.fhir.r4b.utils.client.network.ResourceRequest;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.hl7.fhir.utilities.http.HTTPRequest;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeader;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class FHIRToolingClientTest {
|
||||
|
||||
String TX_ADDR = "http://tx.fhir.org";
|
||||
|
||||
Header h1 = new Header("header1", "value1");
|
||||
Header h2 = new Header("header2", "value2");
|
||||
Header h3 = new Header("header3", "value3");
|
||||
HTTPHeader h1 = new HTTPHeader("header1", "value1");
|
||||
HTTPHeader h2 = new HTTPHeader("header2", "value2");
|
||||
HTTPHeader h3 = new HTTPHeader("header3", "value3");
|
||||
|
||||
HTTPHeader agentHeader = new HTTPHeader("User-Agent", "fhir/test-cases");
|
||||
|
||||
private Client mockClient;
|
||||
private FHIRToolingClient toolingClient;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<Iterable<HTTPHeader>> headersArgumentCaptor;
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException, URISyntaxException {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
mockClient = Mockito.mock(Client.class);
|
||||
ResourceRequest<Resource> resourceResourceRequest = new ResourceRequest<>(generateBundle(), 200, "");
|
||||
|
||||
// GET
|
||||
Mockito.when(mockClient.issueGetResourceRequest(Mockito.any(URI.class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), Mockito.anyString(), Mockito.anyLong())).thenReturn(resourceResourceRequest);
|
||||
ArgumentMatchers.any(), Mockito.anyString(), Mockito.anyLong())).thenReturn(resourceResourceRequest);
|
||||
Mockito
|
||||
.when(mockClient.issueGetResourceRequest(Mockito.any(URI.class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), Mockito.eq("TerminologyCapabilities"), Mockito.anyLong()))
|
||||
ArgumentMatchers.any(), Mockito.eq("TerminologyCapabilities"), Mockito.anyLong()))
|
||||
.thenReturn(new ResourceRequest<>(new TerminologyCapabilities(), 200, "location"));
|
||||
Mockito
|
||||
.when(mockClient.issueGetResourceRequest(Mockito.any(URI.class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), Mockito.eq("CapabilitiesStatement"), Mockito.anyLong()))
|
||||
ArgumentMatchers.any(), Mockito.eq("CapabilitiesStatement"), Mockito.anyLong()))
|
||||
.thenReturn(new ResourceRequest<>(new CapabilityStatement(), 200, "location"));
|
||||
Mockito
|
||||
.when(mockClient.issueGetResourceRequest(Mockito.any(URI.class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), Mockito.eq("CapabilitiesStatement-Quick"), Mockito.anyLong()))
|
||||
ArgumentMatchers.any(), Mockito.eq("CapabilitiesStatement-Quick"), Mockito.anyLong()))
|
||||
.thenReturn(new ResourceRequest<>(new CapabilityStatement(), 200, "location"));
|
||||
|
||||
// PUT
|
||||
Mockito.when(mockClient.issuePutRequest(Mockito.any(URI.class), Mockito.any(byte[].class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), Mockito.anyString(), Mockito.anyLong())).thenReturn(resourceResourceRequest);
|
||||
ArgumentMatchers.any(), Mockito.anyString(), Mockito.anyLong())).thenReturn(resourceResourceRequest);
|
||||
// POST
|
||||
Mockito.when(mockClient.issuePostRequest(Mockito.any(URI.class), Mockito.any(byte[].class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), Mockito.anyString(), Mockito.anyLong())).thenReturn(resourceResourceRequest);
|
||||
ArgumentMatchers.any(), Mockito.anyString(), Mockito.anyLong())).thenReturn(resourceResourceRequest);
|
||||
Mockito
|
||||
.when(mockClient.issuePostRequest(Mockito.any(URI.class), Mockito.any(byte[].class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), Mockito.contains("validate"), Mockito.anyLong()))
|
||||
ArgumentMatchers.any(), Mockito.contains("validate"), Mockito.anyLong()))
|
||||
.thenReturn(new ResourceRequest<>(new OperationOutcome(), 200, "location"));
|
||||
// BUNDLE REQ
|
||||
Mockito
|
||||
.when(mockClient.executeBundleRequest(Mockito.any(Request.Builder.class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), Mockito.anyString(), Mockito.anyInt(), Mockito.anyLong()))
|
||||
.when(mockClient.executeBundleRequest(Mockito.any(HTTPRequest.class), Mockito.anyString(),
|
||||
ArgumentMatchers.any(), Mockito.anyString(), Mockito.anyInt(), Mockito.anyLong()))
|
||||
.thenReturn(generateBundle());
|
||||
toolingClient = new FHIRToolingClient(TX_ADDR, "fhir/test-cases");
|
||||
toolingClient.setClient(mockClient);
|
||||
}
|
||||
|
||||
private ArrayList<Header> getHeaders() {
|
||||
private List<HTTPHeader> getHeaders() {
|
||||
return new ArrayList<>(Arrays.asList(h1, h2, h3));
|
||||
}
|
||||
|
||||
private List<HTTPHeader> getHeadersWithAgent() {
|
||||
return new ArrayList<>(Arrays.asList(h1, h2, h3, agentHeader));
|
||||
}
|
||||
|
||||
private Bundle generateBundle() {
|
||||
Patient patient = generatePatient();
|
||||
Observation observation = generateObservation();
|
||||
|
@ -115,108 +125,102 @@ class FHIRToolingClientTest {
|
|||
return observation;
|
||||
}
|
||||
|
||||
private void checkHeaders(Headers argumentCaptorValue) {
|
||||
getHeaders().forEach(header -> {
|
||||
// System.out.println("Checking header <" + header.component1().utf8() + ", " + header.component2().utf8() + ">");
|
||||
Assertions.assertEquals(argumentCaptorValue.get(header.component1().utf8()), header.component2().utf8());
|
||||
private void checkHeaders(Iterable<HTTPHeader> argumentCaptorValue) {
|
||||
List<HTTPHeader> capturedHeaders = new ArrayList<>();
|
||||
argumentCaptorValue.forEach(capturedHeaders::add);
|
||||
|
||||
getHeadersWithAgent().forEach(header -> {
|
||||
assertTrue(capturedHeaders.contains(header));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTerminologyCapabilities() throws IOException {
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.getTerminologyCapabilities();
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCapabilitiesStatement() throws IOException {
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.getCapabilitiesStatement();
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCapabilitiesStatementQuick() throws IOException {
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.getCapabilitiesStatementQuick();
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void read() throws IOException {
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.read(Patient.class, "id");
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void vread() throws IOException {
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.vread(Patient.class, "id", "version");
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCanonical() throws IOException {
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.getCanonical(Patient.class, "canonicalURL");
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void update() throws IOException {
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.update(generatePatient());
|
||||
Mockito.verify(mockClient).issuePutRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.any(byte[].class),
|
||||
ArgumentMatchers.anyString(), headersArgumentCaptor.capture(), ArgumentMatchers.anyString(),
|
||||
ArgumentMatchers.anyLong());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate() throws IOException {
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.validate(Patient.class, generatePatient(), "id");
|
||||
Mockito.verify(mockClient).issuePostRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.any(byte[].class),
|
||||
ArgumentMatchers.anyString(), headersArgumentCaptor.capture(), ArgumentMatchers.anyString(),
|
||||
ArgumentMatchers.anyLong());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package org.hl7.fhir.r4b.utils.client.network;
|
||||
|
||||
import okhttp3.internal.http2.Header;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeader;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
|
@ -14,9 +14,9 @@ class ClientHeadersTest {
|
|||
|
||||
ClientHeaders clientHeaders;
|
||||
|
||||
Header h1 = new Header("header1", "value1");
|
||||
Header h2 = new Header("header2", "value2");
|
||||
Header h3 = new Header("header3", "value3");
|
||||
HTTPHeader h1 = new HTTPHeader("header1", "value1");
|
||||
HTTPHeader h2 = new HTTPHeader("header2", "value2");
|
||||
HTTPHeader h3 = new HTTPHeader("header3", "value3");
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
|
@ -42,7 +42,7 @@ class ClientHeadersTest {
|
|||
@Test
|
||||
@DisplayName("Happy path add headers as list.")
|
||||
void addHeaders() {
|
||||
List<Header> headersList = Arrays.asList(h1, h2, h3);
|
||||
List<HTTPHeader> headersList = Arrays.asList(h1, h2, h3);
|
||||
clientHeaders.addHeaders(headersList);
|
||||
Assertions.assertEquals(3, clientHeaders.headers().size());
|
||||
Assertions.assertEquals(headersList, clientHeaders.headers());
|
||||
|
@ -51,7 +51,7 @@ class ClientHeadersTest {
|
|||
@Test
|
||||
@DisplayName("Happy path add headers as list.")
|
||||
void addHeadersDuplicateAdd() {
|
||||
List<Header> headersList = Arrays.asList(h1, h2, h1);
|
||||
List<HTTPHeader> headersList = Arrays.asList(h1, h2, h1);
|
||||
Assertions.assertThrows(FHIRException.class, () -> clientHeaders.addHeaders(headersList));
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ class ClientHeadersTest {
|
|||
clientHeaders.addHeader(h3);
|
||||
clientHeaders.removeHeader(h2);
|
||||
Assertions.assertEquals(2, clientHeaders.headers().size());
|
||||
clientHeaders.removeHeader(new Header("header3", "value3"));
|
||||
clientHeaders.removeHeader(new HTTPHeader("header3", "value3"));
|
||||
Assertions.assertEquals(1, clientHeaders.headers().size());
|
||||
}
|
||||
|
||||
|
@ -78,8 +78,8 @@ class ClientHeadersTest {
|
|||
@Test
|
||||
@DisplayName("Happy path remove list of existing headers.")
|
||||
void removeHeaders() {
|
||||
List<Header> headersToAddList = Arrays.asList(h1, h2, h3);
|
||||
List<Header> headersToRemoveList = Arrays.asList(h2, h3);
|
||||
List<HTTPHeader> headersToAddList = Arrays.asList(h1, h2, h3);
|
||||
List<HTTPHeader> headersToRemoveList = Arrays.asList(h2, h3);
|
||||
clientHeaders.addHeaders(headersToAddList);
|
||||
clientHeaders.removeHeaders(headersToRemoveList);
|
||||
Assertions.assertEquals(1, clientHeaders.headers().size());
|
||||
|
@ -88,15 +88,15 @@ class ClientHeadersTest {
|
|||
@Test
|
||||
@DisplayName("Remove list containing unknown header.")
|
||||
void removeHeadersUnknown() {
|
||||
List<Header> headersToAddList = Arrays.asList(h1, h3);
|
||||
List<Header> headersToRemoveList = Arrays.asList(h2, h3);
|
||||
List<HTTPHeader> headersToAddList = Arrays.asList(h1, h3);
|
||||
List<HTTPHeader> headersToRemoveList = Arrays.asList(h2, h3);
|
||||
clientHeaders.addHeaders(headersToAddList);
|
||||
Assertions.assertThrows(FHIRException.class, () -> clientHeaders.removeHeaders(headersToRemoveList));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clearHeaders() {
|
||||
List<Header> headersToAddList = Arrays.asList(h1, h3);
|
||||
List<HTTPHeader> headersToAddList = Arrays.asList(h1, h3);
|
||||
clientHeaders.addHeaders(headersToAddList);
|
||||
Assertions.assertEquals(2, clientHeaders.headers().size());
|
||||
clientHeaders.clearHeaders();
|
||||
|
|
|
@ -1,71 +1,51 @@
|
|||
package org.hl7.fhir.r4b.utils.client.network;
|
||||
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
|
||||
import org.hl7.fhir.r4b.model.OperationOutcome;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeader;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeaderUtil;
|
||||
import org.hl7.fhir.utilities.http.HTTPRequest;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
class FhirRequestBuilderTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("Test default headers are added correctly.")
|
||||
void addDefaultHeaders() {
|
||||
Request.Builder request = new Request.Builder().url("http://www.google.com");
|
||||
FhirRequestBuilder.addDefaultHeaders(request, null);
|
||||
|
||||
Map<String, List<String>> headersMap = request.build().headers().toMultimap();
|
||||
Assertions.assertNotNull(headersMap.get("User-Agent"), "User-Agent header null.");
|
||||
Assertions.assertEquals("hapi-fhir-tooling-client", headersMap.get("User-Agent").get(0),
|
||||
"User-Agent header not populated with expected value \"hapi-fhir-tooling-client\".");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test resource format headers are added correctly.")
|
||||
void addResourceFormatHeaders() {
|
||||
void addResourceFormatHeadersGET() {
|
||||
String testFormat = "yaml";
|
||||
Request.Builder request = new Request.Builder().url("http://www.google.com");
|
||||
FhirRequestBuilder.addResourceFormatHeaders(request, testFormat);
|
||||
HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com").withMethod(HTTPRequest.HttpMethod.GET);
|
||||
|
||||
Map<String, List<String>> headersMap = request.build().headers().toMultimap();
|
||||
Iterable<HTTPHeader> headers = FhirRequestBuilder.getResourceFormatHeaders(request, testFormat);
|
||||
|
||||
Map<String, List<String>> headersMap = HTTPHeaderUtil.getMultimap(headers);
|
||||
Assertions.assertNotNull(headersMap.get("Accept"), "Accept header null.");
|
||||
Assertions.assertEquals(testFormat, headersMap.get("Accept").get(0),
|
||||
"Accept header not populated with expected value " + testFormat + ".");
|
||||
|
||||
Assertions.assertNotNull(headersMap.get("Content-Type"), "Content-Type header null.");
|
||||
Assertions.assertEquals(testFormat + ";charset=" + FhirRequestBuilder.DEFAULT_CHARSET,
|
||||
headersMap.get("Content-Type").get(0), "Content-Type header not populated with expected value \"" + testFormat
|
||||
+ ";charset=" + FhirRequestBuilder.DEFAULT_CHARSET + "\".");
|
||||
Assertions.assertNull(headersMap.get("Content-Type"), "Content-Type header null.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test a list of provided headers are added correctly.")
|
||||
void addHeaders() {
|
||||
String headerName1 = "headerName1";
|
||||
String headerValue1 = "headerValue1";
|
||||
String headerName2 = "headerName2";
|
||||
String headerValue2 = "headerValue2";
|
||||
@DisplayName("Test resource format headers are added correctly (POST).")
|
||||
void addResourceFormatHeadersPOST() {
|
||||
String testFormat = "yaml";
|
||||
HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com").withMethod(HTTPRequest.HttpMethod.POST);
|
||||
|
||||
Headers headers = new Headers.Builder().add(headerName1, headerValue1).add(headerName2, headerValue2).build();
|
||||
Iterable<HTTPHeader> headers = FhirRequestBuilder.getResourceFormatHeaders(request, testFormat);
|
||||
|
||||
Request.Builder request = new Request.Builder().url("http://www.google.com");
|
||||
FhirRequestBuilder.addHeaders(request, headers);
|
||||
Map<String, List<String>> headersMap = HTTPHeaderUtil.getMultimap(headers);
|
||||
Assertions.assertNotNull(headersMap.get("Accept"), "Accept header null.");
|
||||
Assertions.assertEquals(testFormat, headersMap.get("Accept").get(0),
|
||||
"Accept header not populated with expected value " + testFormat + ".");
|
||||
|
||||
Map<String, List<String>> headersMap = request.build().headers().toMultimap();
|
||||
Assertions.assertNotNull(headersMap.get(headerName1), headerName1 + " header null.");
|
||||
Assertions.assertEquals(headerValue1, headersMap.get(headerName1).get(0),
|
||||
headerName1 + " header not populated with expected value " + headerValue1 + ".");
|
||||
Assertions.assertNotNull(headersMap.get(headerName2), headerName2 + " header null.");
|
||||
Assertions.assertEquals(headerValue2, headersMap.get(headerName2).get(0),
|
||||
headerName2 + " header not populated with expected value " + headerValue2 + ".");
|
||||
Assertions.assertNotNull(headersMap.get("Content-Type"), "Content-Type header null.");
|
||||
Assertions.assertEquals(testFormat + ";charset=" + FhirRequestBuilder.DEFAULT_CHARSET, headersMap.get("Content-Type").get(0),
|
||||
"Content-Type header not populated with expected value \"" + testFormat + ";charset=" + FhirRequestBuilder.DEFAULT_CHARSET + "\".");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -115,7 +95,7 @@ class FhirRequestBuilderTest {
|
|||
@DisplayName("Test that getLocationHeader returns header for 'location'.")
|
||||
void getLocationHeaderWhenOnlyLocationIsSet() {
|
||||
final String expectedLocationHeader = "location_header_value";
|
||||
Headers headers = new Headers.Builder().add(FhirRequestBuilder.LOCATION_HEADER, expectedLocationHeader).build();
|
||||
Iterable<HTTPHeader> headers = List.of(new HTTPHeader(FhirRequestBuilder.LOCATION_HEADER, expectedLocationHeader));
|
||||
Assertions.assertEquals(expectedLocationHeader, FhirRequestBuilder.getLocationHeader(headers));
|
||||
}
|
||||
|
||||
|
@ -123,8 +103,7 @@ class FhirRequestBuilderTest {
|
|||
@DisplayName("Test that getLocationHeader returns header for 'content-location'.")
|
||||
void getLocationHeaderWhenOnlyContentLocationIsSet() {
|
||||
final String expectedContentLocationHeader = "content_location_header_value";
|
||||
Headers headers = new Headers.Builder()
|
||||
.add(FhirRequestBuilder.CONTENT_LOCATION_HEADER, expectedContentLocationHeader).build();
|
||||
Iterable<HTTPHeader> headers = List.of(new HTTPHeader(FhirRequestBuilder.CONTENT_LOCATION_HEADER, expectedContentLocationHeader));
|
||||
Assertions.assertEquals(expectedContentLocationHeader, FhirRequestBuilder.getLocationHeader(headers));
|
||||
}
|
||||
|
||||
|
@ -133,15 +112,17 @@ class FhirRequestBuilderTest {
|
|||
void getLocationHeaderWhenLocationAndContentLocationAreSet() {
|
||||
final String expectedLocationHeader = "location_header_value";
|
||||
final String expectedContentLocationHeader = "content_location_header_value";
|
||||
Headers headers = new Headers.Builder().add(FhirRequestBuilder.LOCATION_HEADER, expectedLocationHeader)
|
||||
.add(FhirRequestBuilder.CONTENT_LOCATION_HEADER, expectedContentLocationHeader).build();
|
||||
Iterable<HTTPHeader> headers = List.of(
|
||||
new HTTPHeader(FhirRequestBuilder.LOCATION_HEADER, expectedLocationHeader),
|
||||
new HTTPHeader(FhirRequestBuilder.CONTENT_LOCATION_HEADER, expectedContentLocationHeader)
|
||||
);
|
||||
Assertions.assertEquals(expectedLocationHeader, FhirRequestBuilder.getLocationHeader(headers));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test that getLocationHeader returns null when no location available.")
|
||||
void getLocationHeaderWhenNoLocationSet() {
|
||||
Headers headers = new Headers.Builder().build();
|
||||
Assertions.assertNull(FhirRequestBuilder.getLocationHeader(headers));
|
||||
|
||||
Assertions.assertNull(FhirRequestBuilder.getLocationHeader(Collections.emptyList()));
|
||||
}
|
||||
}
|
|
@ -126,6 +126,12 @@
|
|||
</dependency>
|
||||
|
||||
<!-- Test dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
|
|
|
@ -604,7 +604,7 @@ public class FHIRPathEngine {
|
|||
warnings.addAll(typeWarnings);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
public TypeDetails checkOnTypes(Object appContext, String resourceType, TypeDetails types, ExpressionNode expr, List<IssueMessage> warnings) throws FHIRLexerException, PathEngineException, DefinitionException {
|
||||
typeWarnings.clear();
|
||||
TypeDetails res = executeType(new ExecutionTypeContext(appContext, resourceType, types, types), types, expr, null, true, false, expr);
|
||||
|
@ -612,6 +612,13 @@ public class FHIRPathEngine {
|
|||
return res;
|
||||
}
|
||||
|
||||
public TypeDetails checkOnTypes(Object appContext, String resourceType, TypeDetails types, ExpressionNode expr, List<IssueMessage> warnings, boolean canBeNone) throws FHIRLexerException, PathEngineException, DefinitionException {
|
||||
typeWarnings.clear();
|
||||
TypeDetails res = executeType(new ExecutionTypeContext(appContext, resourceType, types, types), types, expr, null, true, canBeNone, expr);
|
||||
warnings.addAll(typeWarnings);
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* check that paths referred to in the ExpressionNode are valid
|
||||
*
|
||||
|
@ -6585,7 +6592,7 @@ public class FHIRPathEngine {
|
|||
|
||||
|
||||
/** given an element definition in a profile, what element contains the differentiating fixed
|
||||
* for the element, given the differentiating expresssion. The expression is only allowed to
|
||||
* for the element, given the differentiating expression. The expression is only allowed to
|
||||
* use a subset of FHIRPath
|
||||
*
|
||||
* @param profile
|
||||
|
|
|
@ -1,22 +1,14 @@
|
|||
package org.hl7.fhir.r5.terminologies;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
@ -156,9 +148,9 @@ public class TerminologyCacheManager {
|
|||
// post it to
|
||||
String url = "https://tx.fhir.org/post/tx-cache/"+ghOrg+"/"+ghRepo+"/"+ghBranch+".zip";
|
||||
System.out.println("Sending tx-cache to "+url+" ("+Utilities.describeSize(bs.toByteArray().length)+")");
|
||||
HTTPResult res = ManagedWebAccess.builder()
|
||||
HTTPResult res = ManagedWebAccess.accessor()
|
||||
.withBasicAuth(token.substring(0, token.indexOf(':')), token.substring(token.indexOf(':') + 1))
|
||||
.withAccept("application/zip").put(url, bs.toByteArray(), null);
|
||||
.put(url, bs.toByteArray(), null, "application/zip");
|
||||
|
||||
if (res.getCode() >= 300) {
|
||||
System.out.println("sending cache failed: "+res.getCode());
|
||||
|
|
|
@ -2,7 +2,6 @@ package org.hl7.fhir.r5.terminologies.client;
|
|||
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/*
|
||||
|
@ -41,13 +40,10 @@ import org.hl7.fhir.r5.model.CanonicalResource;
|
|||
import org.hl7.fhir.r5.model.CapabilityStatement;
|
||||
import org.hl7.fhir.r5.model.CodeSystem;
|
||||
import org.hl7.fhir.r5.model.Parameters;
|
||||
import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent;
|
||||
import org.hl7.fhir.r5.model.Resource;
|
||||
import org.hl7.fhir.r5.model.TerminologyCapabilities;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
import org.hl7.fhir.r5.terminologies.client.ITerminologyClient;
|
||||
import org.hl7.fhir.r5.terminologies.client.TerminologyClientManager.ITerminologyClientFactory;
|
||||
import org.hl7.fhir.r5.terminologies.client.TerminologyClientR5.TerminologyClientR5Factory;
|
||||
import org.hl7.fhir.r5.utils.client.FHIRToolingClient;
|
||||
import org.hl7.fhir.r5.utils.client.network.ClientHeaders;
|
||||
import org.hl7.fhir.utilities.FhirPublication;
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
package org.hl7.fhir.r5.utils.client;
|
||||
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.internal.http2.Header;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.model.Bundle;
|
||||
import org.hl7.fhir.r5.model.OperationOutcome;
|
||||
import org.hl7.fhir.r5.model.Resource;
|
||||
import org.hl7.fhir.r5.utils.client.EFhirClientException;
|
||||
|
||||
/*
|
||||
Copyright (c) 2011+, HL7, Inc.
|
||||
|
@ -45,7 +44,7 @@ import org.hl7.fhir.r5.utils.client.network.ResourceRequest;
|
|||
import org.hl7.fhir.utilities.FHIRBaseToolingClient;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -93,18 +92,24 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
|
||||
private String base;
|
||||
private ResourceAddress resourceAddress;
|
||||
@Setter
|
||||
private ResourceFormat preferredResourceFormat;
|
||||
private int maxResultSetSize = -1;//_count
|
||||
private CapabilityStatement capabilities;
|
||||
@Getter
|
||||
@Setter
|
||||
private Client client = new Client();
|
||||
private ArrayList<Header> headers = new ArrayList<>();
|
||||
private String username;
|
||||
private String password;
|
||||
private List<HTTPHeader> headers = new ArrayList<>();
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
private String userAgent;
|
||||
|
||||
@Setter
|
||||
private String acceptLanguage;
|
||||
|
||||
private String acceptLang;
|
||||
private String contentLang;
|
||||
@Setter
|
||||
private String contentLanguage;
|
||||
|
||||
|
||||
private int useCount;
|
||||
|
@ -124,22 +129,10 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
this.maxResultSetSize = -1;
|
||||
}
|
||||
|
||||
public Client getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void setClient(Client client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public String getPreferredResourceFormat() {
|
||||
return preferredResourceFormat.getHeader();
|
||||
}
|
||||
|
||||
public void setPreferredResourceFormat(ResourceFormat resourceFormat) {
|
||||
preferredResourceFormat = resourceFormat;
|
||||
}
|
||||
|
||||
public int getMaximumRecordCount() {
|
||||
return maxResultSetSize;
|
||||
}
|
||||
|
@ -410,10 +403,14 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
}
|
||||
|
||||
/**
|
||||
* Helper method to prevent nesting of previously thrown EFhirClientExceptions
|
||||
* Helper method to prevent nesting of previously thrown EFhirClientExceptions. If the e param is an instance of
|
||||
* EFhirClientException, it will be rethrown. Otherwise, a new EFhirClientException will be thrown with e as the
|
||||
* cause.
|
||||
*
|
||||
* @param e
|
||||
* @throws EFhirClientException
|
||||
* @param code The EFhirClientException code.
|
||||
* @param message The EFhirClientException message.
|
||||
* @param e The exception.
|
||||
* @throws EFhirClientException representing the exception.
|
||||
*/
|
||||
protected void handleException(int code, String message, Exception e) throws EFhirClientException {
|
||||
if (e instanceof EFhirClientException) {
|
||||
|
@ -427,8 +424,8 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
* Helper method to determine whether desired resource representation
|
||||
* is Json or XML.
|
||||
*
|
||||
* @param format
|
||||
* @return
|
||||
* @param format The format
|
||||
* @return true if the format is JSON, false otherwise
|
||||
*/
|
||||
protected boolean isJson(String format) {
|
||||
boolean isJson = false;
|
||||
|
@ -573,22 +570,6 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
return result == null ? null : (ConceptMap) result.getPayload();
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public long getTimeout() {
|
||||
return client.getTimeout();
|
||||
}
|
||||
|
@ -613,51 +594,27 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
client.setRetryCount(retryCount);
|
||||
}
|
||||
|
||||
public void setClientHeaders(ArrayList<Header> headers) {
|
||||
this.headers = headers;
|
||||
public void setClientHeaders(Iterable<HTTPHeader> headers) {
|
||||
this.headers = new ArrayList<>();
|
||||
headers.forEach(this.headers::add);
|
||||
}
|
||||
|
||||
private Headers generateHeaders(boolean hasBody) {
|
||||
Headers.Builder builder = new Headers.Builder();
|
||||
// Add basic auth header if it exists
|
||||
if (basicAuthHeaderExists()) {
|
||||
builder.add(getAuthorizationHeader().toString());
|
||||
}
|
||||
private Iterable<HTTPHeader> generateHeaders(boolean hasBody) {
|
||||
// Add any other headers
|
||||
if(this.headers != null) {
|
||||
this.headers.forEach(header -> builder.add(header.toString()));
|
||||
}
|
||||
List<HTTPHeader> headers = new ArrayList<>(this.headers);
|
||||
if (!Utilities.noString(userAgent)) {
|
||||
builder.add("User-Agent: "+userAgent);
|
||||
headers.add(new HTTPHeader("User-Agent",userAgent));
|
||||
}
|
||||
|
||||
if (!Utilities.noString(acceptLang)) {
|
||||
builder.add("Accept-Language: "+acceptLang);
|
||||
if (!Utilities.noString(acceptLanguage)) {
|
||||
headers.add(new HTTPHeader("Accept-Language", acceptLanguage));
|
||||
}
|
||||
|
||||
if (hasBody && !Utilities.noString(contentLang)) {
|
||||
builder.add("Content-Language: "+contentLang);
|
||||
if (hasBody && !Utilities.noString(contentLanguage)) {
|
||||
headers.add(new HTTPHeader("Content-Language", contentLanguage));
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public boolean basicAuthHeaderExists() {
|
||||
return (username != null) && (password != null);
|
||||
}
|
||||
|
||||
public Header getAuthorizationHeader() {
|
||||
String usernamePassword = username + ":" + password;
|
||||
String base64usernamePassword = Base64.getEncoder().encodeToString(usernamePassword.getBytes());
|
||||
return new Header("Authorization", "Basic " + base64usernamePassword);
|
||||
}
|
||||
|
||||
public String getUserAgent() {
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
public void setUserAgent(String userAgent) {
|
||||
this.userAgent = userAgent;
|
||||
return headers;
|
||||
}
|
||||
|
||||
public String getServerVersion() {
|
||||
|
@ -671,14 +628,6 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
|
|||
return capabilities == null ? null : capabilities.getSoftware().getVersion();
|
||||
}
|
||||
|
||||
public void setAcceptLanguage(String lang) {
|
||||
this.acceptLang = lang;
|
||||
}
|
||||
|
||||
public void setContentLanguage(String lang) {
|
||||
this.contentLang = lang;
|
||||
}
|
||||
|
||||
public Bundle search(String type, String criteria) {
|
||||
recordUse();
|
||||
return fetchFeed(Utilities.pathURL(base, type+criteria));
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
package org.hl7.fhir.r5.utils.client.network;
|
||||
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import org.hl7.fhir.r5.model.Bundle;
|
||||
import org.hl7.fhir.r5.model.Resource;
|
||||
import org.hl7.fhir.r5.utils.client.EFhirClientException;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.hl7.fhir.utilities.http.HTTPRequest;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
@ -18,93 +20,60 @@ public class Client {
|
|||
|
||||
public static final String DEFAULT_CHARSET = "UTF-8";
|
||||
private static final long DEFAULT_TIMEOUT = 5000;
|
||||
@Getter @Setter
|
||||
private ToolingClientLogger logger;
|
||||
private FhirLoggingInterceptor fhirLoggingInterceptor;
|
||||
|
||||
@Setter @Getter
|
||||
private int retryCount;
|
||||
@Setter @Getter
|
||||
private long timeout = DEFAULT_TIMEOUT;
|
||||
private byte[] payload;
|
||||
|
||||
@Setter @Getter
|
||||
private String base;
|
||||
|
||||
public String getBase() {
|
||||
return base;
|
||||
}
|
||||
|
||||
public void setBase(String base) {
|
||||
this.base = base;
|
||||
}
|
||||
|
||||
public ToolingClientLogger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
public void setLogger(ToolingClientLogger logger) {
|
||||
this.logger = logger;
|
||||
this.fhirLoggingInterceptor = new FhirLoggingInterceptor(logger);
|
||||
}
|
||||
|
||||
public int getRetryCount() {
|
||||
return retryCount;
|
||||
}
|
||||
|
||||
public void setRetryCount(int retryCount) {
|
||||
this.retryCount = retryCount;
|
||||
}
|
||||
|
||||
public long getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public void setTimeout(long timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issueOptionsRequest(URI optionsUri,
|
||||
String resourceFormat,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
this.payload = null;
|
||||
Request.Builder request = new Request.Builder()
|
||||
.method("OPTIONS", null)
|
||||
.url(optionsUri.toURL());
|
||||
|
||||
return executeFhirRequest(request, resourceFormat, new Headers.Builder().build(), message, retryCount, timeout);
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(optionsUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.OPTIONS);
|
||||
return executeFhirRequest(request, resourceFormat, Collections.emptyList(), message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issueGetResourceRequest(URI resourceUri,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
Iterable<HTTPHeader> headers,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
this.payload = null;
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL());
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.GET);
|
||||
|
||||
return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public int tester(int trytry) {
|
||||
return 5;
|
||||
}
|
||||
public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri,
|
||||
byte[] payload,
|
||||
String resourceFormat,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
return issuePutRequest(resourceUri, payload, resourceFormat, new Headers.Builder().build(), message, timeout);
|
||||
return issuePutRequest(resourceUri, payload, resourceFormat, Collections.emptyList(), message, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri,
|
||||
byte[] payload,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
Iterable<HTTPHeader> headers,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
if (payload == null) throw new EFhirClientException(0, "PUT requests require a non-null payload");
|
||||
this.payload = payload;
|
||||
RequestBody body = RequestBody.create(payload);
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL())
|
||||
.put(body);
|
||||
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.PUT)
|
||||
.withBody(payload)
|
||||
.withContentType(getContentTypeWithDefaultCharset(resourceFormat));
|
||||
|
||||
return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout);
|
||||
}
|
||||
|
@ -114,37 +83,38 @@ public class Client {
|
|||
String resourceFormat,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
return issuePostRequest(resourceUri, payload, resourceFormat, new Headers.Builder().build(), message, timeout);
|
||||
return issuePostRequest(resourceUri, payload, resourceFormat, Collections.emptyList(), message, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri,
|
||||
byte[] payload,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
Iterable<HTTPHeader> headers,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
if (payload == null) throw new EFhirClientException(0, "POST requests require a non-null payload");
|
||||
this.payload = payload;
|
||||
RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL())
|
||||
.post(body);
|
||||
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.POST)
|
||||
.withBody(payload)
|
||||
.withContentType(getContentTypeWithDefaultCharset(resourceFormat));
|
||||
|
||||
return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public boolean issueDeleteRequest(URI resourceUri) throws IOException {
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL())
|
||||
.delete();
|
||||
return executeFhirRequest(request, null, new Headers.Builder().build(), null, retryCount, timeout).isSuccessfulRequest();
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.DELETE);
|
||||
return executeFhirRequest(request, null, Collections.emptyList(), null, retryCount, timeout).isSuccessfulRequest();
|
||||
}
|
||||
|
||||
public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat) throws IOException {
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL());
|
||||
|
||||
return executeBundleRequest(request, resourceFormat, new Headers.Builder().build(), null, retryCount, timeout);
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.GET);
|
||||
return executeBundleRequest(request, resourceFormat, Collections.emptyList(), null, retryCount, timeout);
|
||||
}
|
||||
|
||||
public Bundle issuePostFeedRequest(URI resourceUri,
|
||||
|
@ -154,57 +124,63 @@ public class Client {
|
|||
String resourceFormat) throws IOException {
|
||||
String boundary = "----WebKitFormBoundarykbMUo6H8QaUnYtRy";
|
||||
byte[] payload = ByteUtils.encodeFormSubmission(parameters, resourceName, resource, boundary);
|
||||
RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL())
|
||||
.post(body);
|
||||
|
||||
return executeBundleRequest(request, resourceFormat, new Headers.Builder().build(), null, retryCount, timeout);
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.POST)
|
||||
.withBody(payload)
|
||||
.withContentType(getContentTypeWithDefaultCharset(resourceFormat));
|
||||
return executeBundleRequest(request, resourceFormat, Collections.emptyList(), null, retryCount, timeout);
|
||||
}
|
||||
|
||||
public Bundle postBatchRequest(URI resourceUri,
|
||||
byte[] payload,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
Iterable<HTTPHeader> headers,
|
||||
String message,
|
||||
int timeout) throws IOException {
|
||||
if (payload == null) throw new EFhirClientException(0, "POST requests require a non-null payload");
|
||||
RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL())
|
||||
.post(body);
|
||||
|
||||
HTTPRequest request = new HTTPRequest()
|
||||
.withUrl(resourceUri.toURL())
|
||||
.withMethod(HTTPRequest.HttpMethod.POST)
|
||||
.withBody(payload)
|
||||
.withContentType(getContentTypeWithDefaultCharset(resourceFormat));
|
||||
return executeBundleRequest(request, resourceFormat, headers, message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> Bundle executeBundleRequest(Request.Builder request,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
String message,
|
||||
int retryCount,
|
||||
long timeout) throws IOException {
|
||||
private static String getContentTypeWithDefaultCharset(String resourceFormat) {
|
||||
return resourceFormat + ";charset=" + DEFAULT_CHARSET;
|
||||
}
|
||||
|
||||
public <T extends Resource> Bundle executeBundleRequest(HTTPRequest request,
|
||||
String resourceFormat,
|
||||
Iterable<HTTPHeader> headers,
|
||||
String message,
|
||||
int retryCount,
|
||||
long timeout) throws IOException {
|
||||
return new FhirRequestBuilder(request, base)
|
||||
.withLogger(fhirLoggingInterceptor)
|
||||
.withLogger(logger)
|
||||
.withResourceFormat(resourceFormat)
|
||||
.withRetryCount(retryCount)
|
||||
.withMessage(message)
|
||||
.withHeaders(headers == null ? new Headers.Builder().build() : headers)
|
||||
.withHeaders(headers == null ? Collections.emptyList() : headers)
|
||||
.withTimeout(timeout, TimeUnit.MILLISECONDS)
|
||||
.executeAsBatch();
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> executeFhirRequest(Request.Builder request,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
String message,
|
||||
int retryCount,
|
||||
long timeout) throws IOException {
|
||||
public <T extends Resource> ResourceRequest<T> executeFhirRequest(HTTPRequest request,
|
||||
String resourceFormat,
|
||||
Iterable<HTTPHeader> headers,
|
||||
String message,
|
||||
int retryCount,
|
||||
long timeout) throws IOException {
|
||||
return new FhirRequestBuilder(request, base)
|
||||
.withLogger(fhirLoggingInterceptor)
|
||||
.withLogger(logger)
|
||||
.withResourceFormat(resourceFormat)
|
||||
.withRetryCount(retryCount)
|
||||
.withMessage(message)
|
||||
.withHeaders(headers == null ? new Headers.Builder().build() : headers)
|
||||
.withHeaders(headers == null ? Collections.emptyList() : headers)
|
||||
.withTimeout(timeout, TimeUnit.MILLISECONDS)
|
||||
.execute();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
package org.hl7.fhir.r5.utils.client.network;
|
||||
|
||||
import okhttp3.internal.http2.Header;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -15,30 +16,31 @@ import java.util.stream.Collectors;
|
|||
*/
|
||||
public class ClientHeaders {
|
||||
|
||||
private final ArrayList<Header> headers;
|
||||
private final List<HTTPHeader> headers;
|
||||
|
||||
public ClientHeaders() {
|
||||
this.headers = new ArrayList<>();
|
||||
}
|
||||
|
||||
public ClientHeaders(ArrayList<Header> headers) {
|
||||
this.headers = headers;
|
||||
public ClientHeaders(List<HTTPHeader> headers) {
|
||||
|
||||
this.headers = new ArrayList<>(headers);
|
||||
}
|
||||
|
||||
public ArrayList<Header> headers() {
|
||||
public List<HTTPHeader> headers() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a header to the list of stored headers for network operations.
|
||||
*
|
||||
* @param header {@link Header} to add to the list.
|
||||
* @param header {@link HTTPHeader} to add to the list.
|
||||
* @throws FHIRException if the header being added is a duplicate.
|
||||
*/
|
||||
public ClientHeaders addHeader(Header header) throws FHIRException {
|
||||
public ClientHeaders addHeader(HTTPHeader header) throws FHIRException {
|
||||
if (headers.contains(header)) {
|
||||
throw new FHIRException("Attempting to add duplicate header, <" + header.name + ", "
|
||||
+ header.value + ">.");
|
||||
throw new FHIRException("Attempting to add duplicate header, <" + header.getName() + ", "
|
||||
+ header.getValue() + ">.");
|
||||
}
|
||||
headers.add(header);
|
||||
return this;
|
||||
|
@ -47,39 +49,39 @@ public class ClientHeaders {
|
|||
/**
|
||||
* Add a header to the list of stored headers for network operations.
|
||||
*
|
||||
* @param headerList {@link List} of {@link Header} to add.
|
||||
* @param headerList {@link List} of {@link HTTPHeader} to add.
|
||||
* @throws FHIRException if any of the headers being added is a duplicate.
|
||||
*/
|
||||
public ClientHeaders addHeaders(List<Header> headerList) throws FHIRException {
|
||||
public ClientHeaders addHeaders(List<HTTPHeader> headerList) throws FHIRException {
|
||||
headerList.forEach(this::addHeader);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the passed in header from the list of stored headers.
|
||||
* @param header {@link Header} to remove from the list.
|
||||
* @param header {@link HTTPHeader} to remove from the list.
|
||||
* @throws FHIRException if the header passed in does not exist within the stored list.
|
||||
*/
|
||||
public ClientHeaders removeHeader(Header header) throws FHIRException {
|
||||
public ClientHeaders removeHeader(HTTPHeader header) throws FHIRException {
|
||||
if (!headers.remove(header)) {
|
||||
throw new FHIRException("Attempting to remove header, <" + header.name + ", "
|
||||
+ header.value + ">, from GenericClientHeaders that is not currently stored.");
|
||||
throw new FHIRException("Attempting to remove header, <" + header.getName() + ", "
|
||||
+ header.getValue() + ">, from GenericClientHeaders that is not currently stored.");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the passed in headers from the list of stored headers.
|
||||
* @param headerList {@link List} of {@link Header} to remove.
|
||||
* @param headerList {@link List} of {@link HTTPHeader} to remove.
|
||||
* @throws FHIRException if any of the headers passed in does not exist within the stored list.
|
||||
*/
|
||||
public ClientHeaders removeHeaders(List<Header> headerList) throws FHIRException {
|
||||
public ClientHeaders removeHeaders(List<HTTPHeader> headerList) throws FHIRException {
|
||||
headerList.forEach(this::removeHeader);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all stored {@link Header}.
|
||||
* Clears all stored {@link HTTPHeader}.
|
||||
*/
|
||||
public ClientHeaders clearHeaders() {
|
||||
headers.clear();
|
||||
|
@ -89,7 +91,7 @@ public class ClientHeaders {
|
|||
@Override
|
||||
public String toString() {
|
||||
return this.headers.stream()
|
||||
.map(header -> "\t" + header.name + ":" + header.value)
|
||||
.map(header -> "\t" + header.getName() + ":" + header.getValue())
|
||||
.collect(Collectors.joining(",\n", "{\n", "\n}"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
package org.hl7.fhir.r5.utils.client.network;
|
||||
|
||||
import okhttp3.*;
|
||||
import okio.Buffer;
|
||||
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class FhirLoggingInterceptor implements Interceptor {
|
||||
|
||||
private ToolingClientLogger logger;
|
||||
|
||||
public FhirLoggingInterceptor(ToolingClientLogger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public FhirLoggingInterceptor setLogger(ToolingClientLogger logger) {
|
||||
this.logger = logger;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response intercept(@Nonnull Interceptor.Chain chain) throws IOException {
|
||||
// Log Request
|
||||
Request request = chain.request();
|
||||
List<String> hdrs = new ArrayList<>();
|
||||
for (String s : request.headers().toString().split("\\n")) {
|
||||
hdrs.add(s.trim());
|
||||
}
|
||||
byte[] cnt = null;
|
||||
if (request.body() != null) {
|
||||
Buffer buf = new Buffer();
|
||||
request.body().writeTo(buf);
|
||||
cnt = buf.readByteArray();
|
||||
}
|
||||
if (logger != null) {
|
||||
logger.logRequest(request.method(), request.url().toString(), hdrs, cnt);
|
||||
}
|
||||
|
||||
// Log Response
|
||||
Response response = null;
|
||||
response = chain.proceed(chain.request());
|
||||
|
||||
MediaType contentType = null;
|
||||
byte[] bodyBytes = null;
|
||||
if (response.body() != null) {
|
||||
contentType = response.body().contentType();
|
||||
bodyBytes = response.body().bytes();
|
||||
}
|
||||
|
||||
// Get Headers as List
|
||||
List<String> headerList = new ArrayList<>();
|
||||
Map<String, List<String>> headerMap = response.headers().toMultimap();
|
||||
headerMap.keySet().forEach(key -> headerMap.get(key).forEach(value -> headerList.add(key + ":" + value)));
|
||||
|
||||
if (logger != null) {
|
||||
long responseTimeInMillis = response.receivedResponseAtMillis() - response.sentRequestAtMillis();
|
||||
logger.logResponse(Integer.toString(response.code()), headerList, bodyBytes, responseTimeInMillis);
|
||||
}
|
||||
|
||||
// Reading byte[] clears body. Need to recreate.
|
||||
ResponseBody body = ResponseBody.create(bodyBytes, contentType);
|
||||
return response.newBuilder().body(body).build();
|
||||
}
|
||||
}
|
|
@ -1,14 +1,12 @@
|
|||
package org.hl7.fhir.r5.utils.client.network;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.formats.IParser;
|
||||
import org.hl7.fhir.r5.formats.JsonParser;
|
||||
import org.hl7.fhir.r5.formats.XmlParser;
|
||||
|
@ -20,32 +18,20 @@ import org.hl7.fhir.r5.utils.ResourceUtilities;
|
|||
import org.hl7.fhir.r5.utils.client.EFhirClientException;
|
||||
import org.hl7.fhir.r5.utils.client.ResourceFormat;
|
||||
import org.hl7.fhir.utilities.MimeType;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.settings.FhirSettings;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.hl7.fhir.utilities.http.*;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlUtils;
|
||||
|
||||
import okhttp3.Authenticator;
|
||||
import okhttp3.Credentials;
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class FhirRequestBuilder {
|
||||
|
||||
protected static final String HTTP_PROXY_USER = "http.proxyUser";
|
||||
protected static final String HTTP_PROXY_PASS = "http.proxyPassword";
|
||||
protected static final String HEADER_PROXY_AUTH = "Proxy-Authorization";
|
||||
protected static final String DEFAULT_CHARSET = "UTF-8";
|
||||
|
||||
protected static final String LOCATION_HEADER = "location";
|
||||
protected static final String CONTENT_LOCATION_HEADER = "content-location";
|
||||
protected static final String DEFAULT_CHARSET = "UTF-8";
|
||||
/**
|
||||
* The singleton instance of the HttpClient, used for all requests.
|
||||
*/
|
||||
private static OkHttpClient okHttpClient;
|
||||
private final Request.Builder httpRequest;
|
||||
|
||||
private final HTTPRequest httpRequest;
|
||||
private String resourceFormat = null;
|
||||
private Headers headers = null;
|
||||
private Iterable<HTTPHeader> headers = null;
|
||||
private String message = null;
|
||||
private int retryCount = 1;
|
||||
/**
|
||||
|
@ -58,69 +44,57 @@ public class FhirRequestBuilder {
|
|||
private TimeUnit timeoutUnit = TimeUnit.MILLISECONDS;
|
||||
|
||||
/**
|
||||
* {@link FhirLoggingInterceptor} for log output.
|
||||
* {@link ToolingClientLogger} for log output.
|
||||
*/
|
||||
private FhirLoggingInterceptor logger = null;
|
||||
private ToolingClientLogger logger = null;
|
||||
|
||||
private String source;
|
||||
|
||||
public FhirRequestBuilder(Request.Builder httpRequest, String source) {
|
||||
this.httpRequest = httpRequest;
|
||||
public FhirRequestBuilder(HTTPRequest httpRequest, String source) {
|
||||
this.source = source;
|
||||
this.httpRequest = httpRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds necessary default headers, formatting headers, and any passed in {@link Headers} to the passed in
|
||||
* {@link okhttp3.Request.Builder}
|
||||
* Adds necessary default headers, formatting headers, and any passed in {@link HTTPHeader}s to the passed in
|
||||
* {@link HTTPRequest}
|
||||
*
|
||||
* @param request {@link okhttp3.Request.Builder} to add headers to.
|
||||
* @param request {@link HTTPRequest} to add headers to.
|
||||
* @param format Expected {@link Resource} format.
|
||||
* @param headers Any additional {@link Headers} to add to the request.
|
||||
* @param headers Any additional {@link HTTPHeader}s to add to the request.
|
||||
*/
|
||||
protected static void formatHeaders(Request.Builder request, String format, Headers headers) {
|
||||
addDefaultHeaders(request, headers);
|
||||
if (format != null) addResourceFormatHeaders(request, format);
|
||||
if (headers != null) addHeaders(request, headers);
|
||||
}
|
||||
protected static HTTPRequest formatHeaders(HTTPRequest request, String format, Iterable<HTTPHeader> headers) {
|
||||
List<HTTPHeader> allHeaders = new ArrayList<>();
|
||||
request.getHeaders().forEach(allHeaders::add);
|
||||
|
||||
/**
|
||||
* Adds necessary headers for all REST requests.
|
||||
* <li>User-Agent : hapi-fhir-tooling-client</li>
|
||||
*
|
||||
* @param request {@link Request.Builder} to add default headers to.
|
||||
*/
|
||||
protected static void addDefaultHeaders(Request.Builder request, Headers headers) {
|
||||
if (headers == null || !headers.names().contains("User-Agent")) {
|
||||
request.addHeader("User-Agent", "hapi-fhir-tooling-client");
|
||||
}
|
||||
if (format != null) getResourceFormatHeaders(request, format).forEach(allHeaders::add);
|
||||
if (headers != null) headers.forEach(allHeaders::add);
|
||||
return request.withHeaders(allHeaders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds necessary headers for the given resource format provided.
|
||||
*
|
||||
* @param request {@link Request.Builder} to add default headers to.
|
||||
* @param httpRequest {@link HTTPRequest} to add default headers to.
|
||||
* @param format Expected {@link Resource} format.
|
||||
*/
|
||||
protected static void addResourceFormatHeaders(Request.Builder request, String format) {
|
||||
request.addHeader("Accept", format);
|
||||
if (Utilities.existsInList(request.getMethod$okhttp(), "POST", "PUT")) {
|
||||
request.addHeader("Content-Type", format + ";charset=" + DEFAULT_CHARSET);
|
||||
protected static Iterable<HTTPHeader> getResourceFormatHeaders(HTTPRequest httpRequest, String format) {
|
||||
List<HTTPHeader> headers = new ArrayList<>();
|
||||
headers.add(new HTTPHeader("Accept", format));
|
||||
if (httpRequest.getMethod() == HTTPRequest.HttpMethod.PUT
|
||||
|| httpRequest.getMethod() == HTTPRequest.HttpMethod.POST
|
||||
|| httpRequest.getMethod() == HTTPRequest.HttpMethod.PATCH
|
||||
) {
|
||||
headers.add( new HTTPHeader("Content-Type", format + ";charset=" + DEFAULT_CHARSET));
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through the passed in {@link Headers} and adds them to the provided {@link Request.Builder}.
|
||||
*
|
||||
* @param request {@link Request.Builder} to add headers to.
|
||||
* @param headers {@link Headers} to add to request.
|
||||
*/
|
||||
protected static void addHeaders(Request.Builder request, Headers headers) {
|
||||
headers.forEach(header -> request.addHeader(header.getFirst(), header.getSecond()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if any of the {@link org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent} within the
|
||||
* provided {@link OperationOutcome} have an {@link org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity} of
|
||||
* {@link org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity#ERROR} or
|
||||
* {@link org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity#FATAL}
|
||||
* Returns true if any of the {@link OperationOutcome.OperationOutcomeIssueComponent} within the
|
||||
* provided {@link OperationOutcome} have an {@link OperationOutcome.IssueSeverity} of
|
||||
* {@link OperationOutcome.IssueSeverity#ERROR} or
|
||||
* {@link OperationOutcome.IssueSeverity#FATAL}
|
||||
*
|
||||
* @param oo {@link OperationOutcome} to evaluate.
|
||||
* @return {@link Boolean#TRUE} if an error exists.
|
||||
|
@ -131,71 +105,8 @@ public class FhirRequestBuilder {
|
|||
|| issue.getSeverity() == OperationOutcome.IssueSeverity.FATAL));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the 'location' header from the passes in {@link Headers}. If no value for 'location' exists, the
|
||||
* value for 'content-location' is returned. If neither header exists, we return null.
|
||||
*
|
||||
* @param headers {@link Headers} to evaluate
|
||||
* @return {@link String} header value, or null if no location headers are set.
|
||||
*/
|
||||
protected static String getLocationHeader(Headers headers) {
|
||||
Map<String, List<String>> headerMap = headers.toMultimap();
|
||||
if (headerMap.containsKey(LOCATION_HEADER)) {
|
||||
return headerMap.get(LOCATION_HEADER).get(0);
|
||||
} else if (headerMap.containsKey(CONTENT_LOCATION_HEADER)) {
|
||||
return headerMap.get(CONTENT_LOCATION_HEADER).get(0);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We only ever want to have one copy of the HttpClient kicking around at any given time. If we need to make changes
|
||||
* to any configuration, such as proxy settings, timeout, caches, etc, we can do a per-call configuration through
|
||||
* the {@link OkHttpClient#newBuilder()} method. That will return a builder that shares the same connection pool,
|
||||
* dispatcher, and configuration with the original client.
|
||||
* </p>
|
||||
* The {@link OkHttpClient} uses the proxy auth properties set in the current system properties. The reason we don't
|
||||
* set the proxy address and authentication explicitly, is due to the fact that this class is often used in conjunction
|
||||
* with other http client tools which rely on the system.properties settings to determine proxy settings. It's easier
|
||||
* to keep the method consistent across the board. ...for now.
|
||||
*
|
||||
* @return {@link OkHttpClient} instance
|
||||
*/
|
||||
protected OkHttpClient getHttpClient() {
|
||||
if (FhirSettings.isProhibitNetworkAccess()) {
|
||||
throw new FHIRException("Network Access is prohibited in this context");
|
||||
}
|
||||
|
||||
if (okHttpClient == null) {
|
||||
okHttpClient = new OkHttpClient();
|
||||
}
|
||||
|
||||
Authenticator proxyAuthenticator = getAuthenticator();
|
||||
|
||||
OkHttpClient.Builder builder = okHttpClient.newBuilder();
|
||||
if (logger != null) builder.addInterceptor(logger);
|
||||
builder.addInterceptor(new RetryInterceptor(retryCount));
|
||||
return builder.connectTimeout(timeout, timeoutUnit)
|
||||
.writeTimeout(timeout, timeoutUnit)
|
||||
.readTimeout(timeout, timeoutUnit)
|
||||
.proxyAuthenticator(proxyAuthenticator)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private static Authenticator getAuthenticator() {
|
||||
return (route, response) -> {
|
||||
final String httpProxyUser = System.getProperty(HTTP_PROXY_USER);
|
||||
final String httpProxyPass = System.getProperty(HTTP_PROXY_PASS);
|
||||
if (httpProxyUser != null && httpProxyPass != null) {
|
||||
String credential = Credentials.basic(httpProxyUser, httpProxyPass);
|
||||
return response.request().newBuilder()
|
||||
.header(HEADER_PROXY_AUTH, credential)
|
||||
.build();
|
||||
}
|
||||
return response.request().newBuilder().build();
|
||||
};
|
||||
protected ManagedFhirWebAccessor getManagedWebAccessor() {
|
||||
return ManagedWebAccess.fhirAccessor().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger);
|
||||
}
|
||||
|
||||
public FhirRequestBuilder withResourceFormat(String resourceFormat) {
|
||||
|
@ -203,7 +114,7 @@ public class FhirRequestBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public FhirRequestBuilder withHeaders(Headers headers) {
|
||||
public FhirRequestBuilder withHeaders(Iterable<HTTPHeader> headers) {
|
||||
this.headers = headers;
|
||||
return this;
|
||||
}
|
||||
|
@ -218,7 +129,7 @@ public class FhirRequestBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public FhirRequestBuilder withLogger(FhirLoggingInterceptor logger) {
|
||||
public FhirRequestBuilder withLogger(ToolingClientLogger logger) {
|
||||
this.logger = logger;
|
||||
return this;
|
||||
}
|
||||
|
@ -229,20 +140,16 @@ public class FhirRequestBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
protected Request buildRequest() {
|
||||
return httpRequest.build();
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> execute() throws IOException {
|
||||
formatHeaders(httpRequest, resourceFormat, headers);
|
||||
Response response = getHttpClient().newCall(httpRequest.build()).execute();
|
||||
HTTPRequest requestWithHeaders = formatHeaders(httpRequest, resourceFormat, headers);
|
||||
HTTPResult response = getManagedWebAccessor().httpCall(requestWithHeaders);//getHttpClient().newCall(httpRequest.build()).execute();
|
||||
T resource = unmarshalReference(response, resourceFormat, null);
|
||||
return new ResourceRequest<T>(resource, response.code(), getLocationHeader(response.headers()));
|
||||
return new ResourceRequest<T>(resource, response.getCode(), getLocationHeader(response.getHeaders()));
|
||||
}
|
||||
|
||||
public Bundle executeAsBatch() throws IOException {
|
||||
formatHeaders(httpRequest, resourceFormat, null);
|
||||
Response response = getHttpClient().newCall(httpRequest.build()).execute();
|
||||
HTTPRequest requestWithHeaders = formatHeaders(httpRequest, resourceFormat, null);
|
||||
HTTPResult response = getManagedWebAccessor().httpCall(requestWithHeaders);
|
||||
return unmarshalFeed(response, resourceFormat);
|
||||
}
|
||||
|
||||
|
@ -250,12 +157,12 @@ public class FhirRequestBuilder {
|
|||
* Unmarshalls a resource from the response stream.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T extends Resource> T unmarshalReference(Response response, String format, String resourceType) {
|
||||
int code = response.code();
|
||||
protected <T extends Resource> T unmarshalReference(HTTPResult response, String format, String resourceType) {
|
||||
int code = response.getCode();
|
||||
boolean ok = code >= 200 && code < 300;
|
||||
if (response.body() == null) {
|
||||
if (response.getContent() == null) {
|
||||
if (!ok) {
|
||||
throw new EFhirClientException(code, response.message());
|
||||
throw new EFhirClientException(code, response.getMessage());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@ -264,9 +171,9 @@ public class FhirRequestBuilder {
|
|||
|
||||
Resource resource = null;
|
||||
try {
|
||||
body = response.body().string();
|
||||
String ct = response.header("Content-Type");
|
||||
if (ct == null) {
|
||||
body = response.getContentAsString();
|
||||
String contentType = HTTPHeaderUtil.getSingleHeader(response.getHeaders(), "Content-Type");
|
||||
if (contentType == null) {
|
||||
if (ok) {
|
||||
resource = getParser(format).parse(body);
|
||||
} else {
|
||||
|
@ -275,10 +182,10 @@ public class FhirRequestBuilder {
|
|||
resource = OperationOutcomeUtilities.outcomeFromTextError(body);
|
||||
}
|
||||
} else {
|
||||
if (ct.contains(";")) {
|
||||
ct = ct.substring(0, ct.indexOf(";"));
|
||||
if (contentType.contains(";")) {
|
||||
contentType = contentType.substring(0, contentType.indexOf(";"));
|
||||
}
|
||||
switch (ct) {
|
||||
switch (contentType) {
|
||||
case "application/json":
|
||||
case "application/fhir+json":
|
||||
if (!format.contains("json")) {
|
||||
|
@ -298,10 +205,10 @@ public class FhirRequestBuilder {
|
|||
resource = OperationOutcomeUtilities.outcomeFromTextError(body);
|
||||
break;
|
||||
case "text/html" :
|
||||
resource = OperationOutcomeUtilities.outcomeFromTextError(XhtmlUtils.convertHtmlToText(response.body().string(), source));
|
||||
resource = OperationOutcomeUtilities.outcomeFromTextError(XhtmlUtils.convertHtmlToText(response.getContentAsString(), source));
|
||||
break;
|
||||
default: // not sure what else to do?
|
||||
System.out.println("Got content-type '"+ct+"' from "+source);
|
||||
System.out.println("Got content-type '"+contentType+"' from "+source);
|
||||
System.out.println(body);
|
||||
resource = OperationOutcomeUtilities.outcomeFromTextError(body);
|
||||
}
|
||||
|
@ -336,14 +243,14 @@ public class FhirRequestBuilder {
|
|||
/**
|
||||
* Unmarshalls Bundle from response stream.
|
||||
*/
|
||||
protected Bundle unmarshalFeed(Response response, String format) {
|
||||
protected Bundle unmarshalFeed(HTTPResult response, String format) {
|
||||
return unmarshalReference(response, format, "Bundle");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the appropriate parser based on the format type passed in. Defaults to XML parser if a blank format is
|
||||
* provided...because reasons.
|
||||
* <p>
|
||||
* <p/>
|
||||
* Currently supports only "json" and "xml" formats.
|
||||
*
|
||||
* @param format One of "json" or "xml".
|
||||
|
@ -362,4 +269,19 @@ public class FhirRequestBuilder {
|
|||
throw new EFhirClientException(0, "Invalid format: " + format);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the 'location' header from the passed headers. If no value for 'location' exists, the
|
||||
* value for 'content-location' is returned. If neither header exists, we return null.
|
||||
*
|
||||
* @param headers Headers to search for 'location' or 'content-location'.
|
||||
*/
|
||||
protected static String getLocationHeader(Iterable<HTTPHeader> headers) {
|
||||
String locationHeader = HTTPHeaderUtil.getSingleHeader(headers, LOCATION_HEADER);
|
||||
|
||||
if (locationHeader != null) {
|
||||
return locationHeader;
|
||||
}
|
||||
return HTTPHeaderUtil.getSingleHeader(headers, CONTENT_LOCATION_HEADER);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
package org.hl7.fhir.r5.utils.client.network;
|
||||
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* An {@link Interceptor} for {@link okhttp3.OkHttpClient} that controls the number of times we retry a to execute a
|
||||
* given request, before reporting a failure. This includes unsuccessful return codes and timeouts.
|
||||
*/
|
||||
public class RetryInterceptor implements Interceptor {
|
||||
|
||||
// Delay between retying failed requests, in millis
|
||||
private final long RETRY_TIME = 2000;
|
||||
|
||||
// Maximum number of times to retry the request before failing
|
||||
private final int maxRetry;
|
||||
|
||||
// Internal counter for tracking the number of times we've tried this request
|
||||
private int retryCounter = 0;
|
||||
|
||||
public RetryInterceptor(int maxRetry) {
|
||||
this.maxRetry = maxRetry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response intercept(Interceptor.Chain chain) throws IOException {
|
||||
Request request = chain.request();
|
||||
Response response = null;
|
||||
|
||||
do {
|
||||
try {
|
||||
// If we are retrying a failed request that failed due to a bad response from the server, we must close it first
|
||||
if (response != null) {
|
||||
// System.out.println("Previous " + chain.request().method() + " attempt returned HTTP<" + (response.code())
|
||||
// + "> from url -> " + chain.request().url() + ".");
|
||||
response.close();
|
||||
}
|
||||
// System.out.println(chain.request().method() + " attempt <" + (retryCounter + 1) + "> to url -> " + chain.request().url());
|
||||
response = chain.proceed(request);
|
||||
} catch (IOException e) {
|
||||
try {
|
||||
// Include a small break in between requests.
|
||||
Thread.sleep(RETRY_TIME);
|
||||
} catch (InterruptedException e1) {
|
||||
System.out.println(chain.request().method() + " to url -> " + chain.request().url() + " interrupted on try <" + retryCounter + ">");
|
||||
}
|
||||
} finally {
|
||||
retryCounter++;
|
||||
}
|
||||
} while ((response == null || !response.isSuccessful()) && (retryCounter <= maxRetry + 1));
|
||||
|
||||
/*
|
||||
* if something has gone wrong, and we are unable to complete the request, we still need to initialize the return
|
||||
* response so we don't get a null pointer exception.
|
||||
*/
|
||||
return response != null ? response : chain.proceed(request);
|
||||
}
|
||||
|
||||
}
|
|
@ -5,6 +5,7 @@ import java.net.URI;
|
|||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.exceptions.FHIRFormatError;
|
||||
|
@ -22,17 +23,14 @@ import org.hl7.fhir.r5.model.TerminologyCapabilities;
|
|||
import org.hl7.fhir.r5.utils.client.network.Client;
|
||||
import org.hl7.fhir.r5.utils.client.network.ResourceRequest;
|
||||
|
||||
import org.hl7.fhir.utilities.http.HTTPRequest;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeader;
|
||||
import org.hl7.fhir.utilities.settings.FhirSettings;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.*;
|
||||
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.internal.http2.Header;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.times;
|
||||
|
@ -41,46 +39,56 @@ class FHIRToolingClientTest {
|
|||
|
||||
String TX_ADDR = FhirSettings.getTxFhirDevelopment();
|
||||
|
||||
Header h1 = new Header("header1", "value1");
|
||||
Header h2 = new Header("header2", "value2");
|
||||
Header h3 = new Header("header3", "value3");
|
||||
HTTPHeader h1 = new HTTPHeader("header1", "value1");
|
||||
HTTPHeader h2 = new HTTPHeader("header2", "value2");
|
||||
HTTPHeader h3 = new HTTPHeader("header3", "value3");
|
||||
|
||||
HTTPHeader agentHeader = new HTTPHeader("User-Agent", "fhir/test-cases");
|
||||
|
||||
private Client mockClient;
|
||||
private FHIRToolingClient toolingClient;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<Iterable<HTTPHeader>> headersArgumentCaptor;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException, URISyntaxException {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
mockClient = Mockito.mock(Client.class);
|
||||
ResourceRequest<Resource> resourceResourceRequest = new ResourceRequest<>(generateBundle(), 200, "");
|
||||
|
||||
//GET
|
||||
Mockito.when(mockClient.issueGetResourceRequest(Mockito.any(URI.class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), Mockito.anyString(), Mockito.anyLong()))
|
||||
Mockito.any(Iterable.class), Mockito.anyString(), Mockito.anyLong()))
|
||||
.thenReturn(resourceResourceRequest);
|
||||
|
||||
//PUT
|
||||
Mockito.when(mockClient.issuePutRequest(Mockito.any(URI.class), Mockito.any(byte[].class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), Mockito.anyString(), Mockito.anyLong()))
|
||||
ArgumentMatchers.any(), Mockito.anyString(), Mockito.anyLong()))
|
||||
.thenReturn(resourceResourceRequest);
|
||||
//POST
|
||||
Mockito.when(mockClient.issuePostRequest(Mockito.any(URI.class), Mockito.any(byte[].class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), Mockito.anyString(), Mockito.anyLong()))
|
||||
ArgumentMatchers.any(), Mockito.anyString(), Mockito.anyLong()))
|
||||
.thenReturn(resourceResourceRequest);
|
||||
Mockito.when(mockClient.issuePostRequest(Mockito.any(URI.class), Mockito.any(byte[].class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), Mockito.contains("validate"), Mockito.anyLong()))
|
||||
ArgumentMatchers.any(), Mockito.contains("validate"), Mockito.anyLong()))
|
||||
.thenReturn(new ResourceRequest<>(new OperationOutcome(), 200, "location"));
|
||||
//BUNDLE REQ
|
||||
Mockito.when(mockClient.executeBundleRequest(Mockito.any(Request.Builder.class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), Mockito.anyString(), Mockito.anyInt(), Mockito.anyLong()))
|
||||
Mockito.when(mockClient.executeBundleRequest(Mockito.any(HTTPRequest.class), Mockito.anyString(),
|
||||
ArgumentMatchers.any(), Mockito.anyString(), Mockito.anyInt(), Mockito.anyLong()))
|
||||
.thenReturn(generateBundle());
|
||||
toolingClient = new FHIRToolingClient(TX_ADDR, "fhir/test-cases");
|
||||
toolingClient.setClient(mockClient);
|
||||
}
|
||||
|
||||
private ArrayList<Header> getHeaders() {
|
||||
private List<HTTPHeader> getHeaders() {
|
||||
return new ArrayList<>(Arrays.asList(h1, h2, h3));
|
||||
}
|
||||
|
||||
private List<HTTPHeader> getHeadersWithAgent() {
|
||||
return new ArrayList<>(Arrays.asList(h1, h2, h3, agentHeader));
|
||||
}
|
||||
|
||||
private Bundle generateBundle() {
|
||||
Patient patient = generatePatient();
|
||||
Observation observation = generateObservation();
|
||||
|
@ -140,36 +148,37 @@ class FHIRToolingClientTest {
|
|||
return observation;
|
||||
}
|
||||
|
||||
private void checkHeaders(Headers argumentCaptorValue) {
|
||||
getHeaders().forEach(header -> {
|
||||
// System.out.println("Checking header <" + header.component1().utf8() + ", " + header.component2().utf8() + ">");
|
||||
Assertions.assertEquals(argumentCaptorValue.get(header.component1().utf8()), header.component2().utf8());
|
||||
private void checkHeaders(Iterable<HTTPHeader> argumentCaptorValue) {
|
||||
List<HTTPHeader> capturedHeaders = new ArrayList<>();
|
||||
argumentCaptorValue.forEach(capturedHeaders::add);
|
||||
|
||||
getHeadersWithAgent().forEach(header -> {
|
||||
assertTrue(capturedHeaders.contains(header));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTerminologyCapabilities() throws IOException {
|
||||
|
||||
Mockito.when(mockClient.issueGetResourceRequest(Mockito.any(URI.class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), Mockito.eq("TerminologyCapabilities"), Mockito.anyLong()))
|
||||
ArgumentMatchers.any(), Mockito.eq("TerminologyCapabilities"), Mockito.anyLong()))
|
||||
.thenReturn(new ResourceRequest<>(new TerminologyCapabilities(), 200, "location"));
|
||||
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.getTerminologyCapabilities();
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTerminologyCapabilitiesNotSupported() throws IOException {
|
||||
Mockito.when(mockClient.issueGetResourceRequest(Mockito.any(URI.class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), Mockito.eq("TerminologyCapabilities"), Mockito.anyLong()))
|
||||
ArgumentMatchers.any(), Mockito.eq("TerminologyCapabilities"), Mockito.anyLong()))
|
||||
.thenReturn(new ResourceRequest<>(new CapabilityStatement(), 200, "location"));
|
||||
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
Exception exception = assertThrows(FHIRException.class, () -> {
|
||||
toolingClient.getTerminologyCapabilities();
|
||||
|
@ -180,24 +189,23 @@ class FHIRToolingClientTest {
|
|||
@Test
|
||||
void getTerminologyCapabilitiesFailsForJSON() throws IOException {
|
||||
Mockito.when(mockClient.issueGetResourceRequest(Mockito.any(URI.class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), Mockito.eq("TerminologyCapabilities"), Mockito.anyLong()))
|
||||
ArgumentMatchers.any(), Mockito.eq("TerminologyCapabilities"), Mockito.anyLong()))
|
||||
.thenThrow(new FHIRFormatError("dummy error"))
|
||||
.thenReturn(new ResourceRequest<>(new TerminologyCapabilities(), 200, "location"));
|
||||
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.getTerminologyCapabilities();
|
||||
Mockito.verify(mockClient, times(2)).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTerminologyCapabilitiesStatementFailsForJSONandXML() throws IOException {
|
||||
Mockito.when(mockClient.issueGetResourceRequest(Mockito.any(URI.class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), Mockito.eq("TerminologyCapabilities"), Mockito.anyLong()))
|
||||
ArgumentMatchers.any(), Mockito.eq("TerminologyCapabilities"), Mockito.anyLong()))
|
||||
.thenThrow(new FHIRFormatError("dummy error"))
|
||||
.thenThrow(new FHIRFormatError("dummy error 2"));
|
||||
assertEquals(ResourceFormat.RESOURCE_JSON.getHeader(), toolingClient.getPreferredResourceFormat());
|
||||
|
@ -210,10 +218,9 @@ class FHIRToolingClientTest {
|
|||
@Test
|
||||
void getCapabilitiesStatement() throws IOException {
|
||||
Mockito.when(mockClient.issueGetResourceRequest(Mockito.any(URI.class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), Mockito.eq("CapabilitiesStatement"), Mockito.anyLong()))
|
||||
ArgumentMatchers.any(), Mockito.eq("CapabilitiesStatement"), Mockito.anyLong()))
|
||||
.thenReturn(new ResourceRequest<>(new CapabilityStatement(), 200, "location"));
|
||||
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
assertEquals(ResourceFormat.RESOURCE_JSON.getHeader(), toolingClient.getPreferredResourceFormat());
|
||||
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
|
@ -222,18 +229,17 @@ class FHIRToolingClientTest {
|
|||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
assertEquals(ResourceFormat.RESOURCE_JSON.getHeader(), toolingClient.getPreferredResourceFormat());
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCapabilitiesStatementFailsForJSON() throws IOException {
|
||||
Mockito.when(mockClient.issueGetResourceRequest(Mockito.any(URI.class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), Mockito.eq("CapabilitiesStatement"), Mockito.anyLong()))
|
||||
ArgumentMatchers.any(), Mockito.eq("CapabilitiesStatement"), Mockito.anyLong()))
|
||||
.thenThrow(new FHIRFormatError("dummy error"))
|
||||
.thenReturn(new ResourceRequest<>(new CapabilityStatement(), 200, "location"));
|
||||
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
assertEquals(ResourceFormat.RESOURCE_JSON.getHeader(), toolingClient.getPreferredResourceFormat());
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.getCapabilitiesStatement();
|
||||
|
@ -241,14 +247,14 @@ class FHIRToolingClientTest {
|
|||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
assertEquals(ResourceFormat.RESOURCE_XML.getHeader(), toolingClient.getPreferredResourceFormat());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCapabilitiesStatementFailsForJSONandXML() throws IOException {
|
||||
Mockito.when(mockClient.issueGetResourceRequest(Mockito.any(URI.class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), Mockito.eq("CapabilitiesStatement"), Mockito.anyLong()))
|
||||
ArgumentMatchers.any(), Mockito.eq("CapabilitiesStatement"), Mockito.anyLong()))
|
||||
.thenThrow(new FHIRFormatError("dummy error"))
|
||||
.thenThrow(new FHIRFormatError("dummy error 2"));
|
||||
assertEquals(ResourceFormat.RESOURCE_JSON.getHeader(), toolingClient.getPreferredResourceFormat());
|
||||
|
@ -261,17 +267,16 @@ class FHIRToolingClientTest {
|
|||
@Test
|
||||
void getCapabilitiesStatementQuick() throws IOException {
|
||||
Mockito.when(mockClient.issueGetResourceRequest(Mockito.any(URI.class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), Mockito.eq("CapabilitiesStatement-Quick"), Mockito.anyLong()))
|
||||
ArgumentMatchers.any(), Mockito.eq("CapabilitiesStatement-Quick"), Mockito.anyLong()))
|
||||
.thenReturn(new ResourceRequest<>(new CapabilityStatement(), 200, "location"));
|
||||
assertEquals(ResourceFormat.RESOURCE_JSON.getHeader(), toolingClient.getPreferredResourceFormat());
|
||||
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.getCapabilitiesStatementQuick();
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
assertEquals(ResourceFormat.RESOURCE_JSON.getHeader(), toolingClient.getPreferredResourceFormat());
|
||||
|
||||
|
@ -280,19 +285,18 @@ class FHIRToolingClientTest {
|
|||
@Test
|
||||
void getCapabilitiesStatementQuickFailsForJSON() throws IOException {
|
||||
Mockito.when(mockClient.issueGetResourceRequest(Mockito.any(URI.class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), Mockito.eq("CapabilitiesStatement-Quick"), Mockito.anyLong()))
|
||||
ArgumentMatchers.any(), Mockito.eq("CapabilitiesStatement-Quick"), Mockito.anyLong()))
|
||||
.thenThrow(new FHIRFormatError("dummy error"))
|
||||
.thenReturn(new ResourceRequest<>(new CapabilityStatement(), 200, "location"));
|
||||
|
||||
assertEquals(ResourceFormat.RESOURCE_JSON.getHeader(), toolingClient.getPreferredResourceFormat());
|
||||
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.getCapabilitiesStatementQuick();
|
||||
Mockito.verify(mockClient, times(2)).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
assertEquals(ResourceFormat.RESOURCE_XML.getHeader(), toolingClient.getPreferredResourceFormat());
|
||||
|
||||
|
@ -301,11 +305,10 @@ class FHIRToolingClientTest {
|
|||
@Test
|
||||
void getCapabilitiesStatementQuickFailsForJSONandXML() throws IOException {
|
||||
Mockito.when(mockClient.issueGetResourceRequest(Mockito.any(URI.class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), Mockito.eq("CapabilitiesStatement-Quick"), Mockito.anyLong()))
|
||||
ArgumentMatchers.any(), Mockito.eq("CapabilitiesStatement-Quick"), Mockito.anyLong()))
|
||||
.thenThrow(new FHIRFormatError("dummy error"))
|
||||
.thenThrow(new FHIRFormatError("dummy error 2"));
|
||||
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
assertEquals(ResourceFormat.RESOURCE_JSON.getHeader(), toolingClient.getPreferredResourceFormat());
|
||||
Exception exception = assertThrows(FHIRException.class, () -> {
|
||||
|
@ -317,63 +320,58 @@ class FHIRToolingClientTest {
|
|||
|
||||
@Test
|
||||
void read() throws IOException {
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.read(Patient.class, "id");
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void vread() throws IOException {
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.vread(Patient.class, "id", "version");
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCanonical() throws IOException {
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.getCanonical(Patient.class, "canonicalURL");
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void update() throws IOException {
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.update(generatePatient());
|
||||
Mockito.verify(mockClient).issuePutRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.any(byte[].class),
|
||||
ArgumentMatchers.anyString(), headersArgumentCaptor.capture(), ArgumentMatchers.anyString(),
|
||||
ArgumentMatchers.anyLong());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate() throws IOException {
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.validate(Patient.class, generatePatient(), "id");
|
||||
Mockito.verify(mockClient).issuePostRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.any(byte[].class),
|
||||
ArgumentMatchers.anyString(), headersArgumentCaptor.capture(), ArgumentMatchers.anyString(),
|
||||
ArgumentMatchers.anyLong());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
Iterable<HTTPHeader> argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
}
|
|
@ -4,20 +4,19 @@ import java.util.Arrays;
|
|||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeader;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import okhttp3.internal.http2.Header;
|
||||
|
||||
class ClientHeadersTest {
|
||||
|
||||
ClientHeaders clientHeaders;
|
||||
|
||||
Header h1 = new Header("header1", "value1");
|
||||
Header h2 = new Header("header2", "value2");
|
||||
Header h3 = new Header("header3", "value3");
|
||||
HTTPHeader h1 = new HTTPHeader("header1", "value1");
|
||||
HTTPHeader h2 = new HTTPHeader("header2", "value2");
|
||||
HTTPHeader h3 = new HTTPHeader("header3", "value3");
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
|
@ -43,7 +42,7 @@ class ClientHeadersTest {
|
|||
@Test
|
||||
@DisplayName("Happy path add headers as list.")
|
||||
void addHeaders() {
|
||||
List<Header> headersList = Arrays.asList(h1, h2, h3);
|
||||
List<HTTPHeader> headersList = Arrays.asList(h1, h2, h3);
|
||||
clientHeaders.addHeaders(headersList);
|
||||
Assertions.assertEquals(3, clientHeaders.headers().size());
|
||||
Assertions.assertEquals(headersList, clientHeaders.headers());
|
||||
|
@ -52,7 +51,7 @@ class ClientHeadersTest {
|
|||
@Test
|
||||
@DisplayName("Happy path add headers as list.")
|
||||
void addHeadersDuplicateAdd() {
|
||||
List<Header> headersList = Arrays.asList(h1, h2, h1);
|
||||
List<HTTPHeader> headersList = Arrays.asList(h1, h2, h1);
|
||||
Assertions.assertThrows(FHIRException.class, () -> clientHeaders.addHeaders(headersList));
|
||||
}
|
||||
|
||||
|
@ -64,7 +63,7 @@ class ClientHeadersTest {
|
|||
clientHeaders.addHeader(h3);
|
||||
clientHeaders.removeHeader(h2);
|
||||
Assertions.assertEquals(2, clientHeaders.headers().size());
|
||||
clientHeaders.removeHeader(new Header("header3", "value3"));
|
||||
clientHeaders.removeHeader(new HTTPHeader("header3", "value3"));
|
||||
Assertions.assertEquals(1, clientHeaders.headers().size());
|
||||
}
|
||||
|
||||
|
@ -79,8 +78,8 @@ class ClientHeadersTest {
|
|||
@Test
|
||||
@DisplayName("Happy path remove list of existing headers.")
|
||||
void removeHeaders() {
|
||||
List<Header> headersToAddList = Arrays.asList(h1, h2, h3);
|
||||
List<Header> headersToRemoveList = Arrays.asList(h2, h3);
|
||||
List<HTTPHeader> headersToAddList = Arrays.asList(h1, h2, h3);
|
||||
List<HTTPHeader> headersToRemoveList = Arrays.asList(h2, h3);
|
||||
clientHeaders.addHeaders(headersToAddList);
|
||||
clientHeaders.removeHeaders(headersToRemoveList);
|
||||
Assertions.assertEquals(1, clientHeaders.headers().size());
|
||||
|
@ -89,15 +88,15 @@ class ClientHeadersTest {
|
|||
@Test
|
||||
@DisplayName("Remove list containing unknown header.")
|
||||
void removeHeadersUnknown() {
|
||||
List<Header> headersToAddList = Arrays.asList(h1, h3);
|
||||
List<Header> headersToRemoveList = Arrays.asList(h2, h3);
|
||||
List<HTTPHeader> headersToAddList = Arrays.asList(h1, h3);
|
||||
List<HTTPHeader> headersToRemoveList = Arrays.asList(h2, h3);
|
||||
clientHeaders.addHeaders(headersToAddList);
|
||||
Assertions.assertThrows(FHIRException.class, () -> clientHeaders.removeHeaders(headersToRemoveList));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clearHeaders() {
|
||||
List<Header> headersToAddList = Arrays.asList(h1, h3);
|
||||
List<HTTPHeader> headersToAddList = Arrays.asList(h1, h3);
|
||||
clientHeaders.addHeaders(headersToAddList);
|
||||
Assertions.assertEquals(2, clientHeaders.headers().size());
|
||||
clientHeaders.clearHeaders();
|
||||
|
|
|
@ -1,39 +1,29 @@
|
|||
package org.hl7.fhir.r5.utils.client.network;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hl7.fhir.r5.model.OperationOutcome;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeaderUtil;
|
||||
import org.hl7.fhir.utilities.http.HTTPRequest;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeader;
|
||||
import org.hl7.fhir.utilities.http.HTTPResult;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.Request;
|
||||
|
||||
class FhirRequestBuilderTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("Test default headers are added correctly.")
|
||||
void addDefaultHeaders() {
|
||||
Request.Builder request = new Request.Builder().url("http://www.google.com");
|
||||
FhirRequestBuilder.addDefaultHeaders(request, null);
|
||||
|
||||
Map<String, List<String>> headersMap = request.build().headers().toMultimap();
|
||||
Assertions.assertNotNull(headersMap.get("User-Agent"), "User-Agent header null.");
|
||||
Assertions.assertEquals("hapi-fhir-tooling-client", headersMap.get("User-Agent").get(0),
|
||||
"User-Agent header not populated with expected value \"hapi-fhir-tooling-client\".");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test resource format headers are added correctly (GET).")
|
||||
void addResourceFormatHeadersGET() {
|
||||
String testFormat = "yaml";
|
||||
Request.Builder request = new Request.Builder().url("http://www.google.com");
|
||||
request.setMethod$okhttp("GET");
|
||||
FhirRequestBuilder.addResourceFormatHeaders(request, testFormat);
|
||||
HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com").withMethod(HTTPRequest.HttpMethod.GET);
|
||||
|
||||
Map<String, List<String>> headersMap = request.build().headers().toMultimap();
|
||||
Iterable<HTTPHeader> headers = FhirRequestBuilder.getResourceFormatHeaders(request, testFormat);
|
||||
|
||||
Map<String, List<String>> headersMap = HTTPHeaderUtil.getMultimap(headers);
|
||||
Assertions.assertNotNull(headersMap.get("Accept"), "Accept header null.");
|
||||
Assertions.assertEquals(testFormat, headersMap.get("Accept").get(0),
|
||||
"Accept header not populated with expected value " + testFormat + ".");
|
||||
|
@ -45,11 +35,11 @@ class FhirRequestBuilderTest {
|
|||
@DisplayName("Test resource format headers are added correctly (POST).")
|
||||
void addResourceFormatHeadersPOST() {
|
||||
String testFormat = "yaml";
|
||||
Request.Builder request = new Request.Builder().url("http://www.google.com");
|
||||
request.setMethod$okhttp("POST");
|
||||
FhirRequestBuilder.addResourceFormatHeaders(request, testFormat);
|
||||
HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com").withMethod(HTTPRequest.HttpMethod.POST);
|
||||
|
||||
Map<String, List<String>> headersMap = request.build().headers().toMultimap();
|
||||
Iterable<HTTPHeader> headers = FhirRequestBuilder.getResourceFormatHeaders(request, testFormat);
|
||||
|
||||
Map<String, List<String>> headersMap = HTTPHeaderUtil.getMultimap(headers);
|
||||
Assertions.assertNotNull(headersMap.get("Accept"), "Accept header null.");
|
||||
Assertions.assertEquals(testFormat, headersMap.get("Accept").get(0),
|
||||
"Accept header not populated with expected value " + testFormat + ".");
|
||||
|
@ -59,31 +49,6 @@ class FhirRequestBuilderTest {
|
|||
"Content-Type header not populated with expected value \"" + testFormat + ";charset=" + FhirRequestBuilder.DEFAULT_CHARSET + "\".");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test a list of provided headers are added correctly.")
|
||||
void addHeaders() {
|
||||
String headerName1 = "headerName1";
|
||||
String headerValue1 = "headerValue1";
|
||||
String headerName2 = "headerName2";
|
||||
String headerValue2 = "headerValue2";
|
||||
|
||||
Headers headers = new Headers.Builder()
|
||||
.add(headerName1, headerValue1)
|
||||
.add(headerName2, headerValue2)
|
||||
.build();
|
||||
|
||||
Request.Builder request = new Request.Builder().url("http://www.google.com");
|
||||
FhirRequestBuilder.addHeaders(request, headers);
|
||||
|
||||
Map<String, List<String>> headersMap = request.build().headers().toMultimap();
|
||||
Assertions.assertNotNull(headersMap.get(headerName1), headerName1 + " header null.");
|
||||
Assertions.assertEquals(headerValue1, headersMap.get(headerName1).get(0),
|
||||
headerName1 + " header not populated with expected value " + headerValue1 + ".");
|
||||
Assertions.assertNotNull(headersMap.get(headerName2), headerName2 + " header null.");
|
||||
Assertions.assertEquals(headerValue2, headersMap.get(headerName2).get(0),
|
||||
headerName2 + " header not populated with expected value " + headerValue2 + ".");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test that FATAL issue severity triggers error.")
|
||||
void hasErrorTestFatal() {
|
||||
|
@ -120,19 +85,22 @@ class FhirRequestBuilderTest {
|
|||
@DisplayName("Test that getLocationHeader returns header for 'location'.")
|
||||
void getLocationHeaderWhenOnlyLocationIsSet() {
|
||||
final String expectedLocationHeader = "location_header_value";
|
||||
Headers headers = new Headers.Builder()
|
||||
.add(FhirRequestBuilder.LOCATION_HEADER, expectedLocationHeader)
|
||||
.build();
|
||||
Assertions.assertEquals(expectedLocationHeader, FhirRequestBuilder.getLocationHeader(headers));
|
||||
HTTPResult result = new HTTPResult("source",
|
||||
200,
|
||||
"message",
|
||||
"contentType",
|
||||
new byte[0],
|
||||
List.of(new HTTPHeader(FhirRequestBuilder.LOCATION_HEADER, expectedLocationHeader)));
|
||||
|
||||
Assertions.assertEquals(expectedLocationHeader, FhirRequestBuilder.getLocationHeader(result.getHeaders()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test that getLocationHeader returns header for 'content-location'.")
|
||||
void getLocationHeaderWhenOnlyContentLocationIsSet() {
|
||||
final String expectedContentLocationHeader = "content_location_header_value";
|
||||
Headers headers = new Headers.Builder()
|
||||
.add(FhirRequestBuilder.CONTENT_LOCATION_HEADER, expectedContentLocationHeader)
|
||||
.build();
|
||||
Iterable<HTTPHeader> headers = List.of(new HTTPHeader(FhirRequestBuilder.CONTENT_LOCATION_HEADER, expectedContentLocationHeader));
|
||||
|
||||
Assertions.assertEquals(expectedContentLocationHeader, FhirRequestBuilder.getLocationHeader(headers));
|
||||
}
|
||||
|
||||
|
@ -141,18 +109,18 @@ class FhirRequestBuilderTest {
|
|||
void getLocationHeaderWhenLocationAndContentLocationAreSet() {
|
||||
final String expectedLocationHeader = "location_header_value";
|
||||
final String expectedContentLocationHeader = "content_location_header_value";
|
||||
Headers headers = new Headers.Builder()
|
||||
.add(FhirRequestBuilder.LOCATION_HEADER, expectedLocationHeader)
|
||||
.add(FhirRequestBuilder.CONTENT_LOCATION_HEADER, expectedContentLocationHeader)
|
||||
.build();
|
||||
|
||||
Iterable<HTTPHeader> headers = List.of(
|
||||
new HTTPHeader(FhirRequestBuilder.LOCATION_HEADER, expectedLocationHeader),
|
||||
new HTTPHeader(FhirRequestBuilder.CONTENT_LOCATION_HEADER, expectedContentLocationHeader)
|
||||
);
|
||||
|
||||
Assertions.assertEquals(expectedLocationHeader, FhirRequestBuilder.getLocationHeader(headers));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test that getLocationHeader returns null when no location available.")
|
||||
void getLocationHeaderWhenNoLocationSet() {
|
||||
Headers headers = new Headers.Builder()
|
||||
.build();
|
||||
Assertions.assertNull(FhirRequestBuilder.getLocationHeader(headers));
|
||||
Assertions.assertNull(FhirRequestBuilder.getLocationHeader(Collections.emptyList()));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package org.hl7.fhir.utilities.http;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.With;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
@EqualsAndHashCode
|
||||
public class HTTPHeader {
|
||||
@With @Getter @Nonnull
|
||||
private final String name;
|
||||
@With @Getter
|
||||
private final String value;
|
||||
|
||||
public HTTPHeader(@Nonnull String name, String value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package org.hl7.fhir.utilities.http;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class HTTPHeaderUtil {
|
||||
|
||||
public static final String USER_AGENT = "User-Agent";
|
||||
|
||||
|
||||
public static Map<String, List<String>> getMultimap(Iterable<HTTPHeader> headers) {
|
||||
Map<String, List<String>> result = new HashMap<>();
|
||||
if (headers != null) {
|
||||
for (HTTPHeader header : headers) {
|
||||
List<String> values = result.getOrDefault(header.getName(), new ArrayList<>());
|
||||
values.add(header.getValue());
|
||||
result.put(header.getName(), values);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Iterable<String> getHeaders(Iterable<HTTPHeader> headers, String key) {
|
||||
List<String> result = new ArrayList<>();
|
||||
if (headers != null) {
|
||||
for (HTTPHeader header : headers) {
|
||||
if (header.getName().equalsIgnoreCase(key)) {
|
||||
result.add(header.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String getSingleHeader(Iterable<HTTPHeader> headers, String key) {
|
||||
for (HTTPHeader header : headers) {
|
||||
if (header.getName().equalsIgnoreCase(key)) {
|
||||
return header.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package org.hl7.fhir.utilities.http;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.With;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class HTTPRequest {
|
||||
|
||||
public HTTPRequest() {
|
||||
url = null;
|
||||
method = HttpMethod.GET;
|
||||
body = null;
|
||||
contentType = null;
|
||||
headers = Collections.emptyList();
|
||||
}
|
||||
|
||||
public enum HttpMethod {
|
||||
GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH
|
||||
}
|
||||
|
||||
@Getter @Nullable
|
||||
private final URL url;
|
||||
|
||||
public HTTPRequest withUrl(URL url) {
|
||||
return new HTTPRequest(url, method, body, contentType, headers);
|
||||
}
|
||||
|
||||
public HTTPRequest withUrl(String url) {
|
||||
try {
|
||||
return withUrl(new URL(url));
|
||||
} catch (MalformedURLException e) {
|
||||
throw new IllegalArgumentException("Invalid URL: " + url, e);
|
||||
}
|
||||
}
|
||||
|
||||
@With @Getter
|
||||
private final HttpMethod method;
|
||||
|
||||
@With @Getter @Nullable
|
||||
private final byte[] body;
|
||||
|
||||
@With @Getter @Nullable
|
||||
private final String contentType;
|
||||
|
||||
@With @Getter @Nonnull
|
||||
private final Iterable<HTTPHeader> headers;
|
||||
}
|
|
@ -2,39 +2,40 @@ package org.hl7.fhir.utilities.http;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.hl7.fhir.utilities.TextFile;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
public class HTTPResult {
|
||||
private int code;
|
||||
private String contentType;
|
||||
private byte[] content;
|
||||
private String source;
|
||||
private String message;
|
||||
|
||||
|
||||
|
||||
@Getter
|
||||
private final int code;
|
||||
@Getter
|
||||
private final String contentType;
|
||||
@Getter
|
||||
private final byte[] content;
|
||||
@Getter
|
||||
private final String source;
|
||||
@Getter
|
||||
private final String message;
|
||||
@Getter
|
||||
private final Iterable<HTTPHeader> headers;
|
||||
|
||||
public HTTPResult(String source, int code, String message, String contentType, byte[] content) {
|
||||
this(source, code, message, contentType, content, Collections.emptyList());
|
||||
}
|
||||
|
||||
|
||||
public HTTPResult(String source, int code, String message, String contentType, byte[] content, Iterable<HTTPHeader> headers) {
|
||||
super();
|
||||
this.source = source;
|
||||
this.code = code;
|
||||
this.contentType = contentType;
|
||||
this.content = content;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
public byte[] getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public String getSource() {
|
||||
return source;
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public void checkThrowException() throws IOException {
|
||||
|
@ -52,11 +53,7 @@ public class HTTPResult {
|
|||
}
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public String getContentAsString() {
|
||||
return new String(content, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
package org.hl7.fhir.utilities.http;
|
||||
|
||||
import okhttp3.*;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.hl7.fhir.utilities.http.okhttpimpl.LoggingInterceptor;
|
||||
import org.hl7.fhir.utilities.http.okhttpimpl.ProxyAuthenticator;
|
||||
import org.hl7.fhir.utilities.http.okhttpimpl.RetryInterceptor;
|
||||
import org.hl7.fhir.utilities.settings.ServerDetailsPOJO;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ManagedFhirWebAccessor extends ManagedWebAccessorBase<ManagedFhirWebAccessor> {
|
||||
|
||||
/**
|
||||
* The singleton instance of the HttpClient, used for all requests.
|
||||
*/
|
||||
private static OkHttpClient okHttpClient;
|
||||
|
||||
private long timeout;
|
||||
private TimeUnit timeoutUnit;
|
||||
private int retries;
|
||||
private ToolingClientLogger logger;
|
||||
private LoggingInterceptor loggingInterceptor;
|
||||
|
||||
public ManagedFhirWebAccessor withTimeout(long timeout, TimeUnit timeoutUnit) {
|
||||
this.timeout = timeout;
|
||||
this.timeoutUnit = timeoutUnit;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ManagedFhirWebAccessor withRetries(int retries) {
|
||||
this.retries = retries;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ManagedFhirWebAccessor withLogger(ToolingClientLogger logger) {
|
||||
this.logger = logger;
|
||||
this.loggingInterceptor = new LoggingInterceptor(logger);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public ManagedFhirWebAccessor(String userAgent, List<ServerDetailsPOJO> serverAuthDetails) {
|
||||
super(userAgent, serverAuthDetails);
|
||||
this.timeout = 5000;
|
||||
this.timeoutUnit = TimeUnit.MILLISECONDS;
|
||||
}
|
||||
|
||||
protected HTTPRequest httpRequestWithDefaultHeaders(HTTPRequest request) {
|
||||
List<HTTPHeader> headers = new ArrayList<>();
|
||||
if (HTTPHeaderUtil.getSingleHeader(request.getHeaders(), HTTPHeaderUtil.USER_AGENT) == null
|
||||
&& getUserAgent() != null) {
|
||||
headers.add(new HTTPHeader(HTTPHeaderUtil.USER_AGENT, getUserAgent()));
|
||||
}
|
||||
request.getHeaders().forEach(headers::add);
|
||||
return request.withHeaders(headers);
|
||||
}
|
||||
|
||||
protected HTTPRequest requestWithManagedHeaders(HTTPRequest httpRequest) {
|
||||
HTTPRequest requestWithDefaultHeaders = httpRequestWithDefaultHeaders(httpRequest);
|
||||
|
||||
List<HTTPHeader> headers = new ArrayList<>();
|
||||
requestWithDefaultHeaders.getHeaders().forEach(headers::add);
|
||||
|
||||
for (Map.Entry<String, String> entry : this.getHeaders().entrySet()) {
|
||||
headers.add(new HTTPHeader(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
|
||||
if (getAuthenticationMode() != null) {
|
||||
if (getAuthenticationMode() != HTTPAuthenticationMode.NONE) {
|
||||
switch (getAuthenticationMode()) {
|
||||
case BASIC:
|
||||
final String basicCredential = Credentials.basic(getUsername(), getPassword());
|
||||
headers.add(new HTTPHeader("Authorization", basicCredential));
|
||||
break;
|
||||
case TOKEN:
|
||||
String tokenCredential = "Bearer " + getToken();
|
||||
headers.add(new HTTPHeader("Authorization", tokenCredential));
|
||||
break;
|
||||
case APIKEY:
|
||||
String apiKeyCredential = getToken();
|
||||
headers.add(new HTTPHeader("Api-Key", apiKeyCredential));
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ServerDetailsPOJO settings = ManagedWebAccessUtils.getServer(httpRequest.getUrl().toString(), getServerAuthDetails());
|
||||
if (settings != null) {
|
||||
switch (settings.getAuthenticationType()) {
|
||||
case "basic":
|
||||
final String basicCredential = Credentials.basic(settings.getUsername(), settings.getPassword());
|
||||
headers.add(new HTTPHeader("Authorization", basicCredential));
|
||||
break;
|
||||
case "token":
|
||||
String tokenCredential = "Bearer " + settings.getToken();
|
||||
headers.add(new HTTPHeader("Authorization", tokenCredential));
|
||||
break;
|
||||
case "apikey":
|
||||
String apiKeyCredential = settings.getApikey();
|
||||
headers.add(new HTTPHeader("Api-Key", apiKeyCredential));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return httpRequest.withHeaders(headers);
|
||||
}
|
||||
|
||||
public HTTPResult httpCall(HTTPRequest httpRequest) throws IOException {
|
||||
switch (ManagedWebAccess.getAccessPolicy()) {
|
||||
case DIRECT:
|
||||
|
||||
HTTPRequest httpRequestWithDirectHeaders = requestWithManagedHeaders(httpRequest);
|
||||
assert httpRequestWithDirectHeaders.getUrl() != null;
|
||||
|
||||
RequestBody body = httpRequestWithDirectHeaders.getBody() == null ? null : RequestBody.create(httpRequestWithDirectHeaders.getBody());
|
||||
Request.Builder requestBuilder = new Request.Builder()
|
||||
.url(httpRequestWithDirectHeaders.getUrl())
|
||||
.method(httpRequestWithDirectHeaders.getMethod().name(), body);
|
||||
|
||||
for (HTTPHeader header : httpRequestWithDirectHeaders.getHeaders()) {
|
||||
requestBuilder.addHeader(header.getName(), header.getValue());
|
||||
}
|
||||
OkHttpClient okHttpClient = getOkHttpClient();
|
||||
//TODO check and throw based on httpRequest:
|
||||
|
||||
if (!ManagedWebAccess.inAllowedPaths(httpRequestWithDirectHeaders.getUrl().toString())) {
|
||||
throw new IOException("The pathname '"+httpRequestWithDirectHeaders.getUrl().toString()+"' cannot be accessed by policy");}
|
||||
Response response = okHttpClient.newCall(requestBuilder.build()).execute();
|
||||
return getHTTPResult(response);
|
||||
case MANAGED:
|
||||
HTTPRequest httpRequestWithManagedHeaders = requestWithManagedHeaders(httpRequest);
|
||||
assert httpRequestWithManagedHeaders.getUrl() != null;
|
||||
return ManagedWebAccess.getFhirWebAccessor().httpCall(httpRequestWithManagedHeaders);
|
||||
case PROHIBITED:
|
||||
throw new IOException("Access to the internet is not allowed by local security policy");
|
||||
default:
|
||||
throw new IOException("Internal Error");
|
||||
}
|
||||
}
|
||||
|
||||
private HTTPResult getHTTPResult(Response execute) throws IOException {
|
||||
return new HTTPResult(execute.request().url().toString(), execute.code(), execute.message(), execute.header("Content-Type"), execute.body() != null && execute.body().contentLength() != 0 ? execute.body().bytes() : null, getHeadersFromResponse(execute));
|
||||
}
|
||||
|
||||
private Iterable<HTTPHeader> getHeadersFromResponse(Response response) {
|
||||
List<HTTPHeader> headers = new ArrayList<>();
|
||||
for (String name : response.headers().names()) {
|
||||
headers.add(new HTTPHeader(name, response.header(name)));
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
private OkHttpClient getOkHttpClient() {
|
||||
if (okHttpClient == null) {
|
||||
okHttpClient = new OkHttpClient();
|
||||
}
|
||||
OkHttpClient.Builder builder = okHttpClient.newBuilder();
|
||||
if (logger != null) builder.addInterceptor(loggingInterceptor);
|
||||
builder.addInterceptor(new RetryInterceptor(retries));
|
||||
builder.proxyAuthenticator(new ProxyAuthenticator());
|
||||
return builder.connectTimeout(timeout, timeoutUnit)
|
||||
.writeTimeout(timeout, timeoutUnit)
|
||||
.readTimeout(timeout, timeoutUnit).build();
|
||||
}
|
||||
|
||||
}
|
|
@ -31,23 +31,16 @@ package org.hl7.fhir.utilities.http;
|
|||
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import lombok.Getter;
|
||||
import org.hl7.fhir.utilities.settings.FhirSettings;
|
||||
import org.hl7.fhir.utilities.settings.ServerDetailsPOJO;
|
||||
|
||||
/**
|
||||
* see security.md - manages access to the local file system by the FHIR HAPI Core library
|
||||
*
|
||||
* see security.md - manages web access by the FHIR HAPI Core library
|
||||
* <p/>
|
||||
* By using accessPolicy, allowedDomains and accessor, a host java application can control
|
||||
* whether this library has direct access to the web (and which domains it is allowed to access),
|
||||
* or whether the host application provides controlled access, or whether no access is allowed at all
|
||||
|
@ -57,13 +50,17 @@ import org.hl7.fhir.utilities.settings.ServerDetailsPOJO;
|
|||
*
|
||||
*/
|
||||
public class ManagedWebAccess {
|
||||
|
||||
|
||||
public interface IWebAccessor {
|
||||
HTTPResult get(String url, String accept, Map<String, String> headers) throws IOException;
|
||||
HTTPResult post(String url, byte[] bytes, String contentType, String accept, Map<String, String> headers) throws IOException;
|
||||
HTTPResult put(String url, byte[] bytes, String contentType, String accept, Map<String, String> headers) throws IOException;
|
||||
}
|
||||
|
||||
public interface IFhirWebAccessor {
|
||||
HTTPResult httpCall(HTTPRequest httpRequest);
|
||||
}
|
||||
|
||||
public enum WebAccessPolicy {
|
||||
DIRECT, // open access to the web, though access can be restricted only to domains in AllowedDomains
|
||||
MANAGED, // no access except by the IWebAccessor
|
||||
|
@ -71,12 +68,18 @@ public class ManagedWebAccess {
|
|||
}
|
||||
|
||||
private static WebAccessPolicy accessPolicy = WebAccessPolicy.DIRECT; // for legacy reasons
|
||||
//TODO get this from fhir settings
|
||||
private static List<String> allowedDomains = new ArrayList<>();
|
||||
@Getter
|
||||
private static IWebAccessor accessor;
|
||||
|
||||
@Getter
|
||||
private static IFhirWebAccessor fhirWebAccessor;
|
||||
|
||||
@Getter
|
||||
private static String userAgent;
|
||||
private static List<ServerDetailsPOJO> serverAuthDetails;
|
||||
|
||||
|
||||
|
||||
public static WebAccessPolicy getAccessPolicy() {
|
||||
return accessPolicy;
|
||||
}
|
||||
|
@ -97,37 +100,51 @@ public class ManagedWebAccess {
|
|||
return false;
|
||||
}
|
||||
|
||||
public static String getUserAgent() {
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
public static void setUserAgent(String userAgent) {
|
||||
ManagedWebAccess.userAgent = userAgent;
|
||||
}
|
||||
|
||||
public static IWebAccessor getAccessor() {
|
||||
return accessor;
|
||||
public static ManagedWebAccessor accessor() {
|
||||
return new ManagedWebAccessor(userAgent, serverAuthDetails);
|
||||
}
|
||||
|
||||
public static ManagedWebAccessBuilder builder() {
|
||||
return new ManagedWebAccessBuilder(userAgent, serverAuthDetails);
|
||||
public static ManagedFhirWebAccessor fhirAccessor() {
|
||||
return new ManagedFhirWebAccessor(userAgent, serverAuthDetails);
|
||||
}
|
||||
|
||||
public static HTTPResult get(String url) throws IOException {
|
||||
return builder().get(url);
|
||||
return accessor().get(url);
|
||||
}
|
||||
|
||||
public static HTTPResult get(String url, String accept) throws IOException {
|
||||
return builder().withAccept(accept).get(url);
|
||||
return accessor().get(url, accept);
|
||||
}
|
||||
|
||||
public static HTTPResult post(String url, byte[] content, String contentType, String accept) throws IOException {
|
||||
return builder().withAccept(accept).post(url, content, contentType);
|
||||
return accessor().post(url, content, contentType, accept);
|
||||
}
|
||||
|
||||
|
||||
public static HTTPResult put(String url, byte[] content, String contentType, String accept) throws IOException {
|
||||
return builder().withAccept(accept).put(url, content, contentType);
|
||||
return accessor().put(url, content, contentType, accept);
|
||||
}
|
||||
|
||||
public static HTTPResult httpCall(HTTPRequest httpRequest) throws IOException {
|
||||
return fhirAccessor().httpCall(httpRequest);
|
||||
}
|
||||
|
||||
public static void loadFromFHIRSettings() {
|
||||
setAccessPolicy(FhirSettings.isProhibitNetworkAccess() ? WebAccessPolicy.PROHIBITED : WebAccessPolicy.DIRECT);
|
||||
setUserAgent("hapi-fhir-tooling-client");
|
||||
serverAuthDetails = new ArrayList<>();
|
||||
serverAuthDetails.addAll(FhirSettings.getPackageServers());
|
||||
serverAuthDetails.addAll(FhirSettings.getTerminologyServers());
|
||||
}
|
||||
|
||||
public static void loadFromFHIRSettings(FhirSettings settings) {
|
||||
setAccessPolicy(settings.isProhibitNetworkAccess() ? WebAccessPolicy.PROHIBITED : WebAccessPolicy.DIRECT);
|
||||
setUserAgent("hapi-fhir-tooling-client");
|
||||
serverAuthDetails = new ArrayList<>();
|
||||
serverAuthDetails.addAll(settings.getPackageServers());
|
||||
serverAuthDetails.addAll(settings.getTerminologyServers());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package org.hl7.fhir.utilities.http;
|
||||
|
||||
import org.hl7.fhir.utilities.settings.ServerDetailsPOJO;
|
||||
|
||||
public class ManagedWebAccessUtils {
|
||||
|
||||
public static ServerDetailsPOJO getServer(String url, Iterable<ServerDetailsPOJO> serverAuthDetails) {
|
||||
if (serverAuthDetails != null) {
|
||||
for (ServerDetailsPOJO t : serverAuthDetails) {
|
||||
if (url.startsWith(t.getUrl())) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -9,61 +9,30 @@ import java.util.Map;
|
|||
|
||||
import org.hl7.fhir.utilities.settings.ServerDetailsPOJO;
|
||||
|
||||
/**
|
||||
* Simple HTTP client for making requests to a server.
|
||||
*/
|
||||
public class ManagedWebAccessor extends ManagedWebAccessorBase<ManagedWebAccessor> {
|
||||
|
||||
public class ManagedWebAccessBuilder {
|
||||
|
||||
private String userAgent;
|
||||
private HTTPAuthenticationMode authenticationMode;
|
||||
private String username;
|
||||
private String password;
|
||||
private String token;
|
||||
private String accept;
|
||||
private List<ServerDetailsPOJO> serverAuthDetails;
|
||||
private Map<String, String> headers = new HashMap<String, String>();
|
||||
|
||||
public ManagedWebAccessBuilder(String userAgent, List<ServerDetailsPOJO> serverAuthDetails) {
|
||||
this.userAgent = userAgent;
|
||||
this.serverAuthDetails = serverAuthDetails;
|
||||
}
|
||||
|
||||
public ManagedWebAccessBuilder withAccept(String accept) {
|
||||
this.accept = accept;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ManagedWebAccessBuilder withHeader(String name, String value) {
|
||||
headers.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ManagedWebAccessBuilder withBasicAuth(String username, String password) {
|
||||
this.authenticationMode = HTTPAuthenticationMode.BASIC;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
return this;
|
||||
public ManagedWebAccessor(String userAgent, List<ServerDetailsPOJO> serverAuthDetails) {
|
||||
super(userAgent, serverAuthDetails);
|
||||
}
|
||||
|
||||
public ManagedWebAccessBuilder withToken(String token) {
|
||||
this.authenticationMode = HTTPAuthenticationMode.TOKEN;
|
||||
this.token = token;
|
||||
return this;
|
||||
}
|
||||
|
||||
private Map<String, String> headers() {
|
||||
Map<String, String> headers = new HashMap<String, String>();
|
||||
headers.putAll(this.headers);
|
||||
if (authenticationMode == HTTPAuthenticationMode.TOKEN) {
|
||||
headers.put("Authorization", "Bearer " + token);
|
||||
} else if (authenticationMode == HTTPAuthenticationMode.BASIC) {
|
||||
String auth = username+":"+password;
|
||||
private Map<String, String> newHeaders() {
|
||||
Map<String, String> headers = new HashMap<String, String>(this.getHeaders());
|
||||
if (getAuthenticationMode() == HTTPAuthenticationMode.TOKEN) {
|
||||
headers.put("Authorization", "Bearer " + getToken());
|
||||
} else if (getAuthenticationMode() == HTTPAuthenticationMode.BASIC) {
|
||||
String auth = getUsername() +":"+ getPassword();
|
||||
byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes(StandardCharsets.UTF_8));
|
||||
headers.put("Authorization", "Basic " + new String(encodedAuth));
|
||||
} else if (getAuthenticationMode() == HTTPAuthenticationMode.APIKEY) {
|
||||
headers.put("Api-Key", getToken());
|
||||
}
|
||||
|
||||
if (userAgent != null) {
|
||||
headers.put("User-Agent", userAgent);
|
||||
if (getUserAgent() != null) {
|
||||
headers.put("User-Agent", getUserAgent());
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
|
@ -72,25 +41,32 @@ public class ManagedWebAccessBuilder {
|
|||
throw new IOException("The pathname '"+url+"' cannot be accessed by policy");
|
||||
}
|
||||
SimpleHTTPClient client = new SimpleHTTPClient();
|
||||
if (userAgent != null) {
|
||||
client.addHeader("User-Agent", userAgent);
|
||||
|
||||
for (Map.Entry<String, String> entry : this.getHeaders().entrySet()) {
|
||||
client.addHeader(entry.getKey(), entry.getValue());
|
||||
}
|
||||
if (authenticationMode != null && authenticationMode != HTTPAuthenticationMode.NONE) {
|
||||
client.setAuthenticationMode(authenticationMode);
|
||||
switch (authenticationMode) {
|
||||
|
||||
if (getUserAgent() != null) {
|
||||
client.addHeader("User-Agent", getUserAgent());
|
||||
}
|
||||
if (getAuthenticationMode() != null) {
|
||||
if (getAuthenticationMode() != HTTPAuthenticationMode.NONE) {
|
||||
client.setAuthenticationMode(getAuthenticationMode());
|
||||
switch (getAuthenticationMode()) {
|
||||
case BASIC :
|
||||
client.setUsername(username);
|
||||
client.setPassword(password);
|
||||
client.setUsername(getUsername());
|
||||
client.setPassword(getPassword());
|
||||
break;
|
||||
case TOKEN :
|
||||
client.setToken(token);
|
||||
client.setToken(getToken());
|
||||
break;
|
||||
case APIKEY :
|
||||
client.setApiKey(token);
|
||||
client.setApiKey(getToken());
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ServerDetailsPOJO settings = getServer(url);
|
||||
ServerDetailsPOJO settings = ManagedWebAccessUtils.getServer(url, getServerAuthDetails());
|
||||
if (settings != null) {
|
||||
switch (settings.getAuthenticationType()) {
|
||||
case "basic" :
|
||||
|
@ -109,32 +85,23 @@ public class ManagedWebAccessBuilder {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (username != null || token != null) {
|
||||
|
||||
client.setAuthenticationMode(authenticationMode);
|
||||
if (getUsername() != null || getToken() != null) {
|
||||
client.setAuthenticationMode(getAuthenticationMode());
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
|
||||
private ServerDetailsPOJO getServer(String url) {
|
||||
if (serverAuthDetails != null) {
|
||||
for (ServerDetailsPOJO t : serverAuthDetails) {
|
||||
if (url.startsWith(t.getUrl())) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
public HTTPResult get(String url) throws IOException {
|
||||
return get(url, null);
|
||||
}
|
||||
|
||||
public HTTPResult get(String url) throws IOException {
|
||||
public HTTPResult get(String url, String accept) throws IOException {
|
||||
switch (ManagedWebAccess.getAccessPolicy()) {
|
||||
case DIRECT:
|
||||
SimpleHTTPClient client = setupClient(url);
|
||||
return client.get(url, accept);
|
||||
case MANAGED:
|
||||
return ManagedWebAccess.getAccessor().get(url, accept, headers());
|
||||
return ManagedWebAccess.getAccessor().get(url, accept, newHeaders());
|
||||
case PROHIBITED:
|
||||
throw new IOException("Access to the internet is not allowed by local security policy");
|
||||
default:
|
||||
|
@ -142,14 +109,17 @@ public class ManagedWebAccessBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public HTTPResult post(String url, byte[] content, String contentType) throws IOException {
|
||||
return post(url, content, contentType, null);
|
||||
}
|
||||
|
||||
public HTTPResult post(String url, byte[] content, String contentType, String accept) throws IOException {
|
||||
switch (ManagedWebAccess.getAccessPolicy()) {
|
||||
case DIRECT:
|
||||
SimpleHTTPClient client = setupClient(url);
|
||||
return client.post(url, contentType, content, accept);
|
||||
case MANAGED:
|
||||
return ManagedWebAccess.getAccessor().post(url, content, contentType, accept, headers());
|
||||
return ManagedWebAccess.getAccessor().post(url, content, contentType, accept, newHeaders());
|
||||
case PROHIBITED:
|
||||
throw new IOException("Access to the internet is not allowed by local security policy");
|
||||
default:
|
||||
|
@ -158,17 +128,20 @@ public class ManagedWebAccessBuilder {
|
|||
}
|
||||
|
||||
public HTTPResult put(String url, byte[] content, String contentType) throws IOException {
|
||||
return put(url, content, contentType, null);
|
||||
}
|
||||
|
||||
public HTTPResult put(String url, byte[] content, String contentType, String accept) throws IOException {
|
||||
switch (ManagedWebAccess.getAccessPolicy()) {
|
||||
case DIRECT:
|
||||
SimpleHTTPClient client = setupClient(url);
|
||||
return client.put(url, contentType, content, accept);
|
||||
case MANAGED:
|
||||
return ManagedWebAccess.getAccessor().put(url, content, contentType, accept, headers());
|
||||
return ManagedWebAccess.getAccessor().put(url, content, contentType, accept, newHeaders());
|
||||
case PROHIBITED:
|
||||
throw new IOException("Access to the internet is not allowed by local security policy");
|
||||
default:
|
||||
throw new IOException("Internal Error");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
package org.hl7.fhir.utilities.http;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.hl7.fhir.utilities.settings.ServerDetailsPOJO;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class ManagedWebAccessorBase<B extends ManagedWebAccessorBase<B>> {
|
||||
@Getter
|
||||
private final String userAgent;
|
||||
@Getter
|
||||
private HTTPAuthenticationMode authenticationMode;
|
||||
@Getter
|
||||
private String username;
|
||||
@Getter
|
||||
private String password;
|
||||
@Getter
|
||||
private String token;
|
||||
|
||||
@Getter
|
||||
private final List<ServerDetailsPOJO> serverAuthDetails;
|
||||
@Getter
|
||||
private final Map<String, String> headers = new HashMap<>();
|
||||
|
||||
public ManagedWebAccessorBase(String userAgent, List<ServerDetailsPOJO> serverAuthDetails) {
|
||||
this.userAgent = userAgent;
|
||||
this.serverAuthDetails = serverAuthDetails;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
final B self() {
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B withHeader(String name, String value) {
|
||||
headers.put(name, value);
|
||||
return self();
|
||||
}
|
||||
|
||||
public B withBasicAuth(String username, String password) {
|
||||
this.authenticationMode = HTTPAuthenticationMode.BASIC;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
return self();
|
||||
}
|
||||
|
||||
public B withToken(String token) {
|
||||
this.authenticationMode = HTTPAuthenticationMode.TOKEN;
|
||||
this.token = token;
|
||||
return self();
|
||||
}
|
||||
|
||||
public B withApiKey(String apiKey) {
|
||||
this.authenticationMode = HTTPAuthenticationMode.APIKEY;
|
||||
this.token = apiKey;
|
||||
return self();
|
||||
}
|
||||
|
||||
public B withNoneAuth() {
|
||||
this.authenticationMode = HTTPAuthenticationMode.NONE;
|
||||
setAllAuthHeadersToNull();
|
||||
return self();
|
||||
}
|
||||
|
||||
public B withServerSpecificAuth() {
|
||||
this.authenticationMode = null;
|
||||
setAllAuthHeadersToNull();
|
||||
return self();
|
||||
}
|
||||
|
||||
private void setAllAuthHeadersToNull() {
|
||||
this.token = null;
|
||||
this.username = null;
|
||||
this.password = null;
|
||||
}
|
||||
}
|
|
@ -20,27 +20,10 @@ import lombok.Setter;
|
|||
|
||||
public class SimpleHTTPClient {
|
||||
|
||||
|
||||
public static class Header {
|
||||
private String name;
|
||||
private String value;
|
||||
public Header(String name, String value) {
|
||||
super();
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private static final int MAX_REDIRECTS = 5;
|
||||
private static int counter = 1;
|
||||
|
||||
private List<Header> headers = new ArrayList<>();
|
||||
private List<HTTPHeader> headers = new ArrayList<>();
|
||||
|
||||
@Getter @Setter
|
||||
private HTTPAuthenticationMode authenticationMode;
|
||||
|
@ -58,7 +41,7 @@ public class SimpleHTTPClient {
|
|||
private String apiKey;
|
||||
|
||||
public void addHeader(String name, String value) {
|
||||
headers.add(new Header(name, value));
|
||||
headers.add(new HTTPHeader(name, value));
|
||||
}
|
||||
|
||||
public HTTPResult get(String url) throws IOException {
|
||||
|
@ -114,8 +97,8 @@ public class SimpleHTTPClient {
|
|||
|
||||
private void setHeaders(HttpURLConnection c) {
|
||||
if (headers != null) {
|
||||
for (Header h : headers) {
|
||||
c.setRequestProperty(h.getName(), h.getValue());
|
||||
for (HTTPHeader header : headers) {
|
||||
c.setRequestProperty(header.getName(), header.getValue());
|
||||
}
|
||||
}
|
||||
c.setConnectTimeout(15000);
|
||||
|
@ -126,7 +109,7 @@ public class SimpleHTTPClient {
|
|||
private void setAuthenticationHeader(HttpURLConnection c) {
|
||||
String authHeaderValue = null;
|
||||
if (authenticationMode == HTTPAuthenticationMode.TOKEN) {
|
||||
authHeaderValue = "Bearer " + new String(token);
|
||||
authHeaderValue = "Bearer " + token;
|
||||
} else if (authenticationMode == HTTPAuthenticationMode.BASIC) {
|
||||
String auth = username+":"+password;
|
||||
byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes(StandardCharsets.UTF_8));
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
package org.hl7.fhir.utilities.http.okhttpimpl;
|
||||
|
||||
import okhttp3.*;
|
||||
import okio.Buffer;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class LoggingInterceptor implements Interceptor{
|
||||
|
||||
private ToolingClientLogger logger;
|
||||
|
||||
public LoggingInterceptor(ToolingClientLogger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public LoggingInterceptor setLogger(ToolingClientLogger logger) {
|
||||
this.logger = logger;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response intercept(@Nonnull Interceptor.Chain chain) throws IOException {
|
||||
// Log Request
|
||||
Request request = chain.request();
|
||||
List<String> hdrs = new ArrayList<>();
|
||||
for (String s : request.headers().toString().split("\\n")) {
|
||||
hdrs.add(s.trim());
|
||||
}
|
||||
byte[] cnt = null;
|
||||
if (request.body() != null) {
|
||||
Buffer buf = new Buffer();
|
||||
request.body().writeTo(buf);
|
||||
cnt = buf.readByteArray();
|
||||
}
|
||||
if (logger != null) {
|
||||
logger.logRequest(request.method(), request.url().toString(), hdrs, cnt);
|
||||
}
|
||||
|
||||
// Log Response
|
||||
Response response = null;
|
||||
response = chain.proceed(chain.request());
|
||||
|
||||
MediaType contentType = null;
|
||||
byte[] bodyBytes = null;
|
||||
if (response.body() != null) {
|
||||
contentType = response.body().contentType();
|
||||
bodyBytes = response.body().bytes();
|
||||
}
|
||||
|
||||
// Get Headers as List
|
||||
List<String> headerList = new ArrayList<>();
|
||||
Map<String, List<String>> headerMap = response.headers().toMultimap();
|
||||
headerMap.keySet().forEach(key -> headerMap.get(key).forEach(value -> headerList.add(key + ":" + value)));
|
||||
|
||||
if (logger != null) {
|
||||
long responseTimeInMillis = response.receivedResponseAtMillis() - response.sentRequestAtMillis();
|
||||
logger.logResponse(Integer.toString(response.code()), headerList, bodyBytes, responseTimeInMillis);
|
||||
}
|
||||
|
||||
// Reading byte[] clears body. Need to recreate.
|
||||
ResponseBody body = ResponseBody.create(bodyBytes, contentType);
|
||||
return response.newBuilder().body(body).build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package org.hl7.fhir.utilities.http.okhttpimpl;
|
||||
|
||||
import okhttp3.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ProxyAuthenticator implements Authenticator {
|
||||
protected static final String HTTP_PROXY_USER = "http.proxyUser";
|
||||
protected static final String HTTP_PROXY_PASS = "http.proxyPassword";
|
||||
protected static final String HEADER_PROXY_AUTH = "Proxy-Authorization";
|
||||
|
||||
@Override
|
||||
public Request authenticate(Route route, Response response) throws IOException {
|
||||
final String httpProxyUser = System.getProperty(HTTP_PROXY_USER);
|
||||
final String httpProxyPass = System.getProperty(HTTP_PROXY_PASS);
|
||||
if (httpProxyUser != null && httpProxyPass != null) {
|
||||
String credential = Credentials.basic(httpProxyUser, httpProxyPass);
|
||||
return response.request().newBuilder()
|
||||
.header(HEADER_PROXY_AUTH, credential)
|
||||
.build();
|
||||
}
|
||||
return response.request().newBuilder().build();
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
package org.hl7.fhir.dstu3.utils.client.network;
|
||||
|
||||
import java.io.IOException;
|
||||
package org.hl7.fhir.utilities.http.okhttpimpl;
|
||||
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* An {@link Interceptor} for {@link okhttp3.OkHttpClient} that controls the number of times we retry a to execute a
|
||||
* given request, before reporting a failure. This includes unsuccessful return codes and timeouts.
|
||||
|
@ -26,7 +26,7 @@ public class RetryInterceptor implements Interceptor {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Response intercept(Interceptor.Chain chain) throws IOException {
|
||||
public Response intercept(Chain chain) throws IOException {
|
||||
Request request = chain.request();
|
||||
Response response = null;
|
||||
|
|
@ -178,7 +178,6 @@ public class I18nConstants {
|
|||
public static final String DIFFERENTIAL_WALKS_INTO____BUT_THE_BASE_DOES_NOT_AND_THERE_IS_NOT_A_SINGLE_FIXED_TYPE_THE_TYPE_IS__THIS_IS_NOT_HANDLED_YET = "Differential_walks_into____but_the_base_does_not_and_there_is_not_a_single_fixed_type_The_type_is__This_is_not_handled_yet";
|
||||
public static final String DISCRIMINATOR_BAD_PATH = "DISCRIMINATOR_BAD_PATH";
|
||||
public static final String DISCRIMINATOR__IS_BASED_ON_ELEMENT_EXISTENCE_BUT_SLICE__NEITHER_SETS_MIN1_OR_MAX0 = "Discriminator__is_based_on_element_existence_but_slice__neither_sets_min1_or_max0";
|
||||
public static final String DISCRIMINATOR__IS_BASED_ON_TYPE_BUT_SLICE__IN__HAS_MULTIPLE_TYPES = "Discriminator__is_based_on_type_but_slice__in__has_multiple_types";
|
||||
public static final String DISCRIMINATOR__IS_BASED_ON_TYPE_BUT_SLICE__IN__HAS_NO_TYPES = "Discriminator__is_based_on_type_but_slice__in__has_no_types";
|
||||
public static final String DISPLAY_NAME_FOR__SHOULD_BE_ONE_OF__INSTEAD_OF = "Display_Name_for__should_be_one_of__instead_of";
|
||||
public static final String DISPLAY_NAME_WS_FOR__SHOULD_BE_ONE_OF__INSTEAD_OF = "Display_Name_WS_for__should_be_one_of__instead_of";
|
||||
|
@ -475,7 +474,6 @@ public class I18nConstants {
|
|||
public static final String PROBLEM_PROCESSING_EXPRESSION__IN_PROFILE__PATH__ = "Problem_processing_expression__in_profile__path__";
|
||||
public static final String PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_A_TYPE_WITH_A_PROFILE__IN_PROFILE_ = "Profile_based_discriminators_must_have_a_type_with_a_profile__in_profile_";
|
||||
public static final String PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_A_TYPE__IN_PROFILE_ = "Profile_based_discriminators_must_have_a_type__in_profile_";
|
||||
public static final String PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_ONLY_ONE_TYPE__IN_PROFILE = "Profile_based_discriminators_must_have_only_one_type__in_profile";
|
||||
public static final String PROFILE_EXT_NOT_HERE = "Profile_EXT_Not_Here";
|
||||
public static final String PROFILE_VAL_MISSINGELEMENT = "Profile_VAL_MissingElement";
|
||||
public static final String PROFILE_VAL_NOTALLOWED = "Profile_VAL_NotAllowed";
|
||||
|
@ -1123,4 +1121,9 @@ public class I18nConstants {
|
|||
public static final String CODESYSTEM_CS_COMPLETE_AND_EMPTY = "CODESYSTEM_CS_COMPLETE_AND_EMPTY";
|
||||
public static final String VALIDATION_VAL_VERSION_NOHASH = "VALIDATION_VAL_VERSION_NOHASH";
|
||||
public static final String PRIMITIVE_TOO_SHORT = "PRIMITIVE_TOO_SHORT";
|
||||
public static final String CANONICAL_MULTIPLE_VERSIONS_KNOWN = "CANONICAL_MULTIPLE_VERSIONS_KNOWN";
|
||||
public static final String SD_PATH_NO_SLICING = "SD_PATH_NO_SLICING";
|
||||
public static final String SD_PATH_SLICING_DEPRECATED = "SD_PATH_SLICING_DEPRECATED";
|
||||
public static final String SD_PATH_NOT_VALID = "SD_PATH_NOT_VALID";
|
||||
public static final String SD_PATH_ERROR = "SD_PATH_ERROR";
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ import java.util.Date;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
|
||||
|
@ -24,7 +23,7 @@ import org.hl7.fhir.utilities.VersionUtilities;
|
|||
import org.hl7.fhir.utilities.http.HTTPAuthenticationMode;
|
||||
import org.hl7.fhir.utilities.http.HTTPResult;
|
||||
import org.hl7.fhir.utilities.http.ManagedWebAccess;
|
||||
import org.hl7.fhir.utilities.http.ManagedWebAccessBuilder;
|
||||
import org.hl7.fhir.utilities.http.ManagedWebAccessor;
|
||||
import org.hl7.fhir.utilities.json.model.JsonArray;
|
||||
import org.hl7.fhir.utilities.json.model.JsonObject;
|
||||
import org.hl7.fhir.utilities.json.model.JsonProperty;
|
||||
|
@ -176,13 +175,13 @@ public class PackageClient {
|
|||
}
|
||||
|
||||
private InputStream fetchUrl(String source, String accept) throws IOException {
|
||||
ManagedWebAccessBuilder client = ManagedWebAccess.builder().withAccept(accept);
|
||||
ManagedWebAccessor webAccessor = ManagedWebAccess.accessor();
|
||||
if (server.getAuthenticationMode() == HTTPAuthenticationMode.TOKEN) {
|
||||
client.withToken(server.getToken());
|
||||
webAccessor.withToken(server.getToken());
|
||||
} else if (server.getAuthenticationMode() == HTTPAuthenticationMode.BASIC) {
|
||||
client.withBasicAuth(server.getUsername(), server.getPassword());
|
||||
webAccessor.withBasicAuth(server.getUsername(), server.getPassword());
|
||||
}
|
||||
HTTPResult res = client.get(source);
|
||||
HTTPResult res = webAccessor.get(source, accept);
|
||||
res.checkThrowException();
|
||||
return new ByteArrayInputStream(res.getContent());
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import java.io.File;
|
|||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -232,6 +233,14 @@ public class FhirSettings {
|
|||
if (instance.fhirSettings.getPackageManagement() == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return List.of(instance.fhirSettings.getPackageManagement().getServers().toArray(new ServerDetailsPOJO[]{}));
|
||||
return Arrays.asList(instance.fhirSettings.getPackageManagement().getServers().toArray(new ServerDetailsPOJO[]{}));
|
||||
}
|
||||
|
||||
public static List<ServerDetailsPOJO> getTerminologyServers() {
|
||||
getInstance();
|
||||
if (instance.fhirSettings.getTerminologyServers() == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Arrays.asList(instance.fhirSettings.getTerminologyServers().getServers().toArray(new ServerDetailsPOJO[]{}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -179,8 +179,6 @@ Did_not_find_type_root_ = Did not find type root: {0}
|
|||
Differential_does_not_have_a_slice__b_of_____in_profile_ = Differential in profile {5} does not have a slice at {6} (on {0}, position {1} of {2} / {3} / {4})
|
||||
Differential_walks_into____but_the_base_does_not_and_there_is_not_a_single_fixed_type_The_type_is__This_is_not_handled_yet = Differential walks into ''{0} (@ {1})'', but the base does not, and there is not a single fixed type. The type is {2}. This is not handled yet
|
||||
Discriminator__is_based_on_element_existence_but_slice__neither_sets_min1_or_max0 = Discriminator ({0}) is based on element existence, but slice {1} neither sets min>=1 or max=0
|
||||
Discriminator__is_based_on_type_but_slice__in__has_multiple_types_one =
|
||||
Discriminator__is_based_on_type_but_slice__in__has_multiple_types_other = Discriminator ({1}) is based on type, but slice {2} in {3} has {0} types: {4}
|
||||
Discriminator__is_based_on_type_but_slice__in__has_no_types = Discriminator ({0}) is based on type, but slice {1} in {2} has no types
|
||||
Display_Name_WS_for__should_be_one_of__instead_of_one = Wrong whitespace in Display Name ''{4}'' for {1}#{2}. Valid display is {3} (for the language(s) ''{5}'')
|
||||
Display_Name_WS_for__should_be_one_of__instead_of_other = Wrong whitespace in Display Name ''{4}'' for {1}#{2}. Valid display is one of {0} choices: {3} (for the language(s) ''{5}'')
|
||||
|
@ -486,8 +484,6 @@ Profile___has_no_base_and_no_snapshot = Profile {0} ({1}) has no base and no sna
|
|||
Profile__does_not_match_for__because_of_the_following_profile_issues__ = Profile {0} does not match for {1} because of the following profile issues: {2}
|
||||
Profile_based_discriminators_must_have_a_type__in_profile_ = Profile based discriminators must have a type ({0} in profile {1})
|
||||
Profile_based_discriminators_must_have_a_type_with_a_profile__in_profile_ = Profile based discriminators must have a type with a profile ({0} in profile {1})
|
||||
Profile_based_discriminators_must_have_only_one_type__in_profile_one =
|
||||
Profile_based_discriminators_must_have_only_one_type__in_profile_other = Profile based discriminators must have only one type ({1} in profile {2}) but found {0} types
|
||||
QUESTIONNAIRE_QR_ITEM_BADOPTION_CS = The code provided {1} cannot be validated in the options value set ({2}) in the questionnaire because the system {0} could not be found
|
||||
QUESTIONNAIRE_Q_DERIVATION_TYPE_IGNORED = The derivation type ''{0}'' means that no derivation checking has been performed against this questionnaire
|
||||
QUESTIONNAIRE_Q_DERIVATION_TYPE_UNKNOWN = The derivation type ''{0}'' is unknown, which means that no derivation checking has been performed against this questionnaire
|
||||
|
@ -1091,7 +1087,7 @@ XHTML_XHTML_NS_InValid = Wrong namespace on the XHTML (''{0}'', should be ''{1}'
|
|||
XHTML_XHTML_Name_Invalid = Wrong name on the XHTML (''{0}'') - must start with div
|
||||
XSI_TYPE_UNNECESSARY = xsi:type is unnecessary at this point
|
||||
XSI_TYPE_WRONG = The xsi:type value ''{0}'' is wrong (should be ''{1}''). Note that xsi:type is unnecessary at this point
|
||||
_DT_Fixed_Wrong = Value is ''{0}'' but must be ''{1}''{2}
|
||||
_DT_Fixed_Wrong = Value is ''{0}'' but is fixed to ''{1}'' in the profile {2}
|
||||
_has_children__and_multiple_types__in_profile_ = {0} has children ({1}) and multiple types ({2}) in profile {3}
|
||||
_has_children__for_type__in_profile__but_cant_find_type = {0} has children ({1}) for type {2} in profile {3}, but can''t find type
|
||||
_has_no_children__and_no_types_in_profile_ = {0} has no children ({1}) and no types in profile {2}
|
||||
|
@ -1156,4 +1152,8 @@ SD_ED_ADDITIONAL_BINDING_USAGE_INVALID_TYPE = The Usage Context value must be of
|
|||
CODESYSTEM_CS_COMPLETE_AND_EMPTY = When a CodeSystem has content = ''complete'', it doesn't make sense for there to be no concepts defined
|
||||
VALIDATION_VAL_VERSION_NOHASH = Version ''{0}'' contains a ''#'', which as this character is used in some URLs to separate the version and the fragment id. When version does include '#', systems will not be able to parse the URL
|
||||
PRIMITIVE_TOO_SHORT = Value ''{0}'' is shorter than permitted minimum length of {1}
|
||||
|
||||
CANONICAL_MULTIPLE_VERSIONS_KNOWN = The version {2} for the {0} {1} is not known. These versions are known: {3}
|
||||
SD_PATH_SLICING_DEPRECATED = The discriminator type ''{0}'' has been deprecated. Use type=fixed with a pattern[x] instead
|
||||
SD_PATH_NOT_VALID = The discriminator path ''{0}'' does not appear to be valid for the element that is being sliced ''{1}''
|
||||
SD_PATH_ERROR = The discriminator path ''{0}'' does not appear to be valid for the element that is being sliced ''{1}'': {2}
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package org.hl7.fhir.utilities.http;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class HTTPHeaderTests {
|
||||
@Test
|
||||
void testHTTPHeaderEquals() {
|
||||
HTTPHeader header1 = new HTTPHeader("name", "value1");
|
||||
HTTPHeader sameAsHeader1 = new HTTPHeader("name", "value1");
|
||||
assertThat(header1).isEqualTo(sameAsHeader1);
|
||||
|
||||
HTTPHeader notSameNameAsHeader1 = new HTTPHeader("name2", "value1");
|
||||
assertThat(header1).isNotEqualTo(notSameNameAsHeader1);
|
||||
|
||||
HTTPHeader notSameValueAsHeader1 = new HTTPHeader("name", "value2");
|
||||
assertThat(header1).isNotEqualTo(notSameValueAsHeader1);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package org.hl7.fhir.utilities.http;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class ManagedFhirWebAccessorTests {
|
||||
final String expectedUserAgent = "dummy-agent";
|
||||
|
||||
@Test
|
||||
@DisplayName("Test default headers are added correctly.")
|
||||
void addDefaultAgentHeader() {
|
||||
HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com");
|
||||
|
||||
ManagedFhirWebAccessor builder = new ManagedFhirWebAccessor(expectedUserAgent, null);
|
||||
|
||||
HTTPRequest requestWithDefaultHeaders = builder.httpRequestWithDefaultHeaders(request);
|
||||
assertRequestContainsExpectedAgentHeader(requestWithDefaultHeaders);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test default headers are added correctly.")
|
||||
void addDefaultBasicHeader() {
|
||||
HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com");
|
||||
|
||||
ManagedFhirWebAccessor builder = new ManagedFhirWebAccessor(expectedUserAgent, null)
|
||||
.withBasicAuth("dummy-user", "dummy-password");
|
||||
|
||||
HTTPRequest requestWithManagedHeaders = builder.requestWithManagedHeaders(request);
|
||||
assertRequestContainsExpectedAgentHeader(requestWithManagedHeaders);
|
||||
|
||||
Assertions.assertNotNull(HTTPHeaderUtil.getSingleHeader(requestWithManagedHeaders.getHeaders(),"Authorization"), "Authorization header null.");
|
||||
}
|
||||
|
||||
private void assertRequestContainsExpectedAgentHeader(HTTPRequest requestWithDefaultHeaders) {
|
||||
Assertions.assertNotNull(HTTPHeaderUtil.getSingleHeader(requestWithDefaultHeaders.getHeaders(),"User-Agent"), "User-Agent header null.");
|
||||
Assertions.assertEquals(expectedUserAgent, HTTPHeaderUtil.getSingleHeader(requestWithDefaultHeaders.getHeaders(),"User-Agent"),
|
||||
"User-Agent header not populated with expected value \""+expectedUserAgent+"\".");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
package org.hl7.fhir.utilities.http;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import org.apache.commons.net.util.Base64;
|
||||
import org.hl7.fhir.utilities.settings.ServerDetailsPOJO;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class ManagedWebAccessAuthTests {
|
||||
|
||||
public static final String DUMMY_AGENT = "dummyAgent";
|
||||
public static final String DUMMY_USERNAME = "dummy1";
|
||||
public static final String DUMMY_PASSWORD = "pass1";
|
||||
|
||||
public static final String DUMMY_TOKEN = "dummyToken";
|
||||
private static final String DUMMY_API_KEY = "dummyApiKey";
|
||||
private MockWebServer server;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
setupMockServer();
|
||||
}
|
||||
|
||||
void setupMockServer() {
|
||||
server = new MockWebServer();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBaseCase() throws IOException, InterruptedException {
|
||||
HttpUrl serverUrl = server.url("blah/blah/blah?arg=blah");
|
||||
|
||||
server.enqueue(
|
||||
new MockResponse()
|
||||
.setBody("Dummy Response").setResponseCode(200)
|
||||
);
|
||||
|
||||
ManagedFhirWebAccessor builder = new ManagedFhirWebAccessor("dummyAgent", null);
|
||||
HTTPResult result = builder.httpCall(new HTTPRequest().withUrl(serverUrl.toString()).withMethod(HTTPRequest.HttpMethod.GET));
|
||||
|
||||
assertThat(result.getCode()).isEqualTo(200);
|
||||
assertThat(result.getContentAsString()).isEqualTo("Dummy Response");
|
||||
|
||||
RecordedRequest packageRequest = server.takeRequest();
|
||||
|
||||
assert packageRequest.getRequestUrl() != null;
|
||||
assertExpectedHeaders(packageRequest, serverUrl.url().toString(), "GET");
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBasicAuthCase() throws IOException, InterruptedException {
|
||||
|
||||
ManagedFhirWebAccessor builder = new ManagedFhirWebAccessor("dummyAgent", null).withBasicAuth("dummy1", "pass1");
|
||||
|
||||
testBasicServerAuth(builder);
|
||||
}
|
||||
|
||||
private void testBasicServerAuth(ManagedFhirWebAccessor builder) throws IOException, InterruptedException {
|
||||
HttpUrl serverUrl = server.url("blah/blah/blah?arg=blah");
|
||||
|
||||
server.enqueue(
|
||||
new MockResponse()
|
||||
.setBody("Dummy Response").setResponseCode(200)
|
||||
);
|
||||
HTTPResult result = builder.httpCall(new HTTPRequest().withUrl(serverUrl.toString()).withMethod(HTTPRequest.HttpMethod.GET));
|
||||
|
||||
assertThat(result.getCode()).isEqualTo(200);
|
||||
assertThat(result.getContentAsString()).isEqualTo("Dummy Response");
|
||||
|
||||
RecordedRequest packageRequest = server.takeRequest();
|
||||
|
||||
assert packageRequest.getRequestUrl() != null;
|
||||
assertExpectedHeaders(packageRequest, serverUrl.url().toString(), "GET");
|
||||
|
||||
byte[] b = Base64.encodeBase64((DUMMY_USERNAME + ":" + DUMMY_PASSWORD).getBytes(StandardCharsets.US_ASCII));
|
||||
String b64 = new String(b, StandardCharsets.US_ASCII);
|
||||
|
||||
assertThat(packageRequest.getHeader("Authorization")).isEqualTo("Basic " + b64);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTokenAuthCase() throws IOException, InterruptedException {
|
||||
|
||||
|
||||
ManagedFhirWebAccessor builder = new ManagedFhirWebAccessor("dummyAgent", null).withToken(DUMMY_TOKEN);
|
||||
|
||||
testTokenAuthCase(builder);
|
||||
}
|
||||
|
||||
private void testTokenAuthCase(ManagedFhirWebAccessor builder) throws IOException, InterruptedException {
|
||||
HttpUrl serverUrl = server.url("blah/blah/blah?arg=blah");
|
||||
|
||||
server.enqueue(
|
||||
new MockResponse()
|
||||
.setBody("Dummy Response").setResponseCode(200)
|
||||
);
|
||||
HTTPResult result = builder.httpCall(new HTTPRequest().withUrl(serverUrl.toString()).withMethod(HTTPRequest.HttpMethod.GET));
|
||||
|
||||
assertThat(result.getCode()).isEqualTo(200);
|
||||
assertThat(result.getContentAsString()).isEqualTo("Dummy Response");
|
||||
|
||||
RecordedRequest packageRequest = server.takeRequest();
|
||||
|
||||
assert packageRequest.getRequestUrl() != null;
|
||||
assertExpectedHeaders(packageRequest, serverUrl.url().toString(), "GET");
|
||||
|
||||
assertThat(packageRequest.getHeader("Authorization")).isEqualTo("Bearer " + DUMMY_TOKEN);
|
||||
}
|
||||
|
||||
private void assertExpectedHeaders(RecordedRequest packageRequest, String expectedUrl, String expectedHttpMethod) {
|
||||
assertThat(packageRequest.getRequestUrl().toString()).isEqualTo(expectedUrl);
|
||||
assertThat(packageRequest.getMethod()).isEqualTo(expectedHttpMethod);
|
||||
assertThat(packageRequest.getHeader("User-Agent")).isEqualTo(DUMMY_AGENT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApiKeyAuthCase() throws IOException, InterruptedException {
|
||||
|
||||
ManagedFhirWebAccessor builder = new ManagedFhirWebAccessor("dummyAgent", null).withApiKey(DUMMY_API_KEY);
|
||||
|
||||
testApiKeyAuthCase(builder);
|
||||
}
|
||||
|
||||
private void testApiKeyAuthCase(ManagedFhirWebAccessor builder) throws IOException, InterruptedException {
|
||||
HttpUrl serverUrl = server.url("blah/blah/blah?arg=blah");
|
||||
|
||||
server.enqueue(
|
||||
new MockResponse()
|
||||
.setBody("Dummy Response").setResponseCode(200)
|
||||
);
|
||||
HTTPResult result = builder.httpCall(new HTTPRequest().withUrl(serverUrl.toString()).withMethod(HTTPRequest.HttpMethod.GET));
|
||||
|
||||
assertThat(result.getCode()).isEqualTo(200);
|
||||
assertThat(result.getContentAsString()).isEqualTo("Dummy Response");
|
||||
|
||||
RecordedRequest packageRequest = server.takeRequest();
|
||||
|
||||
assert packageRequest.getRequestUrl() != null;
|
||||
assertExpectedHeaders(packageRequest, serverUrl.url().toString(), "GET");
|
||||
|
||||
assertThat(packageRequest.getHeader("Api-Key")).isEqualTo(DUMMY_API_KEY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicAuthFromSettings() throws IOException, InterruptedException {
|
||||
ManagedFhirWebAccessor builder = new ManagedFhirWebAccessor(
|
||||
"dummyAgent",
|
||||
List.of(getBasicAuthServerPojo()));
|
||||
|
||||
testBasicServerAuth(builder);
|
||||
}
|
||||
|
||||
private ServerDetailsPOJO getBasicAuthServerPojo() {
|
||||
return new ServerDetailsPOJO(
|
||||
server.url("").toString(),
|
||||
"basic",
|
||||
"dummyServerType",
|
||||
DUMMY_USERNAME,
|
||||
DUMMY_PASSWORD,
|
||||
null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTokenAuthFromSettings() throws IOException, InterruptedException {
|
||||
ManagedFhirWebAccessor builder = new ManagedFhirWebAccessor(
|
||||
"dummyAgent",
|
||||
List.of(getTokenAuthServerPojo()));
|
||||
|
||||
testTokenAuthCase(builder);
|
||||
}
|
||||
|
||||
private ServerDetailsPOJO getTokenAuthServerPojo() {
|
||||
return new ServerDetailsPOJO(
|
||||
server.url("").toString(),
|
||||
"token",
|
||||
"dummyServerType",
|
||||
null,
|
||||
null,
|
||||
DUMMY_TOKEN, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApiKeyAuthFromSettings() throws IOException, InterruptedException {
|
||||
ManagedFhirWebAccessor builder = new ManagedFhirWebAccessor(
|
||||
"dummyAgent",
|
||||
List.of(getApiKeyAuthServerPojo()));
|
||||
|
||||
testApiKeyAuthCase(builder);
|
||||
}
|
||||
|
||||
private ServerDetailsPOJO getApiKeyAuthServerPojo() {
|
||||
return new ServerDetailsPOJO(
|
||||
server.url("").toString(),
|
||||
"apikey",
|
||||
"dummyServerType",
|
||||
null,
|
||||
null,
|
||||
null, DUMMY_API_KEY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyAllowedPaths() {
|
||||
//TODO the allowed paths cannot be set for now, meaning all will be allowed.
|
||||
ManagedWebAccess.inAllowedPaths("http://www.anywhere.com");
|
||||
}
|
||||
}
|
|
@ -71,6 +71,7 @@ import org.hl7.fhir.utilities.SystemExitManager;
|
|||
import org.hl7.fhir.utilities.TimeTracker;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.VersionUtilities;
|
||||
import org.hl7.fhir.utilities.http.ManagedWebAccess;
|
||||
import org.hl7.fhir.utilities.settings.FhirSettings;
|
||||
import org.hl7.fhir.validation.cli.model.CliContext;
|
||||
import org.hl7.fhir.validation.cli.services.ValidationService;
|
||||
|
@ -151,6 +152,7 @@ public class ValidatorCli {
|
|||
if (cliContext.getFhirSettingsFile() != null) {
|
||||
FhirSettings.setExplicitFilePath(cliContext.getFhirSettingsFile());
|
||||
}
|
||||
ManagedWebAccess.loadFromFHIRSettings();
|
||||
|
||||
FileFormat.checkCharsetAndWarnIfNotUTF8(System.out);
|
||||
|
||||
|
|
|
@ -1047,7 +1047,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
}
|
||||
|
||||
private boolean check(String v1, String v2) {
|
||||
return v1 == null ? Utilities.noString(v1) : v1.equals(v2);
|
||||
boolean res = v1 == null ? Utilities.noString(v1) : v1.equals(v2);
|
||||
return res;
|
||||
}
|
||||
|
||||
private boolean checkAddress(List<ValidationMessage> errors, String path, Element focus, Address fixed, String fixedSource, boolean pattern, String context) {
|
||||
|
@ -3209,10 +3210,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
}
|
||||
|
||||
if (context.hasFixed()) {
|
||||
ok = checkFixedValue(errors, path, e, context.getFixed(), profile.getVersionedUrl(), context.getSliceName(), null, false, "") && ok;
|
||||
ok = checkFixedValue(errors, path, e, context.getFixed(), profile.getVersionedUrl(), context.getSliceName(), null, false, profile.getVersionedUrl()+"#"+context.getId()) && ok;
|
||||
}
|
||||
if (context.hasPattern()) {
|
||||
ok = checkFixedValue(errors, path, e, context.getPattern(), profile.getVersionedUrl(), context.getSliceName(), null, true, "") && ok;
|
||||
ok = checkFixedValue(errors, path, e, context.getPattern(), profile.getVersionedUrl(), context.getSliceName(), null, true, profile.getVersionedUrl()+"#"+context.getId()) && ok;
|
||||
}
|
||||
|
||||
if (ok && !ID_EXEMPT_LIST.contains(e.fhirType())) { // ids get checked elsewhere
|
||||
|
@ -5129,12 +5130,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
if ("0".equals(criteriaElement.getMax())) {
|
||||
expression.append(" and " + discriminator + ".empty()");
|
||||
} else if (s.getType() == DiscriminatorType.TYPE) {
|
||||
String type = null;
|
||||
if (!criteriaElement.getPath().contains("[") && discriminator.contains("[")) {
|
||||
discriminator = discriminator.substring(0, discriminator.indexOf('['));
|
||||
String lastNode = tail(discriminator);
|
||||
type = makeTypeForFHIRPath(criteriaElement.getPath()).substring(lastNode.length());
|
||||
String type = makeTypeForFHIRPath(criteriaElement.getPath()).substring(lastNode.length());
|
||||
expression.append(" and " + discriminator + " is " + type);
|
||||
} else if (!criteriaElement.hasType() || criteriaElement.getType().size() == 1) {
|
||||
String type = null;
|
||||
if (discriminator.contains("["))
|
||||
discriminator = discriminator.substring(0, discriminator.indexOf('['));
|
||||
if (criteriaElement.hasType()) {
|
||||
|
@ -5144,23 +5146,25 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
} else {
|
||||
throw new DefinitionException(context.formatMessage(I18nConstants.DISCRIMINATOR__IS_BASED_ON_TYPE_BUT_SLICE__IN__HAS_NO_TYPES, discriminator, ed.getId(), profile.getVersionedUrl()));
|
||||
}
|
||||
expression.append(" and " + discriminator + " is " + type);
|
||||
} else if (criteriaElement.getType().size() > 1) {
|
||||
throw new DefinitionException(context.formatMessagePlural(criteriaElement.getType().size(), I18nConstants.DISCRIMINATOR__IS_BASED_ON_TYPE_BUT_SLICE__IN__HAS_MULTIPLE_TYPES, discriminator, ed.getId(), profile.getVersionedUrl(), criteriaElement.typeSummary()));
|
||||
CommaSeparatedStringBuilder cb = new CommaSeparatedStringBuilder(" or ");
|
||||
for (TypeRefComponent tr : criteriaElement.getType()) {
|
||||
String type = makeTypeForFHIRPath(tr.getWorkingCode());
|
||||
cb.append(discriminator + " is " + type);
|
||||
}
|
||||
expression.append(" and (" + cb.toString()+")");
|
||||
} else
|
||||
throw new DefinitionException(context.formatMessage(I18nConstants.DISCRIMINATOR__IS_BASED_ON_TYPE_BUT_SLICE__IN__HAS_NO_TYPES, discriminator, ed.getId(), profile.getVersionedUrl()));
|
||||
if (discriminator.isEmpty()) {
|
||||
expression.append(" and $this is " + type);
|
||||
} else {
|
||||
expression.append(" and " + discriminator + " is " + type);
|
||||
}
|
||||
} else if (s.getType() == DiscriminatorType.PROFILE) {
|
||||
if (criteriaElement.getType().size() == 0) {
|
||||
throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_A_TYPE__IN_PROFILE_, criteriaElement.getId(), profile.getVersionedUrl()));
|
||||
}
|
||||
if (criteriaElement.getType().size() != 1) {
|
||||
throw new DefinitionException(context.formatMessagePlural(criteriaElement.getType().size(), I18nConstants.PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_ONLY_ONE_TYPE__IN_PROFILE, criteriaElement.getId(), profile.getVersionedUrl()));
|
||||
List<CanonicalType> list = new ArrayList<>();
|
||||
boolean ref = discriminator.endsWith(".resolve()") || discriminator.equals("resolve()");
|
||||
for (TypeRefComponent tr : criteriaElement.getType()) {
|
||||
list.addAll(ref ? tr.getTargetProfile() : tr.getProfile());
|
||||
}
|
||||
List<CanonicalType> list = discriminator.endsWith(".resolve()") || discriminator.equals("resolve()") ? criteriaElement.getType().get(0).getTargetProfile() : criteriaElement.getType().get(0).getProfile();
|
||||
if (list.size() == 0) {
|
||||
// we don't have to find something
|
||||
// throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_A_TYPE_WITH_A_PROFILE__IN_PROFILE_, criteriaElement.getId(), profile.getVersionedUrl()));
|
||||
|
@ -6357,10 +6361,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
ValidationInfo vi = element.addDefinition(profile, definition, mode);
|
||||
|
||||
if (definition.getFixed() != null) {
|
||||
ok = checkFixedValue(errors, stack.getLiteralPath(), element, definition.getFixed(), profile.getVersionedUrl(), definition.getSliceName(), null, false, "") && ok;
|
||||
ok = checkFixedValue(errors, stack.getLiteralPath(), element, definition.getFixed(), profile.getVersionedUrl(), definition.getSliceName(), null, false, profile.getVersionedUrl()+"#"+definition.getId()) && ok;
|
||||
}
|
||||
if (definition.getPattern() != null) {
|
||||
ok = checkFixedValue(errors, stack.getLiteralPath(), element, definition.getPattern(), profile.getVersionedUrl(), definition.getSliceName(), null, true, "") && ok;
|
||||
ok = checkFixedValue(errors, stack.getLiteralPath(), element, definition.getPattern(), profile.getVersionedUrl(), definition.getSliceName(), null, true, profile.getVersionedUrl()+"#"+definition.getId()) && ok;
|
||||
}
|
||||
|
||||
// get the list of direct defined children, including slices
|
||||
|
@ -6644,10 +6648,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
ok = checkPrimitive(valContext, errors, ei.getPath(), type, checkDefn, ei.getElement(), profile, localStack, stack, valContext.getRootResource()) && ok;
|
||||
} else {
|
||||
if (checkDefn.hasFixed()) {
|
||||
ok = checkFixedValue(errors, ei.getPath(), ei.getElement(), checkDefn.getFixed(), profile.getVersionedUrl(), checkDefn.getSliceName(), null, false, "") && ok;
|
||||
ok = checkFixedValue(errors, ei.getPath(), ei.getElement(), checkDefn.getFixed(), profile.getVersionedUrl(), checkDefn.getSliceName(), null, false, profile.getVersionedUrl()+"#"+definition.getId()) && ok;
|
||||
}
|
||||
if (checkDefn.hasPattern()) {
|
||||
ok = checkFixedValue(errors, ei.getPath(), ei.getElement(), checkDefn.getPattern(), profile.getVersionedUrl(), checkDefn.getSliceName(), null, true, "") && ok;
|
||||
ok = checkFixedValue(errors, ei.getPath(), ei.getElement(), checkDefn.getPattern(), profile.getVersionedUrl(), checkDefn.getSliceName(), null, true, profile.getVersionedUrl()+"#"+definition.getId()) && ok;
|
||||
}
|
||||
}
|
||||
if (type.equals("Identifier")) {
|
||||
|
@ -7600,7 +7604,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
private boolean valueMatchesCriteria(Element value, ElementDefinition criteria, StructureDefinition profile) throws FHIRException {
|
||||
if (criteria.hasFixed()) {
|
||||
List<ValidationMessage> msgs = new ArrayList<ValidationMessage>();
|
||||
checkFixedValue(msgs, "{virtual}", value, criteria.getFixed(), profile.getVersionedUrl(), "value", null, false, "");
|
||||
checkFixedValue(msgs, "{virtual}", value, criteria.getFixed(), profile.getVersionedUrl(), "value", null, false, profile.getVersionedUrl()+"#"+criteria.getId());
|
||||
return msgs.size() == 0;
|
||||
} else if (criteria.hasBinding() && criteria.getBinding().getStrength() == BindingStrength.REQUIRED && criteria.getBinding().hasValueSet()) {
|
||||
throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SLICE_MATCHING__SLICE_MATCHING_BY_VALUE_SET_NOT_DONE));
|
||||
|
|
|
@ -237,6 +237,17 @@ public class MeasureValidator extends BaseValidator {
|
|||
NodeStack ns = stack.push(m, -1, m.getProperty().getDefinition(), m.getProperty().getDefinition());
|
||||
hint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, m.line(), m.col(), ns.getLiteralPath(), Utilities.existsInList(mc.scoring(), "proportion", "ratio", "continuous-variable", "cohort"), I18nConstants.MEASURE_MR_M_SCORING_UNK);
|
||||
ok = validateMeasureReportGroups(hostContext, mc, errors, element, stack, inComplete) && ok;
|
||||
} else {
|
||||
if (measure.contains("|")) {
|
||||
List<Measure> versionList = context.fetchResourcesByUrl(Measure.class, measure.substring(0, measure.indexOf("|")));
|
||||
if (versionList != null && !versionList.isEmpty()) {
|
||||
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
|
||||
for (Measure mm : versionList) {
|
||||
b.append(mm.getVersion());
|
||||
}
|
||||
hint(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, m.line(), m.col(), stack.getLiteralPath(), msrc != null, I18nConstants.CANONICAL_MULTIPLE_VERSIONS_KNOWN, "Measure", measure, measure.substring(measure.indexOf("|")+1), b.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
|
|
|
@ -16,6 +16,7 @@ import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_50;
|
|||
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50;
|
||||
import org.hl7.fhir.exceptions.DefinitionException;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.exceptions.PathEngineException;
|
||||
import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
|
||||
import org.hl7.fhir.r5.elementmodel.Element;
|
||||
import org.hl7.fhir.r5.elementmodel.Manager;
|
||||
|
@ -23,6 +24,7 @@ import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
|
|||
import org.hl7.fhir.r5.extensions.ExtensionConstants;
|
||||
import org.hl7.fhir.r5.fhirpath.ExpressionNode;
|
||||
import org.hl7.fhir.r5.fhirpath.ExpressionNode.CollectionStatus;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRLexer.FHIRLexerException;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine.IssueMessage;
|
||||
import org.hl7.fhir.r5.fhirpath.TypeDetails;
|
||||
|
@ -193,7 +195,7 @@ public class StructureDefinitionValidator extends BaseValidator {
|
|||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
//e.printStackTrace();
|
||||
e.printStackTrace();
|
||||
rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.ERROR_GENERATING_SNAPSHOT, e.getMessage());
|
||||
ok = false;
|
||||
}
|
||||
|
@ -445,6 +447,44 @@ public class StructureDefinitionValidator extends BaseValidator {
|
|||
addCharacteristics(characteristics, path);
|
||||
characteristicsValid = true;
|
||||
}
|
||||
|
||||
if (element.hasChild("slicing")) {
|
||||
Element slicing = element.getNamedChild("slicing");
|
||||
NodeStack sStack = stack.push(slicing, -1, null, null);
|
||||
|
||||
// validating slicing.
|
||||
// slicing can only be present if base cardinality > 1, but we can't always know that on the differential - though we can look it up
|
||||
String tn = path.contains(".") ? path.substring(0, path.indexOf(".")) : path;
|
||||
StructureDefinition tsd = context.fetchTypeDefinition(tn);
|
||||
ElementDefinition ted = null;
|
||||
if (tsd != null) {
|
||||
ted = tsd.getSnapshot().getElementByPath(path);
|
||||
if (ted != null) {
|
||||
ok = rule(errors, "2022-11-02", IssueType.NOTFOUND, sStack, canSlice(ted), I18nConstants.SD_PATH_NO_SLICING, path) && ok;
|
||||
}
|
||||
}
|
||||
int i = 0;
|
||||
for (Element discriminator : slicing.getChildren("discriminator")) {
|
||||
NodeStack dStack = sStack.push(discriminator, i, null, null);
|
||||
String type = discriminator.getNamedChildValue("type");
|
||||
warning(errors, "2024-11-06", IssueType.BUSINESSRULE, dStack, !"pattern".equals(type), I18nConstants.SD_PATH_SLICING_DEPRECATED, type);
|
||||
String pathExp = discriminator.getNamedChildValue("path");
|
||||
if (ted != null) {
|
||||
TypeDetails td = getTypesForElement(elements, element, tn, tsd.getUrl());
|
||||
if (!td.isEmpty()) {
|
||||
List<IssueMessage> warnings = new ArrayList<IssueMessage>();
|
||||
try {
|
||||
TypeDetails eval = fpe.checkOnTypes(this, tn, td, fpe.parse(pathExp), warnings, true);
|
||||
if (eval.isEmpty()) {
|
||||
ok = rule(errors, "2024-11-06", IssueType.INVALID, dStack, false, I18nConstants.SD_PATH_NOT_VALID, pathExp, path) && ok;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ok = rule(errors, "2024-11-06", IssueType.INVALID, dStack, false, I18nConstants.SD_PATH_ERROR, pathExp, path, e.getMessage()) && ok;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!snapshot && (element.hasChild("fixed") || element.hasChild("pattern")) && base != null) {
|
||||
ElementDefinition ed = getDefinitionFromBase(base, element.getNamedChildValue("id"), element.getNamedChildValue("path"));
|
||||
|
@ -599,6 +639,10 @@ public class StructureDefinitionValidator extends BaseValidator {
|
|||
return ok;
|
||||
}
|
||||
|
||||
private boolean canSlice(ElementDefinition ted) {
|
||||
return !("1".equals(ted.getMax())) || ted.getPath().contains("[x]");
|
||||
}
|
||||
|
||||
private boolean checkTypeParameters(List<ValidationMessage> errors, NodeStack stack, Element typeE, String tc, StructureDefinition tsd, String path, StructureDefinition sd) {
|
||||
boolean ok = true;
|
||||
if (tsd.hasExtension(ToolingExtensions.EXT_TYPE_PARAMETER)) {
|
||||
|
|
|
@ -4,7 +4,6 @@ import java.io.BufferedReader;
|
|||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
|
@ -26,7 +25,6 @@ import java.util.TimeZone;
|
|||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import org.hl7.fhir.convertors.conv40_50.VersionConvertor_40_50;
|
||||
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50;
|
||||
import org.hl7.fhir.convertors.txClient.TerminologyClientFactory;
|
||||
import org.hl7.fhir.exceptions.DefinitionException;
|
||||
|
@ -41,19 +39,14 @@ import org.hl7.fhir.r5.model.ValueSet;
|
|||
import org.hl7.fhir.r5.terminologies.client.ITerminologyClient;
|
||||
import org.hl7.fhir.r5.test.utils.CompareUtilities;
|
||||
import org.hl7.fhir.r5.utils.client.EFhirClientException;
|
||||
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
|
||||
import org.hl7.fhir.utilities.FhirPublication;
|
||||
import org.hl7.fhir.utilities.TextFile;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.VersionUtil;
|
||||
import org.hl7.fhir.utilities.*;
|
||||
import org.hl7.fhir.utilities.filesystem.ManagedFileAccess;
|
||||
import org.hl7.fhir.utilities.http.HTTPHeader;
|
||||
import org.hl7.fhir.utilities.json.JsonException;
|
||||
import org.hl7.fhir.utilities.json.model.JsonArray;
|
||||
import org.hl7.fhir.utilities.json.model.JsonObject;
|
||||
import org.hl7.fhir.utilities.json.parser.JsonParser;
|
||||
|
||||
import okhttp3.internal.http2.Header;
|
||||
|
||||
public class TxTester {
|
||||
|
||||
|
||||
|
@ -134,7 +127,7 @@ public class TxTester {
|
|||
return true;
|
||||
} else {
|
||||
System.out.println(software+" did not pass all HL7 terminology service tests (modes "+m+", tests v"+loadVersion()+", runner v"+VersionUtil.getBaseVersion()+")");
|
||||
System.out.println("Failed Tests: "+CommaSeparatedStringBuilder.join(",", fails ));
|
||||
System.out.println("Failed Tests: "+ CommaSeparatedStringBuilder.join(",", fails ));
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
|
@ -250,12 +243,12 @@ public class TxTester {
|
|||
outputT.add("name", test.asString("name"));
|
||||
if (Utilities.noString(filter) || filter.equals("*") || test.asString("name").contains(filter)) {
|
||||
System.out.print(" Test "+test.asString("name")+": ");
|
||||
Header header = null;
|
||||
HTTPHeader header = null;
|
||||
try {
|
||||
if (test.has("header")) {
|
||||
JsonObject hdr = test.getJsonObject("header");
|
||||
if (hdr.has("mode") && modes.contains(hdr.asString("mode"))) {
|
||||
header = new Header(hdr.asString("name"), hdr.asString("value"));
|
||||
header = new HTTPHeader(hdr.asString("name"), hdr.asString("value"));
|
||||
tx.getClientHeaders().addHeader(header);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import org.hl7.fhir.utilities.http.ManagedWebAccess;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
public class ManagedWebAccessAuthTests {
|
||||
@BeforeClass
|
||||
public static void setUp() {
|
||||
ManagedWebAccess.setUserAgent("hapi-fhir-testing-client");
|
||||
}
|
||||
}
|
2
pom.xml
2
pom.xml
|
@ -434,7 +434,7 @@
|
|||
while if true it will use an executable. -->
|
||||
<fork>true</fork>
|
||||
<meminitial>512m</meminitial>
|
||||
<maxmem>768m</maxmem>
|
||||
<maxmem>1000m</maxmem>
|
||||
<debug>true</debug>
|
||||
<compilerArgs>
|
||||
<arg>-J-XX:MaxRAMPercentage=50.0</arg>
|
||||
|
|
|
@ -28,7 +28,7 @@ jobs:
|
|||
options: '--settings $(Agent.TempDirectory)/settings.xml -Dmaven.repo.local=$(MAVEN_CACHE_FOLDER) -DskipTests -DdeployToSonatype -P CHECKSTYLE'
|
||||
${{ else }}:
|
||||
options: '-Dmaven.repo.local=$(MAVEN_CACHE_FOLDER) -DskipTests -P CHECKSTYLE'
|
||||
mavenOptions: '-Xmx768m'
|
||||
mavenOptions: '-Xmx1G'
|
||||
javaHomeOption: 'JDKVersion'
|
||||
jdkVersionOption: '1.11'
|
||||
jdkArchitectureOption: 'x64'
|
||||
|
|
Loading…
Reference in New Issue