From 59fe0c6d20a6f3e87ac66726430252178e5e9d53 Mon Sep 17 00:00:00 2001
From: dotasek
Date: Fri, 8 Nov 2024 16:42:26 -0500
Subject: [PATCH] Manage FhirRequestBuilders via ManagedWebAccess (#1788)
* WIP start moving FhirRequestBuilder to ManagedWebAccess
* Update PECodeGenerator.java
* WIP keep moving FhirRequestBuilder to ManagedWebAccess + fix some tests
* WIP move logging, retry, timeout and proxy
* WIP move accept to parameter
* WIP some HTTP Header refactoring, rename FhirRequest to HTTPRequest
* WIP fix removed import
* Tidy up code
***NO_CI***
* Fix for renamed class
* Change mem settings for build
* Adjust pipeline mem again
* Apply changes to r4b
* Actually add headers from HTTPRequest to OkHttp request
* Use ManagedFhirWebAccess in r4
* Removing leftover okttp usage
* Remove unused classes and okhttp usages and add tests for r4
* Make dstu3 use ManagedFhirWebAccess
* Make sure we get response headers
* Add test coverage (some failing) for DSTU2 ClientUtils
* Add FHIRToolingClient tests
* Use ManagedFhirWebAccess for DSTU2
* WIP restore format header tests - turns out they break
* Fix format header issues
* Switch to static fhirBuilder for all FhirRequestBuilders
+ Fix null user agent issue
+ Load ManagedWebAccess from FHIR settings
+ Add terminology servers to FhirSettings
* fix slicing by type and profile to allow multiple options per slice
* List measure choices when a match by version can't be found
* Validate fhirpath expression in slice discriminators
* Remove conflicting authorization header setting methods
* fix VSAC importer for changes to ManagedWebAccess
* Tests 1
* Rename ...AccessBuilder classes to ...Accessor
* Fix api-key token mixup, more tests
* Resolve all deletions enclosed in FIXME + fix headers in dstu2 postfeed
* More TODO erasures. Plus found missing functionality
* Move tests to utilities. Clarify missing functionality
* Clean up comments and JavaDoc
* Final rename
* update vsac access code
* Fix get bytes for -1 or chunked content
---------
Co-authored-by: Jens Kristian Villadsen
Co-authored-by: Grahame Grieve
Co-authored-by: Grahame Grieve
---
RELEASE_NOTES.md | 3 +
.../fhir/convertors/misc/ICD11Generator.java | 3 +-
.../fhir/convertors/misc/VSACImporter.java | 9 +-
org.hl7.fhir.dstu2/pom.xml | 24 +
.../fhir/dstu2/utils/client/ClientUtils.java | 485 ++++++------------
.../dstu2/utils/client/FHIRToolingClient.java | 354 ++-----------
.../dstu2/utils/client/ResourceAddress.java | 5 +-
.../dstu2/utils/client/ResourceRequest.java | 15 +-
.../dstu2/utils/client/ClientUtilsTest.java | 350 +++++++++++++
.../utils/client/FHIRToolingClientTest.java | 166 ++++++
org.hl7.fhir.dstu3/pom.xml | 13 +
.../dstu3/utils/client/FHIRToolingClient.java | 151 ++----
.../dstu3/utils/client/network/Client.java | 105 ++--
.../utils/client/network/ClientHeaders.java | 37 +-
.../client/network/FhirRequestBuilder.java | 247 ++-------
.../utils/client/FhirToolingClientTest.java | 225 ++++++++
.../utils/client/network/ClientTest.java | 169 ++++++
.../network/FhirRequestBuilderTests.java | 167 +++---
org.hl7.fhir.r4/pom.xml | 13 +-
.../r4/utils/client/FHIRToolingClient.java | 165 ++----
.../fhir/r4/utils/client/network/Client.java | 137 ++---
.../utils/client/network/ClientHeaders.java | 32 +-
.../network/FhirLoggingInterceptor.java | 74 ---
.../client/network/FhirRequestBuilder.java | 217 +++-----
.../client/network/RetryInterceptor.java | 67 ---
.../utils/client/FhirToolingClientTest.java | 224 ++++++++
.../client/network/ClientHeadersTest.java | 104 ++++
.../r4/utils/client/network/ClientTest.java | 140 +++++
.../network/FhirRequestBuilderTest.java | 128 +++++
org.hl7.fhir.r4b/pom.xml | 6 +
.../TerminologyCacheManager.java | 12 +-
.../r4b/utils/client/FHIRToolingClient.java | 136 ++---
.../fhir/r4b/utils/client/network/Client.java | 151 +++---
.../utils/client/network/ClientHeaders.java | 35 +-
.../network/FhirLoggingInterceptor.java | 69 ---
.../client/network/FhirRequestBuilder.java | 192 ++-----
.../client/network/RetryInterceptor.java | 67 ---
.../utils/client/FHIRToolingClientTest.java | 84 +--
.../client/network/ClientHeadersTest.java | 24 +-
.../network/FhirRequestBuilderTest.java | 87 ++--
org.hl7.fhir.r5/pom.xml | 6 +
.../hl7/fhir/r5/fhirpath/FHIRPathEngine.java | 11 +-
.../TerminologyCacheManager.java | 12 +-
.../client/TerminologyClientR5.java | 4 -
.../r5/utils/client/FHIRToolingClient.java | 119 ++---
.../fhir/r5/utils/client/network/Client.java | 176 +++----
.../utils/client/network/ClientHeaders.java | 40 +-
.../network/FhirLoggingInterceptor.java | 70 ---
.../client/network/FhirRequestBuilder.java | 230 +++------
.../client/network/RetryInterceptor.java | 63 ---
.../utils/client/FHIRToolingClientTest.java | 106 ++--
.../client/network/ClientHeadersTest.java | 25 +-
.../network/FhirRequestBuilderTest.java | 92 ++--
.../hl7/fhir/utilities/http/HTTPHeader.java | 21 +
.../fhir/utilities/http/HTTPHeaderUtil.java | 45 ++
.../hl7/fhir/utilities/http/HTTPRequest.java | 54 ++
.../hl7/fhir/utilities/http/HTTPResult.java | 49 +-
.../http/ManagedFhirWebAccessor.java | 170 ++++++
.../fhir/utilities/http/ManagedWebAccess.java | 73 ++-
.../utilities/http/ManagedWebAccessUtils.java | 17 +
...ssBuilder.java => ManagedWebAccessor.java} | 125 ++---
.../http/ManagedWebAccessorBase.java | 78 +++
.../fhir/utilities/http/SimpleHTTPClient.java | 27 +-
.../http/okhttpimpl/LoggingInterceptor.java | 69 +++
.../http/okhttpimpl/ProxyAuthenticator.java | 24 +
.../http/okhttpimpl}/RetryInterceptor.java | 8 +-
.../fhir/utilities/i18n/I18nConstants.java | 7 +-
.../hl7/fhir/utilities/npm/PackageClient.java | 11 +-
.../fhir/utilities/settings/FhirSettings.java | 11 +-
.../src/main/resources/Messages.properties | 12 +-
.../fhir/utilities/http/HTTPHeaderTests.java | 20 +
.../http/ManagedFhirWebAccessorTests.java | 40 ++
.../http/ManagedWebAccessAuthTests.java | 216 ++++++++
.../org/hl7/fhir/validation/ValidatorCli.java | 2 +
.../instance/InstanceValidator.java | 42 +-
.../instance/type/MeasureValidator.java | 11 +
.../type/StructureDefinitionValidator.java | 46 +-
.../hl7/fhir/validation/special/TxTester.java | 17 +-
.../test/java/ManagedWebAccessAuthTests.java | 9 +
pom.xml | 2 +-
setup-and-cache-job-template.yml | 2 +-
81 files changed, 3871 insertions(+), 2983 deletions(-)
create mode 100644 org.hl7.fhir.dstu2/src/test/java/org/hl7/fhir/dstu2/utils/client/ClientUtilsTest.java
create mode 100644 org.hl7.fhir.dstu2/src/test/java/org/hl7/fhir/dstu2/utils/client/FHIRToolingClientTest.java
create mode 100644 org.hl7.fhir.dstu3/src/test/java/org/hl7/fhir/dstu3/utils/client/FhirToolingClientTest.java
create mode 100644 org.hl7.fhir.dstu3/src/test/java/org/hl7/fhir/dstu3/utils/client/network/ClientTest.java
delete mode 100644 org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/FhirLoggingInterceptor.java
delete mode 100644 org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/RetryInterceptor.java
create mode 100644 org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/FhirToolingClientTest.java
create mode 100644 org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/network/ClientHeadersTest.java
create mode 100644 org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/network/ClientTest.java
create mode 100644 org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilderTest.java
delete mode 100644 org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/FhirLoggingInterceptor.java
delete mode 100644 org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/RetryInterceptor.java
delete mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/FhirLoggingInterceptor.java
delete mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/RetryInterceptor.java
create mode 100644 org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/HTTPHeader.java
create mode 100644 org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/HTTPHeaderUtil.java
create mode 100644 org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/HTTPRequest.java
create mode 100644 org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessor.java
create mode 100644 org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessUtils.java
rename org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/{ManagedWebAccessBuilder.java => ManagedWebAccessor.java} (54%)
create mode 100644 org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessorBase.java
create mode 100644 org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/okhttpimpl/LoggingInterceptor.java
create mode 100644 org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/okhttpimpl/ProxyAuthenticator.java
rename {org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network => org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/okhttpimpl}/RetryInterceptor.java (94%)
create mode 100644 org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/http/HTTPHeaderTests.java
create mode 100644 org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessorTests.java
create mode 100644 org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/http/ManagedWebAccessAuthTests.java
create mode 100644 org.hl7.fhir.validation/src/test/java/ManagedWebAccessAuthTests.java
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 2eb0724124..7ab9bcb1d0 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -6,6 +6,9 @@
* Fix problem validating json-property-key value pairs
* Fix special case r5 loading of terminology to fix validation error on ExampleScenario
* Improve handling of JSON format errors
+* Fix slicing by type and profile to allow multiple options per slice
+* List measure choices when a match by version can't be found
+* Validate fhirpath expression in slice discriminators
## Other code changes
diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/ICD11Generator.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/ICD11Generator.java
index 2999ec0127..0e7a391d2e 100644
--- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/ICD11Generator.java
+++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/ICD11Generator.java
@@ -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 @@ private CodeSystem makeEntityCodeSystem() {
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());
}
diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/VSACImporter.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/VSACImporter.java
index cfe79e7733..13fb36c81c 100644
--- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/VSACImporter.java
+++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/VSACImporter.java
@@ -32,6 +32,7 @@
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 VSACImporter() throws FHIRException, IOException {
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 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 @@ private void process(String source, String dest, String apiKey, boolean onlyNew,
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));
}
diff --git a/org.hl7.fhir.dstu2/pom.xml b/org.hl7.fhir.dstu2/pom.xml
index 1dc4fd998b..881479b405 100644
--- a/org.hl7.fhir.dstu2/pom.xml
+++ b/org.hl7.fhir.dstu2/pom.xml
@@ -28,6 +28,12 @@
org.hl7.fhir.utilities
+
+ org.projectlombok
+ lombok
+ provided
+
+
org.fhir
@@ -81,6 +87,24 @@
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+ com.squareup.okhttp3
+ mockwebserver
+ true
+ test
+
+
+ com.squareup.okio
+ okio
+ true
+ test
+
com.fasterxml.jackson.core
jackson-databind
diff --git a/org.hl7.fhir.dstu2/src/main/java/org/hl7/fhir/dstu2/utils/client/ClientUtils.java b/org.hl7.fhir.dstu2/src/main/java/org/hl7/fhir/dstu2/utils/client/ClientUtils.java
index e4261fd9c3..c8dcb0bfcf 100644
--- a/org.hl7.fhir.dstu2/src/main/java/org/hl7/fhir/dstu2/utils/client/ClientUtils.java
+++ b/org.hl7.fhir.dstu2/src/main/java/org/hl7/fhir/dstu2/utils/client/ClientUtils.java
@@ -33,42 +33,21 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
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 org.apache.commons.codec.binary.Base64;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+import lombok.Getter;
+import lombok.Setter;
+
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 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
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 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
* @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;
- private ToolingClientLogger logger;
- private int retryCount;
- private String userAgent;
- private String acceptLang;
- private String contentLang;
-
- 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;
- }
+ @Setter
+ @Getter
+ private ToolingClientLogger logger;
- public void setUsername(String username) {
- this.username = username;
- }
+ @Setter
+ @Getter
+ private int retryCount;
- public String getPassword() {
- return password;
- }
+ @Getter
+ @Setter
+ private String userAgent;
+ @Setter
+ private String acceptLanguage;
+ @Setter
+ private String contentLanguage;
+ private final TimeUnit timeoutUnit = TimeUnit.MILLISECONDS;
- public void setPassword(String password) {
- this.password = password;
+ protected ManagedFhirWebAccessor getManagedWebAccessor() {
+ return ManagedWebAccess.fhirAccessor().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger);
}
public ResourceRequest issueOptionsRequest(URI optionsUri, String resourceFormat,
@@ -146,8 +112,10 @@ public ResourceRequest issueOptionsRequest(URI optionsUr
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 ResourceRequest issueGetResourceRequest(URI resourceUri, String resourceFormat,
@@ -155,17 +123,23 @@ public ResourceRequest issueGetResourceRequest(URI resou
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 ResourceRequest issuePutRequest(URI resourceUri, byte[] payload, String resourceFormat,
- List headers, int timeoutLoading) {
+ Iterable 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 ResourceRequest issuePutRequest(URI resourceUri, byte[] payload, String resourceFormat,
@@ -173,17 +147,25 @@ public ResourceRequest issuePutRequest(URI resourceUri,
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 ResourceRequest issuePostRequest(URI resourceUri, byte[] payload,
- String resourceFormat, List headers, int timeoutLoading) {
+ String resourceFormat, Iterable 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 ResourceRequest issuePostRequest(URI resourceUri, byte[] payload,
@@ -195,30 +177,26 @@ public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat) {
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 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 headers = getFhirHeaders(httpRequest, resourceFormat);
+ HTTPResult response = sendPayload(httpRequest.withHeaders(headers));
return unmarshalFeed(response, resourceFormat);
}
@@ -226,9 +204,12 @@ public boolean issueDeleteRequest(URI resourceUri) {
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 boolean issueDeleteRequest(URI resourceUri) {
* Request/Response Helper methods
***********************************************************/
- protected ResourceRequest issueResourceRequest(String resourceFormat, HttpUriRequest request,
+ protected ResourceRequest 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
- */
- protected ResourceRequest issueResourceRequest(String resourceFormat, HttpUriRequest request,
- byte[] payload, int timeoutLoading) {
- return issueResourceRequest(resourceFormat, request, payload, null, 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 ResourceRequest issueResourceRequest(String resourceFormat, HttpUriRequest request,
- byte[] payload, List headers, int timeoutLoading) {
+ protected ResourceRequest issueResourceRequest(String resourceFormat, HTTPRequest request,
+ @Nonnull Iterable 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);
+ Iterable configuredHeaders = getFhirHeaders(request, resourceFormat, headers);
+ try {
+
+ HTTPResult response = getManagedWebAccessor().httpCall(request.withHeaders(configuredHeaders));
+ T resource = unmarshalReference(response, resourceFormat);
+ return new ResourceRequest(resource, response.getCode(), getLocationHeader(response.getHeaders()));
+ } catch (IOException ioe) {
+ throw new EFhirClientException("Error sending HTTP Post/Put Payload to " + "??" + ": " + ioe.getMessage(),
+ ioe);
}
- T resource = unmarshalReference(response, resourceFormat);
- return new ResourceRequest(resource, response.getStatusLine().getStatusCode(), getLocationHeader(response));
}
/**
- * Method adds required request headers. TODO handle JSON request as well.
+ * Get required headers for FHIR requests.
*
- * @param request
+ * @param httpRequest the request
+ * @param format the expected format
*/
- protected void configureFhirRequest(HttpRequest request, String format) {
- configureFhirRequest(request, format, null);
+ protected Iterable getFhirHeaders(HTTPRequest httpRequest, String format) {
+ return getFhirHeaders(httpRequest, format, null);
}
/**
- * Method adds required request headers. TODO handle JSON request as well.
+ * Get required headers for FHIR requests.
*
- * @param request
+ * @param httpRequest the request
+ * @param format the expected format
+ * @param headers any additional headers to add
*/
- protected void configureFhirRequest(HttpRequest request, String format, List headers) {
+ protected Iterable getFhirHeaders(HTTPRequest httpRequest, String format, Iterable headers) {
+ List configuredHeaders = new ArrayList<>();
if (!Utilities.noString(userAgent)) {
- request.addHeader("User-Agent", userAgent);
+ configuredHeaders.add(new HTTPHeader("User-Agent", userAgent));
}
- if (!Utilities.noString(acceptLang)) {
- request.addHeader("Accept-Language", acceptLang);
+ if (!Utilities.noString(acceptLanguage)) {
+ configuredHeaders.add(new HTTPHeader("Accept-Language", acceptLanguage));
}
- if (!Utilities.noString(contentLang)) {
- request.addHeader("Content-Language", acceptLang);
+ if (!Utilities.noString(contentLanguage)) {
+ configuredHeaders.add(new HTTPHeader("Content-Language", acceptLanguage));
}
- if (format != null) {
- request.addHeader("Accept", format);
- request.addHeader("Content-Type", format + ";charset=" + DEFAULT_CHARSET);
- }
+ Iterable resourceFormatHeaders = getResourceFormatHeaders(httpRequest, format);
+ resourceFormatHeaders.forEach(configuredHeaders::add);
+
if (headers != null) {
- for (Header header : headers) {
- request.addHeader(header);
- }
+ headers.forEach(configuredHeaders::add);
}
- setAuth(request);
+ return configuredHeaders;
}
- /**
- * 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");
+ protected static List getResourceFormatHeaders(HTTPRequest httpRequest, String format) {
+ List 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));
}
- 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;
+ return headers;
}
/**
*
- * @param request
- * @param payload
- * @return
+ * @param request The request to be sent
+ * @return The response from the server
*/
- protected HttpResponse sendRequest(HttpUriRequest request) {
+ protected HTTPResult sendRequest(HTTPRequest request) {
if (FhirSettings.isProhibitNetworkAccess()) {
throw new FHIRException("Network Access is prohibited in this context");
}
- HttpResponse response = null;
+ HTTPResult response = null;
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);
+
+ 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 unmarshalReference(HttpResponse response, String format) {
+ protected 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 @@ protected T unmarshalReference(HttpResponse response, Strin
/**
* 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 @@ else if (rf instanceof OperationOutcome && hasError((OperationOutcome) rf)) {
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 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 @@ protected IParser getParser(String format) {
public Bundle issuePostFeedRequest(URI resourceUri, Map 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 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 @@ private byte[] encodeFormSubmission(Map parameters, String resou
}
/**
- * 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 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 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 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 @@ protected String writeInputStreamAsString(InputStream instream) {
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;
- }
}
\ No newline at end of file
diff --git a/org.hl7.fhir.dstu2/src/main/java/org/hl7/fhir/dstu2/utils/client/FHIRToolingClient.java b/org.hl7.fhir.dstu2/src/main/java/org/hl7/fhir/dstu2/utils/client/FHIRToolingClient.java
index ff5900da4a..5f42d3d2d3 100644
--- a/org.hl7.fhir.dstu2/src/main/java/org/hl7/fhir/dstu2/utils/client/FHIRToolingClient.java
+++ b/org.hl7.fhir.dstu2/src/main/java/org/hl7/fhir/dstu2/utils/client/FHIRToolingClient.java
@@ -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;
- // Pass enpoint for client - URI
- public FHIRToolingClient(String baseServiceUrl, String userAgent) throws URISyntaxException {
- preferredResourceFormat = ResourceFormat.RESOURCE_XML;
- utils.setUserAgent(userAgent);
- detectProxy();
- initialize(baseServiceUrl);
+ protected ClientUtils getClientUtils() {
+ return new ClientUtils();
}
- public FHIRToolingClient(String baseServiceUrl, String userAgent, String username, String password)
- throws URISyntaxException {
+ // Pass enpoint for client - URI
+ public FHIRToolingClient(String baseServiceUrl, String userAgent) throws URISyntaxException {
preferredResourceFormat = ResourceFormat.RESOURCE_XML;
+ utils = getClientUtils();
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 Resource update(Resource resource) {
recordUse();
ResourceRequest result = null;
try {
- List 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 T update(Class resourceClass, T resource, String
recordUse();
ResourceRequest result = null;
try {
- List 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 T update(Class resourceClass, T resource, String
return result.getPayload();
}
-//
-// public boolean delete(Class 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 OperationOutcome create(Class resourceClass, T resource) {
-// ResourceRequest resourceRequest = null;
-// try {
-// List 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 Bundle history(Calendar lastUpdate, Class 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 Bundle history(Date lastUpdate, Class 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 Bundle history(Calendar lastUpdate, Class 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 Bundle history(Date lastUpdate, Class 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 Bundle history(Class 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 Bundle history(Class 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 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 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 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 Bundle search(Class resourceClass, Map 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 Bundle searchPost(Class resourceClass, T resource, Map parameters) {
-// Bundle searchResults = null;
-// try {
-// searchResults = utils.issuePostFeedRequest(resourceAddress.resolveSearchUri(resourceClass, new HashMap()), parameters, "src", resource, getPreferredResourceFormat());
-// } catch (Exception e) {
-// handleException("Error performing search with parameters " + parameters, e);
-// }
-// return searchResults;
-// }
+
public Parameters operateType(Class resourceClass, String name, Parameters params) {
recordUse();
@@ -588,107 +400,15 @@ public OperationOutcome validate(Class resourceClass, T
return (OperationOutcome) result.getPayload();
}
- /*
- * change to meta operations
- *
- * public List 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 List getAllTagsForResourceType(Class
- * 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 List getTagsForReference(Class
- * 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 List getTagsForResourceVersion(Class
- * 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 boolean deleteTagsForReference(Class
- * 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 boolean
- * deleteTagsForResourceVersion(Class resourceClass, String id, List
- * 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 List createTags(List tags,
- * Class 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 List createTags(List tags,
- * Class 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 List deleteTags(List tags,
- * Class 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 @@ protected void handleException(String message, Exception e) throws EFhirClientEx
* 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 Parameters translate(Parameters p) {
public ValueSet expandValueset(ValueSet source, Parameters expParams) {
recordUse();
- List headers = null;
+
Parameters p = expParams == null ? new Parameters() : expParams.copy();
p.addParameter().setName("valueSet").setResource(source);
ResourceRequest 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 ConceptMap initializeClosure(String name) {
recordUse();
Parameters params = new Parameters();
params.addParameter().setName("name").setValue(new StringType(name));
- List headers = null;
+
ResourceRequest result = utils.issuePostRequest(
resourceAddress.resolveOperationUri(null, "closure", new HashMap()),
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 ConceptMap updateClosure(String name, Coding coding) {
Parameters params = new Parameters();
params.addParameter().setName("name").setValue(new StringType(name));
params.addParameter().setName("concept").setValue(coding);
- List headers = null;
+
ResourceRequest result = utils.issuePostRequest(
resourceAddress.resolveOperationUri(null, "closure", new HashMap()),
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 void setTimeout(int timeout) {
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)
diff --git a/org.hl7.fhir.dstu2/src/main/java/org/hl7/fhir/dstu2/utils/client/ResourceAddress.java b/org.hl7.fhir.dstu2/src/main/java/org/hl7/fhir/dstu2/utils/client/ResourceAddress.java
index 7ff64f2bcf..e58b6fdaa0 100644
--- a/org.hl7.fhir.dstu2/src/main/java/org/hl7/fhir/dstu2/utils/client/ResourceAddress.java
+++ b/org.hl7.fhir.dstu2/src/main/java/org/hl7/fhir/dstu2/utils/client/ResourceAddress.java
@@ -233,9 +233,8 @@ public URI resolveMetadataTxCaps() {
/**
* 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);
diff --git a/org.hl7.fhir.dstu2/src/main/java/org/hl7/fhir/dstu2/utils/client/ResourceRequest.java b/org.hl7.fhir.dstu2/src/main/java/org/hl7/fhir/dstu2/utils/client/ResourceRequest.java
index 3b337373d6..885112ba47 100644
--- a/org.hl7.fhir.dstu2/src/main/java/org/hl7/fhir/dstu2/utils/client/ResourceRequest.java
+++ b/org.hl7.fhir.dstu2/src/main/java/org/hl7/fhir/dstu2/utils/client/ResourceRequest.java
@@ -32,11 +32,15 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
import java.util.ArrayList;
import java.util.List;
+import lombok.Getter;
import org.hl7.fhir.dstu2.model.Resource;
public class ResourceRequest {
+ @Getter
private T payload;
+ @Getter
private int httpStatus = -1;
+ @Getter
private String location;
private List successfulStatuses = new ArrayList();
private List errorStatuses = new ArrayList();
@@ -67,14 +71,6 @@ public ResourceRequest(T payload, int httpStatus, int successfulStatus, String l
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 void addErrorStatus(int status) {
this.errorStatuses.add(status);
}
- public String getLocation() {
- return location;
- }
}
\ No newline at end of file
diff --git a/org.hl7.fhir.dstu2/src/test/java/org/hl7/fhir/dstu2/utils/client/ClientUtilsTest.java b/org.hl7.fhir.dstu2/src/test/java/org/hl7/fhir/dstu2/utils/client/ClientUtilsTest.java
new file mode 100644
index 0000000000..f659af3121
--- /dev/null
+++ b/org.hl7.fhir.dstu2/src/test/java/org/hl7/fhir/dstu2/utils/client/ClientUtilsTest.java
@@ -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 headers = ClientUtils.getResourceFormatHeaders(request, testFormat);
+
+ Map> 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 headers = ClientUtils.getResourceFormatHeaders(request, testFormat);
+
+ Map> 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 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 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 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 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 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 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 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 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 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 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 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);
+ }
+}
diff --git a/org.hl7.fhir.dstu2/src/test/java/org/hl7/fhir/dstu2/utils/client/FHIRToolingClientTest.java b/org.hl7.fhir.dstu2/src/test/java/org/hl7/fhir/dstu2/utils/client/FHIRToolingClientTest.java
new file mode 100644
index 0000000000..587fdb9f29
--- /dev/null
+++ b/org.hl7.fhir.dstu2/src/test/java/org/hl7/fhir/dstu2/utils/client/FHIRToolingClientTest.java
@@ -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 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");
+ }
+}
diff --git a/org.hl7.fhir.dstu3/pom.xml b/org.hl7.fhir.dstu3/pom.xml
index 8f61071b87..aef8d1dccc 100644
--- a/org.hl7.fhir.dstu3/pom.xml
+++ b/org.hl7.fhir.dstu3/pom.xml
@@ -28,6 +28,12 @@
hapi-fhir-base
+
+ org.projectlombok
+ lombok
+ provided
+
+
org.fhir
@@ -99,6 +105,13 @@
+
+ com.squareup.okhttp3
+ mockwebserver
+ true
+ test
+
+
com.fasterxml.jackson.core
jackson-databind
diff --git a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/FHIRToolingClient.java b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/FHIRToolingClient.java
index fe6aec345f..220c97cd6f 100644
--- a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/FHIRToolingClient.java
+++ b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/FHIRToolingClient.java
@@ -3,18 +3,15 @@
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.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 headers = new ArrayList<>();
- private String username;
- private String password;
+ private List headers = new ArrayList<>();
+ @Setter
+ @Getter
private String userAgent;
private EnumSet 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 Parameters getTerminologyCapabilities() {
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 CapabilityStatement getCapabilitiesStatement() {
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 CapabilityStatement getCapabilitiesStatementQuick() throws EFhirClientExc
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 T read(Class resourceClass, String id) {//TODO Ch
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 T vread(Class resourceClass, String id, String ve
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 T getCanonical(Class resourceClass, String canoni
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 Resource update(Resource resource) {
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 T update(Class resourceClass, T resource, String
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 Parameters operateType(Class resourceClass, Strin
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 OperationOutcome validate(Class resourceClass, T
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 OperationOutcome validate(Class resourceClass, T
}
/**
- * 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 @@ protected void handleException(String message, Exception e) throws EFhirClientEx
}
/**
- * 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 Parameters lookupCode(Map params) {
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 Parameters lookupCode(Parameters p) {
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 Parameters transform(Parameters p) {
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 ValueSet expandValueset(ValueSet source, Parameters expParams) {
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 ConceptMap initializeClosure(String name) {
result = client.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap()),
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 ConceptMap updateClosure(String name, Coding coding) {
result = client.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap()),
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 ConceptMap updateClosure(String name, Coding coding) {
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 void setRetryCount(int retryCount) {
client.setRetryCount(retryCount);
}
- public void setClientHeaders(ArrayList headers) {
- this.headers = headers;
+ public void setClientHeaders(Iterable 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 generateHeaders(boolean hasBody) {
// Add any other headers
- if(this.headers != null) {
- this.headers.forEach(header -> builder.add(header.toString()));
- }
+ List headers = new ArrayList<>(this.headers);
if (!Utilities.noString(userAgent)) {
- builder.add("User-Agent: "+userAgent);
- }
- if (!Utilities.noString(acceptLang)) {
- builder.add("Accept-Language: "+acceptLang);
- }
- if (!Utilities.noString(contentLang)) {
- builder.add("Content-Language: "+contentLang);
+ 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);
- }
-
- public String getUserAgent() {
- return userAgent;
- }
+ if (hasBody && !Utilities.noString(contentLanguage)) {
+ headers.add(new HTTPHeader("Content-Language",contentLanguage));
+ }
- public void setUserAgent(String userAgent) {
- this.userAgent = userAgent;
+ return headers;
}
public String getServerVersion() {
@@ -627,17 +595,6 @@ public String getServerVersion() {
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++;
}
diff --git a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/Client.java b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/Client.java
index a0635aadf8..a27f64b88f 100644
--- a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/Client.java
+++ b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/Client.java
@@ -2,6 +2,7 @@
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.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 ResourceRequest issueOptionsRequest(URI optionsUr
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 ResourceRequest issueGetResourceRequest(URI resourceUri,
String resourceFormat,
- Headers headers,
+ Iterable 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 ResourceRequest issuePutRequest(URI resourceUri,
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 ResourceRequest issuePutRequest(URI resourceUri,
byte[] payload,
String resourceFormat,
- Headers headers,
+ Iterable 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 ResourceRequest issuePostRequest(URI resourceUri,
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 ResourceRequest issuePostRequest(URI resourceUri,
byte[] payload,
String resourceFormat,
- Headers headers,
+ Iterable 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 Bundle issuePostFeedRequest(URI resourceUri,
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 Bundle postBatchRequest(URI resourceUri,
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 Bundle executeBundleRequest(Request.Builder request,
+ public Bundle executeBundleRequest(HTTPRequest request,
String resourceFormat,
- Headers headers,
+ Iterable headers,
String message,
int retryCount,
long timeout) throws IOException {
@@ -182,23 +183,23 @@ public Bundle executeBundleRequest(Request.Builder request,
.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 ResourceRequest executeFhirRequest(Request.Builder request,
- String resourceFormat,
- Headers headers,
- String message,
- int retryCount,
- long timeout) throws IOException {
+ public ResourceRequest executeFhirRequest(HTTPRequest request,
+ String resourceFormat,
+ Iterable 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();
}
diff --git a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/ClientHeaders.java b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/ClientHeaders.java
index c235c7b199..0d530b00ab 100644
--- a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/ClientHeaders.java
+++ b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/ClientHeaders.java
@@ -5,8 +5,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,30 +15,30 @@
*/
public class ClientHeaders {
- private final ArrayList headers;
+ private final ArrayList headers;
public ClientHeaders() {
this.headers = new ArrayList<>();
}
- public ClientHeaders(ArrayList headers) {
+ public ClientHeaders(ArrayList headers) {
this.headers = headers;
}
- public ArrayList headers() {
+ public ArrayList 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 ClientHeaders addHeader(Header header) throws FHIRException {
/**
* 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 headerList) throws FHIRException {
+ public ClientHeaders addHeaders(List 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 headerList) throws FHIRException {
+ public ClientHeaders removeHeaders(List 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 ClientHeaders clearHeaders() {
@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}"));
}
}
diff --git a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/FhirRequestBuilder.java b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/FhirRequestBuilder.java
index ad15ae999a..ee0fbeba71 100644
--- a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/FhirRequestBuilder.java
+++ b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/FhirRequestBuilder.java
@@ -2,12 +2,11 @@
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.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 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.
- */
- 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);
- }
-
- /**
- * Adds necessary headers for all REST requests.
- * User-Agent : hapi-fhir-tooling-client
- *
- * @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()));
+ * @param headers Any additional {@link HTTPHeader}s to add to the request.
+ */
+ protected static HTTPRequest formatHeaders(HTTPRequest request, String format, Iterable headers) {
+ List 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);
+ }
+ protected static Iterable getResourceFormatHeaders(HTTPRequest httpRequest, String format) {
+ List 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 @@ protected static boolean hasError(OperationOutcome oo) {
}
/**
- * 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}. 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> 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 headers) {
+ String locationHeader = HTTPHeaderUtil.getSingleHeader(headers, 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.
- *
- * 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();
+ if (locationHeader != null) {
+ return locationHeader;
}
-
- Authenticator proxyAuthenticator = getAuthenticator();
-
- return okHttpClient.newBuilder()
- .addInterceptor(new RetryInterceptor(retryCount))
- .connectTimeout(timeout, timeoutUnit)
- .writeTimeout(timeout, timeoutUnit)
- .readTimeout(timeout, timeoutUnit)
- .proxyAuthenticator(proxyAuthenticator)
- .build();
+ return HTTPHeaderUtil.getSingleHeader(headers, CONTENT_LOCATION_HEADER);
}
- @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 FhirRequestBuilder withResourceFormat(String resourceFormat) {
return this;
}
- public FhirRequestBuilder withHeaders(Headers headers) {
+ public FhirRequestBuilder withHeaders(Iterable headers) {
this.headers = headers;
return this;
}
@@ -227,25 +149,16 @@ public FhirRequestBuilder withTimeout(long timeout, TimeUnit unit) {
return this;
}
- protected Request buildRequest() {
- return httpRequest.build();
- }
-
public ResourceRequest 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(resource, response.code(), getLocationHeader(response.headers()));
+ return new ResourceRequest(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 Bundle executeAsBatch() throws IOException {
* Unmarshalls a resource from the response stream.
*/
@SuppressWarnings("unchecked")
- protected T unmarshalReference(Response response, String format) {
+ protected 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 @@ protected T unmarshalReference(Response response, String fo
/**
* 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 @@ else if (rf instanceof OperationOutcome && hasError((OperationOutcome) rf)) {
/**
* Returns the appropriate parser based on the format type passed in. Defaults to XML parser if a blank format is
* provided...because reasons.
- *
+ *
* Currently supports only "json" and "xml" formats.
*
* @param format One of "json" or "xml".
@@ -334,52 +247,4 @@ protected IParser getParser(String format) {
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 headerList = new ArrayList<>(Collections.emptyList());
- Map> 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 headerList = new ArrayList<>(Collections.emptyList());
- Map> 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?");
-// }
- }
}
diff --git a/org.hl7.fhir.dstu3/src/test/java/org/hl7/fhir/dstu3/utils/client/FhirToolingClientTest.java b/org.hl7.fhir.dstu3/src/test/java/org/hl7/fhir/dstu3/utils/client/FhirToolingClientTest.java
new file mode 100644
index 0000000000..54f3637986
--- /dev/null
+++ b/org.hl7.fhir.dstu3/src/test/java/org/hl7/fhir/dstu3/utils/client/FhirToolingClientTest.java
@@ -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> headersArgumentCaptor;
+
+
+ @BeforeEach
+ void setUp() throws IOException, URISyntaxException {
+ MockitoAnnotations.openMocks(this);
+ mockClient = Mockito.mock(Client.class);
+ ResourceRequest 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 getHeaders() {
+ return new ArrayList<>(Arrays.asList(h1, h2, h3));
+ }
+
+ private List 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 argumentCaptorValue) {
+ List 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 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 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 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 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 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 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 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 argumentCaptorValue = headersArgumentCaptor.getValue();
+ checkHeaders(argumentCaptorValue);
+ }
+}
diff --git a/org.hl7.fhir.dstu3/src/test/java/org/hl7/fhir/dstu3/utils/client/network/ClientTest.java b/org.hl7.fhir.dstu3/src/test/java/org/hl7/fhir/dstu3/utils/client/network/ClientTest.java
new file mode 100644
index 0000000000..18f1bed482
--- /dev/null
+++ b/org.hl7.fhir.dstu3/src/test/java/org/hl7/fhir/dstu3/utils/client/network/ClientTest.java
@@ -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 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 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 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 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 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());
+ }
+}
diff --git a/org.hl7.fhir.dstu3/src/test/java/org/hl7/fhir/dstu3/utils/client/network/FhirRequestBuilderTests.java b/org.hl7.fhir.dstu3/src/test/java/org/hl7/fhir/dstu3/utils/client/network/FhirRequestBuilderTests.java
index 7cd2b5eddd..b5789c50bb 100644
--- a/org.hl7.fhir.dstu3/src/test/java/org/hl7/fhir/dstu3/utils/client/network/FhirRequestBuilderTests.java
+++ b/org.hl7.fhir.dstu3/src/test/java/org/hl7/fhir/dstu3/utils/client/network/FhirRequestBuilderTests.java
@@ -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/";
-
- Request mockRequest = new Request.Builder()
- .url(DUMMY_URL)
- .build();
-
- final String RESPONSE_BODY_STRING = "{}";
+ @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);
- 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();
+ Iterable headers = FhirRequestBuilder.getResourceFormatHeaders(request, testFormat);
- final Request.Builder requestBuilder = new Request.Builder()
- .url(DUMMY_URL);
+ Map> 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 + ".");
- final FhirRequestBuilder fhirRequestBuilder = Mockito.spy(new FhirRequestBuilder(requestBuilder, "http://local/local"));
+ Assertions.assertNull(headersMap.get("Content-Type"), "Content-Type header not null.");
+ }
- @Mock
- OkHttpClient client;
+ @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);
- @Mock
- Call mockCall;
+ Iterable headers = FhirRequestBuilder.getResourceFormatHeaders(request, testFormat);
- @Mock
- ToolingClientLogger logger;
+ Map> 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 + ".");
- @BeforeEach
- public void beforeEach() {
- Mockito.doReturn(client).when(fhirRequestBuilder).getHttpClient();
- fhirRequestBuilder.withLogger(logger);
+ 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 + "\".");
}
- @Nested
- class RequestLoggingTests {
+ @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.");
+ }
- @BeforeEach
- public void beforeEach() throws IOException {
- Mockito.doReturn(response).when(mockCall).execute();
- Mockito.doReturn(mockCall).when(client).newCall(ArgumentMatchers.any());
+ @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.");
+ }
- Mockito.doReturn(null).when(fhirRequestBuilder).unmarshalReference(ArgumentMatchers.any(), ArgumentMatchers.isNull());
- }
+ @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
- public void testExecuteLogging() throws IOException {
- fhirRequestBuilder.execute();
- Mockito.verify(logger).logRequest(ArgumentMatchers.eq("GET"), ArgumentMatchers.eq(DUMMY_URL), ArgumentMatchers.anyList(), ArgumentMatchers.isNull());
- }
+ @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
- public void testExecuteBatchLogging() throws IOException {
- fhirRequestBuilder.executeAsBatch();
- Mockito.verify(logger).logRequest(ArgumentMatchers.eq("GET"), ArgumentMatchers.eq(DUMMY_URL), ArgumentMatchers.anyList(), ArgumentMatchers.isNull());
- }
+ @Test
+ @DisplayName("Test that getLocationHeader returns header for 'content-location'.")
+ void getLocationHeaderWhenOnlyContentLocationIsSet() {
+ final String expectedContentLocationHeader = "content_location_header_value";
+ Iterable headers = List.of(new HTTPHeader(FhirRequestBuilder.CONTENT_LOCATION_HEADER, expectedContentLocationHeader));
+ Assertions.assertEquals(expectedContentLocationHeader, FhirRequestBuilder.getLocationHeader(headers));
}
@Test
- public void testUnmarshallReferenceLogging() {
- IParser parser = Mockito.mock(IParser.class);
- Mockito.doReturn(parser).when(fhirRequestBuilder).getParser(ArgumentMatchers.eq("json"));
+ @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";
- fhirRequestBuilder.unmarshalReference(response, "json");
- Mockito.verify(logger).logResponse(ArgumentMatchers.eq("200"), ArgumentMatchers.anyList(), AdditionalMatchers.aryEq(RESPONSE_BODY_STRING.getBytes()), ArgumentMatchers.anyLong());
+ Iterable headers = List.of(
+ new HTTPHeader(FhirRequestBuilder.LOCATION_HEADER, expectedLocationHeader),
+ new HTTPHeader(FhirRequestBuilder.CONTENT_LOCATION_HEADER, expectedContentLocationHeader)
+ );
+
+ Assertions.assertEquals(expectedLocationHeader, FhirRequestBuilder.getLocationHeader(headers));
}
@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 getLocationHeader returns null when no location available.")
+ void getLocationHeaderWhenNoLocationSet() {
+ Assertions.assertNull(FhirRequestBuilder.getLocationHeader(Collections.emptyList()));
}
+
}
diff --git a/org.hl7.fhir.r4/pom.xml b/org.hl7.fhir.r4/pom.xml
index ead565cbbc..179e00ac64 100644
--- a/org.hl7.fhir.r4/pom.xml
+++ b/org.hl7.fhir.r4/pom.xml
@@ -35,6 +35,12 @@
+
+ org.projectlombok
+ lombok
+ provided
+
+
org.fhir
@@ -78,7 +84,12 @@
okio-jvm
true
-
+
+ com.squareup.okhttp3
+ mockwebserver
+ true
+ test
+
org.hl7.fhir.testcases
fhir-test-cases
diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/FHIRToolingClient.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/FHIRToolingClient.java
index b07046dbea..11451ad421 100644
--- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/FHIRToolingClient.java
+++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/FHIRToolingClient.java
@@ -3,11 +3,10 @@
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.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 headers = new ArrayList<>();
- private String username;
- private String password;
+ private List 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 void initialize(String baseServiceUrl) throws URISyntaxException {
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 String getPreferredResourceFormat() {
return preferredResourceFormat.getHeader();
}
- public void setPreferredResourceFormat(ResourceFormat resourceFormat) {
- preferredResourceFormat = resourceFormat;
- }
-
public int getMaximumRecordCount() {
return maxResultSetSize;
}
@@ -131,7 +123,7 @@ public TerminologyCapabilities getTerminologyCapabilities() {
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 CapabilityStatement getCapabilitiesStatement() {
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 CapabilityStatement getCapabilitiesStatementQuick() throws EFhirClientExc
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 Resource read(String resourceClass, String id) {// TODO Change this to Ad
ResourceRequest 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 T read(Class resourceClass, String id) {// TODO C
ResourceRequest 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 T vread(Class resourceClass, String id, String ve
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 T getCanonical(Class resourceClass, String canoni
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 Resource update(Resource resource) {
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 T update(Class resourceClass, T resource, String
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 Parameters operateType(Class resourceClass, Strin
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 OperationOutcome validate(Class resourceClass, T
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 OperationOutcome validate(Class resourceClass, T
}
/**
- * 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 @@ protected void handleException(int code, String message, Exception e) throws EFh
}
/**
- * 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 Parameters lookupCode(Map params) {
org.hl7.fhir.r4.utils.client.network.ResourceRequest 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 Parameters lookupCode(Parameters p) {
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 Parameters translate(Parameters p) {
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 ValueSet expandValueset(ValueSet source, Parameters expParams) {
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 ConceptMap initializeClosure(String name) {
result = client.issuePostRequest(
resourceAddress.resolveOperationUri(null, "closure", new HashMap()),
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 ConceptMap updateClosure(String name, Coding coding) {
result = client.issuePostRequest(
resourceAddress.resolveOperationUri(null, "closure", new HashMap()),
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 ConceptMap updateClosure(String name, Coding coding) {
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 void setRetryCount(int retryCount) {
client.setRetryCount(retryCount);
}
- public void setClientHeaders(ArrayList headers) {
- this.headers = headers;
+ public void setClientHeaders(Iterable 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 generateHeaders(boolean hasBody) {
// Add any other headers
- if (this.headers != null) {
- this.headers.forEach(header -> builder.add(header.toString()));
- }
+ List 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);
- }
-
- 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;
- }
+ if (hasBody && !Utilities.noString(contentLanguage)) {
+ headers.add(new HTTPHeader("Content-Language",contentLanguage));
+ }
- public void setUserAgent(String userAgent) {
- this.userAgent = userAgent;
+ return headers;
}
public String getServerVersion() {
@@ -609,14 +566,6 @@ public String getServerVersion() {
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 T fetchResource(Class resourceClass, String id) {
org.hl7.fhir.r4.utils.client.network.ResourceRequest 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);
}
diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/Client.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/Client.java
index 37918812b9..a17a4f8d79 100644
--- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/Client.java
+++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/Client.java
@@ -2,148 +2,149 @@
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";
- private ToolingClientLogger logger;
- private FhirLoggingInterceptor fhirLoggingInterceptor;
- private int retryCount;
- private String base;
-
- public String getBase() {
- return base;
- }
-
- public void setBase(String base) {
- this.base = base;
- }
+ @Getter @Setter
+ private ToolingClientLogger logger;
- public ToolingClientLogger getLogger() {
- return logger;
- }
-
- public void setLogger(ToolingClientLogger logger) {
- this.logger = logger;
- this.fhirLoggingInterceptor = new FhirLoggingInterceptor(logger);
- }
-
- public int getRetryCount() {
- return retryCount;
- }
+ @Getter @Setter
+ private int retryCount;
- public void setRetryCount(int retryCount) {
- this.retryCount = retryCount;
- }
+ @Getter @Setter
+ private String base;
public ResourceRequest 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 ResourceRequest issueGetResourceRequest(URI resourceUri, String resourceFormat,
- Headers headers, String message, long timeout) throws IOException {
- Request.Builder request = new Request.Builder().url(resourceUri.toURL());
-
+ Iterable 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 ResourceRequest 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 ResourceRequest issuePutRequest(URI resourceUri, byte[] payload, String resourceFormat,
- Headers headers, String message, long timeout) throws IOException {
+ Iterable 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 ResourceRequest 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 ResourceRequest issuePostRequest(URI resourceUri, byte[] payload,
- String resourceFormat, Headers headers, String message, long timeout) throws IOException {
+ String resourceFormat, Iterable 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 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);
+ }
+
+ private static String getContentTypeWithDefaultCharset(String resourceFormat) {
+ return resourceFormat + ";charset=" + DEFAULT_CHARSET;
}
- public 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)
+ public Bundle executeBundleRequest(HTTPRequest request, String resourceFormat,
+ Iterable 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 ResourceRequest 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 ResourceRequest executeFhirRequest(HTTPRequest request, String resourceFormat,
+ Iterable 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();
}
}
diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/ClientHeaders.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/ClientHeaders.java
index 6a1a6ad6aa..ef42c73b49 100644
--- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/ClientHeaders.java
+++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/ClientHeaders.java
@@ -6,7 +6,7 @@
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 @@
*/
public class ClientHeaders {
- private final ArrayList headers;
+ private final ArrayList headers;
public ClientHeaders() {
this.headers = new ArrayList<>();
}
- public ClientHeaders(ArrayList headers) {
+ public ClientHeaders(ArrayList headers) {
this.headers = headers;
}
- public ArrayList headers() {
+ public ArrayList 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 ClientHeaders addHeader(Header header) throws FHIRException {
/**
* 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 headerList) throws FHIRException {
+ public ClientHeaders addHeaders(List headerList) throws FHIRException {
headerList.forEach(this::addHeader);
return this;
}
@@ -58,13 +58,13 @@ public ClientHeaders addHeaders(List headerList) throws FHIRException {
/**
* 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 ClientHeaders removeHeader(Header header) throws FHIRException {
/**
* 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 headerList) throws FHIRException {
+ public ClientHeaders removeHeaders(List 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 ClientHeaders clearHeaders() {
@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}"));
}
}
diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/FhirLoggingInterceptor.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/FhirLoggingInterceptor.java
deleted file mode 100644
index 38d1953805..0000000000
--- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/FhirLoggingInterceptor.java
+++ /dev/null
@@ -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 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 headerList = new ArrayList<>();
- Map> 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();
- }
-}
diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilder.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilder.java
index a9b0d78adb..579e3d1b30 100644
--- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilder.java
+++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilder.java
@@ -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.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 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 headers) {
+ List allHeaders = new ArrayList<>();
+ request.getHeaders().forEach(allHeaders::add);
- /**
- * Adds necessary headers for all REST requests.
- * User-Agent : hapi-fhir-tooling-client
- *
- * @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);
+ if (format != null) getResourceFormatHeaders(request, format).forEach(allHeaders::add);
+ if (headers != null) headers.forEach(allHeaders::add);
+ return request.withHeaders(allHeaders);
}
- /**
- * 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()));
+ protected static Iterable getResourceFormatHeaders(HTTPRequest httpRequest, String format) {
+ List 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;
}
/**
@@ -136,73 +104,24 @@ protected static boolean hasError(OperationOutcome oo) {
}
/**
- * Extracts the 'location' header from the passes in {@link Headers}. If no
+ * Extracts the 'location' header from the passed {@link Iterable}. 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> 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.
- *
- * 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");
- }
+ protected static String getLocationHeader(Iterable headers) {
+ String locationHeader = HTTPHeaderUtil.getSingleHeader(headers, LOCATION_HEADER);
- if (okHttpClient == null) {
- okHttpClient = new OkHttpClient();
+ if (locationHeader != null) {
+ return locationHeader;
}
-
- 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();
+ return HTTPHeaderUtil.getSingleHeader(headers, CONTENT_LOCATION_HEADER);
}
- @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 FhirRequestBuilder withResourceFormat(String resourceFormat) {
return this;
}
- public FhirRequestBuilder withHeaders(Headers headers) {
+ public FhirRequestBuilder withHeaders(Iterable headers) {
this.headers = headers;
return this;
}
@@ -225,7 +144,7 @@ public FhirRequestBuilder withRetryCount(int retryCount) {
return this;
}
- public FhirRequestBuilder withLogger(FhirLoggingInterceptor logger) {
+ public FhirRequestBuilder withLogger(ToolingClientLogger logger) {
this.logger = logger;
return this;
}
@@ -236,33 +155,31 @@ public FhirRequestBuilder withTimeout(long timeout, TimeUnit unit) {
return this;
}
- protected Request buildRequest() {
- return httpRequest.build();
- }
public ResourceRequest 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(resource, response.code(), getLocationHeader(response.headers()));
+ return new ResourceRequest(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 unmarshalReference(Response response, String format, String resourceType) {
- int code = response.code();
+ protected 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 @@ protected T unmarshalReference(Response response, String fo
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 @@ protected T unmarshalReference(Response response, String fo
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 @@ protected T unmarshalReference(Response response, String fo
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 @@ protected T unmarshalReference(Response response, String fo
/**
* 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.
- *
+ *
* Currently supports only "json" and "xml" formats.
*
* @param format One of "json" or "xml".
diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/RetryInterceptor.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/RetryInterceptor.java
deleted file mode 100644
index b90085b028..0000000000
--- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/RetryInterceptor.java
+++ /dev/null
@@ -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);
- }
-
-}
\ No newline at end of file
diff --git a/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/FhirToolingClientTest.java b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/FhirToolingClientTest.java
new file mode 100644
index 0000000000..43de724c14
--- /dev/null
+++ b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/FhirToolingClientTest.java
@@ -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> headersArgumentCaptor;
+
+ @BeforeEach
+ void setUp() throws IOException, URISyntaxException {
+ MockitoAnnotations.openMocks(this);
+ mockClient = Mockito.mock(Client.class);
+ ResourceRequest 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 getHeaders() {
+ return new ArrayList<>(Arrays.asList(h1, h2, h3));
+ }
+
+ private List 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 argumentCaptorValue) {
+ List 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 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 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 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 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 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 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 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 argumentCaptorValue = headersArgumentCaptor.getValue();
+ checkHeaders(argumentCaptorValue);
+ }
+}
diff --git a/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/network/ClientHeadersTest.java b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/network/ClientHeadersTest.java
new file mode 100644
index 0000000000..93ed42f405
--- /dev/null
+++ b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/network/ClientHeadersTest.java
@@ -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 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 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 headersToAddList = Arrays.asList(h1, h2, h3);
+ List 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 headersToAddList = Arrays.asList(h1, h3);
+ List headersToRemoveList = Arrays.asList(h2, h3);
+ clientHeaders.addHeaders(headersToAddList);
+ Assertions.assertThrows(FHIRException.class, () -> clientHeaders.removeHeaders(headersToRemoveList));
+ }
+
+ @Test
+ void clearHeaders() {
+ List headersToAddList = Arrays.asList(h1, h3);
+ clientHeaders.addHeaders(headersToAddList);
+ Assertions.assertEquals(2, clientHeaders.headers().size());
+ clientHeaders.clearHeaders();
+ Assertions.assertEquals(0, clientHeaders.headers().size());
+ }
+}
diff --git a/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/network/ClientTest.java b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/network/ClientTest.java
new file mode 100644
index 0000000000..19cc74d1d5
--- /dev/null
+++ b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/network/ClientTest.java
@@ -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 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 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 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 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 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());
+ }
+}
diff --git a/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilderTest.java b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilderTest.java
new file mode 100644
index 0000000000..5d278d574e
--- /dev/null
+++ b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilderTest.java
@@ -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 headers = FhirRequestBuilder.getResourceFormatHeaders(request, testFormat);
+
+ Map> 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 headers = FhirRequestBuilder.getResourceFormatHeaders(request, testFormat);
+
+ Map> 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 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 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 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()));
+ }
+}
diff --git a/org.hl7.fhir.r4b/pom.xml b/org.hl7.fhir.r4b/pom.xml
index 919630c724..a25a275d8e 100644
--- a/org.hl7.fhir.r4b/pom.xml
+++ b/org.hl7.fhir.r4b/pom.xml
@@ -34,6 +34,12 @@
+
+ org.projectlombok
+ lombok
+ provided
+
+
org.apache.commons
diff --git a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/terminologies/TerminologyCacheManager.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/terminologies/TerminologyCacheManager.java
index 68b94ed833..819d36fc2c 100644
--- a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/terminologies/TerminologyCacheManager.java
+++ b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/terminologies/TerminologyCacheManager.java
@@ -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 void commit(String token) throws IOException {
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 {
diff --git a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/FHIRToolingClient.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/FHIRToolingClient.java
index 65f74dc192..5587639327 100644
--- a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/FHIRToolingClient.java
+++ b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/FHIRToolingClient.java
@@ -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 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
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 headers = new ArrayList<>();
- private String username;
- private String password;
+ private List 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 void initialize(String baseServiceUrl) throws URISyntaxException {
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 TerminologyCapabilities getTerminologyCapabilities() {
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 CapabilityStatement getCapabilitiesStatement() {
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 CapabilityStatement getCapabilitiesStatementQuick() throws EFhirClientExc
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 T read(Class resourceClass, String id) {// TODO C
ResourceRequest 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 T vread(Class resourceClass, String id, String ve
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 T getCanonical(Class resourceClass, String canoni
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 Resource update(Resource resource) {
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 T update(Class resourceClass, T resource, String
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 Parameters operateType(Class resourceClass, Strin
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 Bundle transaction(Bundle batch) {
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 OperationOutcome validate(Class resourceClass, T
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 OperationOutcome validate(Class resourceClass, T
}
/**
- * 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 @@ protected void handleException(String message, Exception e) throws EFhirClientEx
* 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 ValueSet expandValueset(ValueSet source, Parameters expParams) {
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 Parameters lookupCode(Map params) {
org.hl7.fhir.r4b.utils.client.network.ResourceRequest 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 ValueSet expandValueset(ValueSet source, Parameters expParams, Map()),
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 ConceptMap updateClosure(String name, Coding coding) {
result = client.issuePostRequest(
resourceAddress.resolveOperationUri(null, "closure", new HashMap()),
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 ConceptMap updateClosure(String name, Coding coding) {
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 void setRetryCount(int retryCount) {
client.setRetryCount(retryCount);
}
- public void setClientHeaders(ArrayList headers) {
- this.headers = headers;
+ public void setClientHeaders(Iterable 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 generateHeaders(boolean hasBody) {
// Add any other headers
- if (this.headers != null) {
- this.headers.forEach(header -> builder.add(header.toString()));
- }
+ List 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);
- }
-
- public Header getAuthorizationHeader() {
- String usernamePassword = username + ":" + password;
- String base64usernamePassword = Base64.getEncoder().encodeToString(usernamePassword.getBytes());
- return new Header("Authorization", "Basic " + base64usernamePassword);
- }
+ if (!Utilities.noString(acceptLanguage)) {
+ headers.add(new HTTPHeader("Accept-Language", acceptLanguage));
+ }
- public String getUserAgent() {
- return userAgent;
- }
+ if (hasBody && !Utilities.noString(contentLanguage)) {
+ headers.add(new HTTPHeader("Content-Language",contentLanguage));
+ }
- public void setUserAgent(String userAgent) {
- this.userAgent = userAgent;
+ return headers;
}
public String getServerVersion() {
diff --git a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/Client.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/Client.java
index 6cf5fa10f9..d5f145f07e 100644
--- a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/Client.java
+++ b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/Client.java
@@ -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;
- private int retryCount;
- private long timeout = DEFAULT_TIMEOUT;
- private byte[] payload;
- 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;
- }
+ @Setter
+ @Getter
+ private int retryCount;
- public long getTimeout() {
- return timeout;
- }
+ @Setter
+ @Getter
+ private long timeout = DEFAULT_TIMEOUT;
- public void setTimeout(long timeout) {
- this.timeout = timeout;
- }
+ @Setter @Getter
+ private String base;
public ResourceRequest 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 ResourceRequest 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 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 ResourceRequest 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 ResourceRequest issuePutRequest(URI resourceUri, byte[] payload, String resourceFormat,
- Headers headers, String message, long timeout) throws IOException {
+ Iterable 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 ResourceRequest 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 ResourceRequest issuePostRequest(URI resourceUri, byte[] payload,
- String resourceFormat, Headers headers, String message, long timeout) throws IOException {
+ String resourceFormat, Iterable 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 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 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 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 Bundle executeBundleRequest(HTTPRequest request, String resourceFormat,
+ Iterable 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 ResourceRequest 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 ResourceRequest executeFhirRequest(HTTPRequest request, String resourceFormat,
+ Iterable 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();
}
}
diff --git a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/ClientHeaders.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/ClientHeaders.java
index 6b6999d8d1..b837bd094c 100644
--- a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/ClientHeaders.java
+++ b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/ClientHeaders.java
@@ -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 @@
/**
* Generic Implementation of Client Headers.
- *
+ *
* 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 headers;
+ private final ArrayList headers;
public ClientHeaders() {
this.headers = new ArrayList<>();
}
- public ClientHeaders(ArrayList headers) {
+ public ClientHeaders(ArrayList headers) {
this.headers = headers;
}
- public ArrayList headers() {
+ public ArrayList 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 ClientHeaders addHeader(Header header) throws FHIRException {
/**
* 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 headerList) throws FHIRException {
+ public ClientHeaders addHeaders(List headerList) throws FHIRException {
headerList.forEach(this::addHeader);
return this;
}
@@ -57,13 +58,13 @@ public ClientHeaders addHeaders(List headerList) throws FHIRException {
/**
* 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 ClientHeaders removeHeader(Header header) throws FHIRException {
/**
* 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 headerList) throws FHIRException {
+ public ClientHeaders removeHeaders(List 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 ClientHeaders clearHeaders() {
@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}"));
}
}
diff --git a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/FhirLoggingInterceptor.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/FhirLoggingInterceptor.java
deleted file mode 100644
index 7a9a6a2821..0000000000
--- a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/FhirLoggingInterceptor.java
+++ /dev/null
@@ -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 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 headerList = new ArrayList<>();
- Map> 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();
- }
-}
diff --git a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/FhirRequestBuilder.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/FhirRequestBuilder.java
index fa7a663008..d8f2536837 100644
--- a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/FhirRequestBuilder.java
+++ b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/FhirRequestBuilder.java
@@ -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.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 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.
- */
- 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);
- }
-
- /**
- * Adds necessary headers for all REST requests.
- * User-Agent : hapi-fhir-tooling-client
- *
- * @param request {@link Request.Builder} to add default headers to.
+ * @param headers Any additional {@link HTTPHeader} to add to the request.
*/
- 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 HTTPRequest formatHeaders(HTTPRequest request, String format, Iterable headers) {
+ List allHeaders = new ArrayList<>();
+ request.getHeaders().forEach(allHeaders::add);
- /**
- * 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);
+ if (format != null) getResourceFormatHeaders(request, format).forEach(allHeaders::add);
+ if (headers != null) headers.forEach(allHeaders::add);
+ return request.withHeaders(allHeaders);
}
- /**
- * 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()));
+ protected static Iterable getResourceFormatHeaders(HTTPRequest httpRequest, String format) {
+ List 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,68 +102,24 @@ protected static boolean hasError(OperationOutcome oo) {
}
/**
- * 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> 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 headers) {
+ String locationHeader = HTTPHeaderUtil.getSingleHeader(headers, 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.
- *
- * 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();
+ if (locationHeader != null) {
+ return locationHeader;
}
-
- 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();
+ return HTTPHeaderUtil.getSingleHeader(headers, CONTENT_LOCATION_HEADER);
}
- @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 FhirRequestBuilder withResourceFormat(String resourceFormat) {
return this;
}
- public FhirRequestBuilder withHeaders(Headers headers) {
+ public FhirRequestBuilder withHeaders(Iterable headers) {
this.headers = headers;
return this;
}
@@ -215,7 +142,7 @@ public FhirRequestBuilder withRetryCount(int retryCount) {
return this;
}
- public FhirRequestBuilder withLogger(FhirLoggingInterceptor logger) {
+ public FhirRequestBuilder withLogger(ToolingClientLogger logger) {
this.logger = logger;
return this;
}
@@ -226,33 +153,28 @@ public FhirRequestBuilder withTimeout(long timeout, TimeUnit unit) {
return this;
}
- protected Request buildRequest() {
- return httpRequest.build();
- }
-
public ResourceRequest 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(resource, response.code(), getLocationHeader(response.headers()));
+ return new ResourceRequest(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 unmarshalReference(Response response, String format, String resourceType) {
- int code = response.code();
+ protected 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 @@ protected T unmarshalReference(Response response, String fo
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 @@ protected T unmarshalReference(Response response, String fo
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 @@ protected T unmarshalReference(Response response, String fo
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 @@ protected T unmarshalReference(Response response, String fo
/**
* Unmarshalls Bundle from response stream.
*/
- protected Bundle unmarshalFeed(Response response, String format) {
+ protected Bundle unmarshalFeed(HTTPResult response, String format) {
return unmarshalReference(response, format, "Bundle");
}
diff --git a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/RetryInterceptor.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/RetryInterceptor.java
deleted file mode 100644
index 931f66ddde..0000000000
--- a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/RetryInterceptor.java
+++ /dev/null
@@ -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);
- }
-
-}
\ No newline at end of file
diff --git a/org.hl7.fhir.r4b/src/test/java/org/hl7/fhir/r4b/utils/client/FHIRToolingClientTest.java b/org.hl7.fhir.r4b/src/test/java/org/hl7/fhir/r4b/utils/client/FHIRToolingClientTest.java
index eecbc0234e..83099c4c6a 100644
--- a/org.hl7.fhir.r4b/src/test/java/org/hl7/fhir/r4b/utils/client/FHIRToolingClientTest.java
+++ b/org.hl7.fhir.r4b/src/test/java/org/hl7/fhir/r4b/utils/client/FHIRToolingClientTest.java
@@ -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> headersArgumentCaptor;
+
+
@BeforeEach
void setUp() throws IOException, URISyntaxException {
+ MockitoAnnotations.openMocks(this);
mockClient = Mockito.mock(Client.class);
ResourceRequest 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 getHeaders() {
+ private List getHeaders() {
return new ArrayList<>(Arrays.asList(h1, h2, h3));
}
+ private List getHeadersWithAgent() {
+ return new ArrayList<>(Arrays.asList(h1, h2, h3, agentHeader));
+ }
+
private Bundle generateBundle() {
Patient patient = generatePatient();
Observation observation = generateObservation();
@@ -115,108 +125,102 @@ private Observation generateObservation() {
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 argumentCaptorValue) {
+ List capturedHeaders = new ArrayList<>();
+ argumentCaptorValue.forEach(capturedHeaders::add);
+
+ getHeadersWithAgent().forEach(header -> {
+ assertTrue(capturedHeaders.contains(header));
});
}
@Test
void getTerminologyCapabilities() throws IOException {
- ArgumentCaptor 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 argumentCaptorValue = headersArgumentCaptor.getValue();
checkHeaders(argumentCaptorValue);
}
@Test
void getCapabilitiesStatement() throws IOException {
- ArgumentCaptor 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 argumentCaptorValue = headersArgumentCaptor.getValue();
checkHeaders(argumentCaptorValue);
}
@Test
void getCapabilitiesStatementQuick() throws IOException {
- ArgumentCaptor 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 argumentCaptorValue = headersArgumentCaptor.getValue();
checkHeaders(argumentCaptorValue);
}
@Test
void read() throws IOException {
- ArgumentCaptor 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 argumentCaptorValue = headersArgumentCaptor.getValue();
checkHeaders(argumentCaptorValue);
}
@Test
void vread() throws IOException {
- ArgumentCaptor 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 argumentCaptorValue = headersArgumentCaptor.getValue();
checkHeaders(argumentCaptorValue);
}
@Test
void getCanonical() throws IOException {
- ArgumentCaptor 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 argumentCaptorValue = headersArgumentCaptor.getValue();
checkHeaders(argumentCaptorValue);
}
@Test
void update() throws IOException {
- ArgumentCaptor 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 argumentCaptorValue = headersArgumentCaptor.getValue();
checkHeaders(argumentCaptorValue);
}
@Test
void validate() throws IOException {
- ArgumentCaptor 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 argumentCaptorValue = headersArgumentCaptor.getValue();
checkHeaders(argumentCaptorValue);
}
}
\ No newline at end of file
diff --git a/org.hl7.fhir.r4b/src/test/java/org/hl7/fhir/r4b/utils/client/network/ClientHeadersTest.java b/org.hl7.fhir.r4b/src/test/java/org/hl7/fhir/r4b/utils/client/network/ClientHeadersTest.java
index 75c7129d92..72051706ce 100644
--- a/org.hl7.fhir.r4b/src/test/java/org/hl7/fhir/r4b/utils/client/network/ClientHeadersTest.java
+++ b/org.hl7.fhir.r4b/src/test/java/org/hl7/fhir/r4b/utils/client/network/ClientHeadersTest.java
@@ -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 @@ void addHeaderDuplicateAdd() {
@Test
@DisplayName("Happy path add headers as list.")
void addHeaders() {
- List headersList = Arrays.asList(h1, h2, h3);
+ List headersList = Arrays.asList(h1, h2, h3);
clientHeaders.addHeaders(headersList);
Assertions.assertEquals(3, clientHeaders.headers().size());
Assertions.assertEquals(headersList, clientHeaders.headers());
@@ -51,7 +51,7 @@ void addHeaders() {
@Test
@DisplayName("Happy path add headers as list.")
void addHeadersDuplicateAdd() {
- List headersList = Arrays.asList(h1, h2, h1);
+ List headersList = Arrays.asList(h1, h2, h1);
Assertions.assertThrows(FHIRException.class, () -> clientHeaders.addHeaders(headersList));
}
@@ -63,7 +63,7 @@ void removeHeader() {
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 @@ void removeHeaderUnknown() {
@Test
@DisplayName("Happy path remove list of existing headers.")
void removeHeaders() {
- List headersToAddList = Arrays.asList(h1, h2, h3);
- List headersToRemoveList = Arrays.asList(h2, h3);
+ List headersToAddList = Arrays.asList(h1, h2, h3);
+ List headersToRemoveList = Arrays.asList(h2, h3);
clientHeaders.addHeaders(headersToAddList);
clientHeaders.removeHeaders(headersToRemoveList);
Assertions.assertEquals(1, clientHeaders.headers().size());
@@ -88,15 +88,15 @@ void removeHeaders() {
@Test
@DisplayName("Remove list containing unknown header.")
void removeHeadersUnknown() {
- List headersToAddList = Arrays.asList(h1, h3);
- List headersToRemoveList = Arrays.asList(h2, h3);
+ List headersToAddList = Arrays.asList(h1, h3);
+ List headersToRemoveList = Arrays.asList(h2, h3);
clientHeaders.addHeaders(headersToAddList);
Assertions.assertThrows(FHIRException.class, () -> clientHeaders.removeHeaders(headersToRemoveList));
}
@Test
void clearHeaders() {
- List headersToAddList = Arrays.asList(h1, h3);
+ List headersToAddList = Arrays.asList(h1, h3);
clientHeaders.addHeaders(headersToAddList);
Assertions.assertEquals(2, clientHeaders.headers().size());
clientHeaders.clearHeaders();
diff --git a/org.hl7.fhir.r4b/src/test/java/org/hl7/fhir/r4b/utils/client/network/FhirRequestBuilderTest.java b/org.hl7.fhir.r4b/src/test/java/org/hl7/fhir/r4b/utils/client/network/FhirRequestBuilderTest.java
index 8cd65e5d33..9024625faa 100644
--- a/org.hl7.fhir.r4b/src/test/java/org/hl7/fhir/r4b/utils/client/network/FhirRequestBuilderTest.java
+++ b/org.hl7.fhir.r4b/src/test/java/org/hl7/fhir/r4b/utils/client/network/FhirRequestBuilderTest.java
@@ -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> 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);
+
+ Iterable headers = FhirRequestBuilder.getResourceFormatHeaders(request, testFormat);
- Map> headersMap = request.build().headers().toMultimap();
+ Map> 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";
-
- 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> 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 + ".");
+ @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 headers = FhirRequestBuilder.getResourceFormatHeaders(request, testFormat);
+
+ Map> 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
@@ -115,7 +95,7 @@ void hasErrorTestNoErrors() {
@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 headers = List.of(new HTTPHeader(FhirRequestBuilder.LOCATION_HEADER, expectedLocationHeader));
Assertions.assertEquals(expectedLocationHeader, FhirRequestBuilder.getLocationHeader(headers));
}
@@ -123,8 +103,7 @@ void getLocationHeaderWhenOnlyLocationIsSet() {
@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 headers = List.of(new HTTPHeader(FhirRequestBuilder.CONTENT_LOCATION_HEADER, expectedContentLocationHeader));
Assertions.assertEquals(expectedContentLocationHeader, FhirRequestBuilder.getLocationHeader(headers));
}
@@ -133,15 +112,17 @@ void getLocationHeaderWhenOnlyContentLocationIsSet() {
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 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()));
}
}
\ No newline at end of file
diff --git a/org.hl7.fhir.r5/pom.xml b/org.hl7.fhir.r5/pom.xml
index b441c2f4c3..e17a32aa96 100644
--- a/org.hl7.fhir.r5/pom.xml
+++ b/org.hl7.fhir.r5/pom.xml
@@ -126,6 +126,12 @@
+
+ org.assertj
+ assertj-core
+ test
+
+
com.fasterxml.jackson.core
jackson-databind
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/FHIRPathEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/FHIRPathEngine.java
index 93b8a9f193..3db26b7e74 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/FHIRPathEngine.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/FHIRPathEngine.java
@@ -604,7 +604,7 @@ public TypeDetails checkOnTypes(Object appContext, String resourceType, List 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 TypeDetails checkOnTypes(Object appContext, String resourceType, TypeDeta
return res;
}
+ public TypeDetails checkOnTypes(Object appContext, String resourceType, TypeDetails types, ExpressionNode expr, List 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 String takeLog() {
/** 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
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/TerminologyCacheManager.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/TerminologyCacheManager.java
index 3e63ac0a8a..daa378a5b6 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/TerminologyCacheManager.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/TerminologyCacheManager.java
@@ -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 void commit(String token) throws IOException {
// 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());
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientR5.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientR5.java
index 063708bb81..b0f2f7b467 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientR5.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientR5.java
@@ -2,7 +2,6 @@
import java.net.URISyntaxException;
import java.util.EnumSet;
-import java.util.HashMap;
import java.util.Map;
/*
@@ -41,13 +40,10 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
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;
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/FHIRToolingClient.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/FHIRToolingClient.java
index a1a112b963..cfc5e39808 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/FHIRToolingClient.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/FHIRToolingClient.java
@@ -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 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
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 headers = new ArrayList<>();
- private String username;
- private String password;
+ private List 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 void initialize(String baseServiceUrl) throws URISyntaxException {
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 OperationOutcome validate(Class resourceClass, T
}
/**
- * 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 @@ protected void handleException(int code, String message, Exception e) throws EFh
* 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 ConceptMap updateClosure(String name, Coding coding) {
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 void setRetryCount(int retryCount) {
client.setRetryCount(retryCount);
}
- public void setClientHeaders(ArrayList headers) {
- this.headers = headers;
+ public void setClientHeaders(Iterable 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 generateHeaders(boolean hasBody) {
// Add any other headers
- if(this.headers != null) {
- this.headers.forEach(header -> builder.add(header.toString()));
- }
+ List 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 String getServerVersion() {
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));
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/Client.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/Client.java
index 1895c2ccf3..7eea93a942 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/Client.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/Client.java
@@ -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;
- 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;
- }
+ @Setter @Getter
+ private String base;
public ResourceRequest 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 ResourceRequest issueGetResourceRequest(URI resourceUri,
String resourceFormat,
- Headers headers,
+ Iterable 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 ResourceRequest 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 ResourceRequest issuePutRequest(URI resourceUri,
byte[] payload,
String resourceFormat,
- Headers headers,
+ Iterable 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 ResourceRequest issuePostRequest(URI resourceUri,
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 ResourceRequest issuePostRequest(URI resourceUri,
byte[] payload,
String resourceFormat,
- Headers headers,
+ Iterable 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 Bundle issuePostFeedRequest(URI resourceUri,
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 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 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 Bundle executeBundleRequest(HTTPRequest request,
+ String resourceFormat,
+ Iterable 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 ResourceRequest executeFhirRequest(Request.Builder request,
- String resourceFormat,
- Headers headers,
- String message,
- int retryCount,
- long timeout) throws IOException {
+ public ResourceRequest executeFhirRequest(HTTPRequest request,
+ String resourceFormat,
+ Iterable 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();
}
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/ClientHeaders.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/ClientHeaders.java
index efe62ea30c..14e20fa397 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/ClientHeaders.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/ClientHeaders.java
@@ -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 @@
*/
public class ClientHeaders {
- private final ArrayList headers;
+ private final List headers;
public ClientHeaders() {
this.headers = new ArrayList<>();
}
- public ClientHeaders(ArrayList headers) {
- this.headers = headers;
+ public ClientHeaders(List headers) {
+
+ this.headers = new ArrayList<>(headers);
}
- public ArrayList headers() {
+ public List 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 ClientHeaders addHeader(Header header) throws FHIRException {
/**
* 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 headerList) throws FHIRException {
+ public ClientHeaders addHeaders(List 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 headerList) throws FHIRException {
+ public ClientHeaders removeHeaders(List 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 ClientHeaders clearHeaders() {
@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}"));
}
}
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/FhirLoggingInterceptor.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/FhirLoggingInterceptor.java
deleted file mode 100644
index 1314e6c8a7..0000000000
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/FhirLoggingInterceptor.java
+++ /dev/null
@@ -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 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 headerList = new ArrayList<>();
- Map> 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();
- }
-}
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/FhirRequestBuilder.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/FhirRequestBuilder.java
index 49ee7d8816..750bce23f2 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/FhirRequestBuilder.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/FhirRequestBuilder.java
@@ -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.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 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 headers) {
+ List allHeaders = new ArrayList<>();
+ request.getHeaders().forEach(allHeaders::add);
- /**
- * Adds necessary headers for all REST requests.
- * User-Agent : hapi-fhir-tooling-client
- *
- * @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 getResourceFormatHeaders(HTTPRequest httpRequest, String format) {
+ List 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 @@ protected static boolean hasError(OperationOutcome oo) {
|| 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> 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.
- *
- * 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 FhirRequestBuilder withResourceFormat(String resourceFormat) {
return this;
}
- public FhirRequestBuilder withHeaders(Headers headers) {
+ public FhirRequestBuilder withHeaders(Iterable headers) {
this.headers = headers;
return this;
}
@@ -218,7 +129,7 @@ public FhirRequestBuilder withRetryCount(int retryCount) {
return this;
}
- public FhirRequestBuilder withLogger(FhirLoggingInterceptor logger) {
+ public FhirRequestBuilder withLogger(ToolingClientLogger logger) {
this.logger = logger;
return this;
}
@@ -229,20 +140,16 @@ public FhirRequestBuilder withTimeout(long timeout, TimeUnit unit) {
return this;
}
- protected Request buildRequest() {
- return httpRequest.build();
- }
-
public ResourceRequest 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(resource, response.code(), getLocationHeader(response.headers()));
+ return new ResourceRequest(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 Bundle executeAsBatch() throws IOException {
* Unmarshalls a resource from the response stream.
*/
@SuppressWarnings("unchecked")
- protected T unmarshalReference(Response response, String format, String resourceType) {
- int code = response.code();
+ protected 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 @@ protected T unmarshalReference(Response response, String fo
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 @@ protected T unmarshalReference(Response response, String fo
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 @@ protected T unmarshalReference(Response response, String fo
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 @@ protected T unmarshalReference(Response response, String fo
/**
* 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.
- *
+ *
* Currently supports only "json" and "xml" formats.
*
* @param format One of "json" or "xml".
@@ -362,4 +269,19 @@ protected IParser getParser(String format) {
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 headers) {
+ String locationHeader = HTTPHeaderUtil.getSingleHeader(headers, LOCATION_HEADER);
+
+ if (locationHeader != null) {
+ return locationHeader;
+ }
+ return HTTPHeaderUtil.getSingleHeader(headers, CONTENT_LOCATION_HEADER);
+ }
}
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/RetryInterceptor.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/RetryInterceptor.java
deleted file mode 100644
index d6f4e2de11..0000000000
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/RetryInterceptor.java
+++ /dev/null
@@ -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);
- }
-
-}
\ No newline at end of file
diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/FHIRToolingClientTest.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/FHIRToolingClientTest.java
index 6987d1fc05..caa44aa5e5 100644
--- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/FHIRToolingClientTest.java
+++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/FHIRToolingClientTest.java
@@ -5,6 +5,7 @@
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.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> headersArgumentCaptor;
+
@BeforeEach
void setUp() throws IOException, URISyntaxException {
+ MockitoAnnotations.openMocks(this);
mockClient = Mockito.mock(Client.class);
ResourceRequest 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 getHeaders() {
+ private List getHeaders() {
return new ArrayList<>(Arrays.asList(h1, h2, h3));
}
+ private List getHeadersWithAgent() {
+ return new ArrayList<>(Arrays.asList(h1, h2, h3, agentHeader));
+ }
+
private Bundle generateBundle() {
Patient patient = generatePatient();
Observation observation = generateObservation();
@@ -140,36 +148,37 @@ private Observation generateObservation() {
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 argumentCaptorValue) {
+ List 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 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 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 headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
toolingClient.setClientHeaders(getHeaders());
Exception exception = assertThrows(FHIRException.class, () -> {
toolingClient.getTerminologyCapabilities();
@@ -180,24 +189,23 @@ void getTerminologyCapabilitiesNotSupported() throws IOException {
@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 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 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 @@ void getTerminologyCapabilitiesStatementFailsForJSONandXML() throws IOException
@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 headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
assertEquals(ResourceFormat.RESOURCE_JSON.getHeader(), toolingClient.getPreferredResourceFormat());
toolingClient.setClientHeaders(getHeaders());
@@ -222,18 +229,17 @@ void getCapabilitiesStatement() throws IOException {
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
assertEquals(ResourceFormat.RESOURCE_JSON.getHeader(), toolingClient.getPreferredResourceFormat());
- Headers argumentCaptorValue = headersArgumentCaptor.getValue();
+ Iterable 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 headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
assertEquals(ResourceFormat.RESOURCE_JSON.getHeader(), toolingClient.getPreferredResourceFormat());
toolingClient.setClientHeaders(getHeaders());
toolingClient.getCapabilitiesStatement();
@@ -241,14 +247,14 @@ void getCapabilitiesStatementFailsForJSON() throws IOException {
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
assertEquals(ResourceFormat.RESOURCE_XML.getHeader(), toolingClient.getPreferredResourceFormat());
- Headers argumentCaptorValue = headersArgumentCaptor.getValue();
+ Iterable 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 @@ void getCapabilitiesStatementFailsForJSONandXML() throws IOException {
@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 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 argumentCaptorValue = headersArgumentCaptor.getValue();
checkHeaders(argumentCaptorValue);
assertEquals(ResourceFormat.RESOURCE_JSON.getHeader(), toolingClient.getPreferredResourceFormat());
@@ -280,19 +285,18 @@ void getCapabilitiesStatementQuick() throws IOException {
@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 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 argumentCaptorValue = headersArgumentCaptor.getValue();
checkHeaders(argumentCaptorValue);
assertEquals(ResourceFormat.RESOURCE_XML.getHeader(), toolingClient.getPreferredResourceFormat());
@@ -301,11 +305,10 @@ void getCapabilitiesStatementQuickFailsForJSON() throws IOException {
@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 headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
toolingClient.setClientHeaders(getHeaders());
assertEquals(ResourceFormat.RESOURCE_JSON.getHeader(), toolingClient.getPreferredResourceFormat());
Exception exception = assertThrows(FHIRException.class, () -> {
@@ -317,63 +320,58 @@ void getCapabilitiesStatementQuickFailsForJSONandXML() throws IOException {
@Test
void read() throws IOException {
- ArgumentCaptor 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 argumentCaptorValue = headersArgumentCaptor.getValue();
checkHeaders(argumentCaptorValue);
}
@Test
void vread() throws IOException {
- ArgumentCaptor 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 argumentCaptorValue = headersArgumentCaptor.getValue();
checkHeaders(argumentCaptorValue);
}
@Test
void getCanonical() throws IOException {
- ArgumentCaptor 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 argumentCaptorValue = headersArgumentCaptor.getValue();
checkHeaders(argumentCaptorValue);
}
@Test
void update() throws IOException {
- ArgumentCaptor 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 argumentCaptorValue = headersArgumentCaptor.getValue();
checkHeaders(argumentCaptorValue);
}
@Test
void validate() throws IOException {
- ArgumentCaptor 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 argumentCaptorValue = headersArgumentCaptor.getValue();
checkHeaders(argumentCaptorValue);
}
}
\ No newline at end of file
diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/network/ClientHeadersTest.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/network/ClientHeadersTest.java
index 7f102bf735..2dd985409d 100644
--- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/network/ClientHeadersTest.java
+++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/network/ClientHeadersTest.java
@@ -4,20 +4,19 @@
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 @@ void addHeaderDuplicateAdd() {
@Test
@DisplayName("Happy path add headers as list.")
void addHeaders() {
- List headersList = Arrays.asList(h1, h2, h3);
+ List headersList = Arrays.asList(h1, h2, h3);
clientHeaders.addHeaders(headersList);
Assertions.assertEquals(3, clientHeaders.headers().size());
Assertions.assertEquals(headersList, clientHeaders.headers());
@@ -52,7 +51,7 @@ void addHeaders() {
@Test
@DisplayName("Happy path add headers as list.")
void addHeadersDuplicateAdd() {
- List headersList = Arrays.asList(h1, h2, h1);
+ List headersList = Arrays.asList(h1, h2, h1);
Assertions.assertThrows(FHIRException.class, () -> clientHeaders.addHeaders(headersList));
}
@@ -64,7 +63,7 @@ void removeHeader() {
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 @@ void removeHeaderUnknown() {
@Test
@DisplayName("Happy path remove list of existing headers.")
void removeHeaders() {
- List headersToAddList = Arrays.asList(h1, h2, h3);
- List headersToRemoveList = Arrays.asList(h2, h3);
+ List headersToAddList = Arrays.asList(h1, h2, h3);
+ List headersToRemoveList = Arrays.asList(h2, h3);
clientHeaders.addHeaders(headersToAddList);
clientHeaders.removeHeaders(headersToRemoveList);
Assertions.assertEquals(1, clientHeaders.headers().size());
@@ -89,15 +88,15 @@ void removeHeaders() {
@Test
@DisplayName("Remove list containing unknown header.")
void removeHeadersUnknown() {
- List headersToAddList = Arrays.asList(h1, h3);
- List headersToRemoveList = Arrays.asList(h2, h3);
+ List headersToAddList = Arrays.asList(h1, h3);
+ List headersToRemoveList = Arrays.asList(h2, h3);
clientHeaders.addHeaders(headersToAddList);
Assertions.assertThrows(FHIRException.class, () -> clientHeaders.removeHeaders(headersToRemoveList));
}
@Test
void clearHeaders() {
- List headersToAddList = Arrays.asList(h1, h3);
+ List headersToAddList = Arrays.asList(h1, h3);
clientHeaders.addHeaders(headersToAddList);
Assertions.assertEquals(2, clientHeaders.headers().size());
clientHeaders.clearHeaders();
diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/network/FhirRequestBuilderTest.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/network/FhirRequestBuilderTest.java
index 7be6d672a8..415edf1fd0 100644
--- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/network/FhirRequestBuilderTest.java
+++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/network/FhirRequestBuilderTest.java
@@ -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> 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> headersMap = request.build().headers().toMultimap();
+ Iterable headers = FhirRequestBuilder.getResourceFormatHeaders(request, testFormat);
+
+ Map> 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 @@ void addResourceFormatHeadersGET() {
@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);
+
+ Iterable headers = FhirRequestBuilder.getResourceFormatHeaders(request, testFormat);
- Map> headersMap = request.build().headers().toMultimap();
+ Map> 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 @@ void addResourceFormatHeadersPOST() {
"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> 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 @@ void hasErrorTestNoErrors() {
@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 headers = List.of(new HTTPHeader(FhirRequestBuilder.CONTENT_LOCATION_HEADER, expectedContentLocationHeader));
+
Assertions.assertEquals(expectedContentLocationHeader, FhirRequestBuilder.getLocationHeader(headers));
}
@@ -141,18 +109,18 @@ void getLocationHeaderWhenOnlyContentLocationIsSet() {
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 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()));
}
}
\ No newline at end of file
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/HTTPHeader.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/HTTPHeader.java
new file mode 100644
index 0000000000..b45e9ff7fd
--- /dev/null
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/HTTPHeader.java
@@ -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;
+ }
+}
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/HTTPHeaderUtil.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/HTTPHeaderUtil.java
new file mode 100644
index 0000000000..06bf482424
--- /dev/null
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/HTTPHeaderUtil.java
@@ -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> getMultimap(Iterable headers) {
+ Map> result = new HashMap<>();
+ if (headers != null) {
+ for (HTTPHeader header : headers) {
+ List values = result.getOrDefault(header.getName(), new ArrayList<>());
+ values.add(header.getValue());
+ result.put(header.getName(), values);
+ }
+ }
+ return result;
+ }
+
+ public static Iterable getHeaders(Iterable headers, String key) {
+ List 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 headers, String key) {
+ for (HTTPHeader header : headers) {
+ if (header.getName().equalsIgnoreCase(key)) {
+ return header.getValue();
+ }
+ }
+ return null;
+ }
+}
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/HTTPRequest.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/HTTPRequest.java
new file mode 100644
index 0000000000..19bfa9d364
--- /dev/null
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/HTTPRequest.java
@@ -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 headers;
+}
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/HTTPResult.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/HTTPResult.java
index 0a3986ec1c..3946442b83 100644
--- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/HTTPResult.java
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/HTTPResult.java
@@ -2,39 +2,40 @@
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 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 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 void checkThrowException() throws IOException {
}
}
- public String getMessage() {
- return message;
- }
-
public String getContentAsString() {
return new String(content, StandardCharsets.UTF_8);
- }
+ }
}
\ No newline at end of file
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessor.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessor.java
new file mode 100644
index 0000000000..b12c841757
--- /dev/null
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessor.java
@@ -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 {
+
+ /**
+ * 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 serverAuthDetails) {
+ super(userAgent, serverAuthDetails);
+ this.timeout = 5000;
+ this.timeoutUnit = TimeUnit.MILLISECONDS;
+ }
+
+ protected HTTPRequest httpRequestWithDefaultHeaders(HTTPRequest request) {
+ List 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 headers = new ArrayList<>();
+ requestWithDefaultHeaders.getHeaders().forEach(headers::add);
+
+ for (Map.Entry 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 getHeadersFromResponse(Response response) {
+ List 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();
+ }
+
+}
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccess.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccess.java
index f1afac25db..3ebb85c20d 100644
--- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccess.java
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccess.java
@@ -31,23 +31,16 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
-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
+ *
* 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 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
*
*/
public class ManagedWebAccess {
-
+
public interface IWebAccessor {
HTTPResult get(String url, String accept, Map headers) throws IOException;
HTTPResult post(String url, byte[] bytes, String contentType, String accept, Map headers) throws IOException;
HTTPResult put(String url, byte[] bytes, String contentType, String accept, Map 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 enum WebAccessPolicy {
}
private static WebAccessPolicy accessPolicy = WebAccessPolicy.DIRECT; // for legacy reasons
+ //TODO get this from fhir settings
private static List allowedDomains = new ArrayList<>();
+ @Getter
private static IWebAccessor accessor;
+
+ @Getter
+ private static IFhirWebAccessor fhirWebAccessor;
+
+ @Getter
private static String userAgent;
private static List serverAuthDetails;
-
-
+
public static WebAccessPolicy getAccessPolicy() {
return accessPolicy;
}
@@ -97,37 +100,51 @@ static boolean inAllowedPaths(String pathname) {
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());
+ }
}
\ No newline at end of file
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessUtils.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessUtils.java
new file mode 100644
index 0000000000..a973536335
--- /dev/null
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessUtils.java
@@ -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 serverAuthDetails) {
+ if (serverAuthDetails != null) {
+ for (ServerDetailsPOJO t : serverAuthDetails) {
+ if (url.startsWith(t.getUrl())) {
+ return t;
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessBuilder.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessor.java
similarity index 54%
rename from org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessBuilder.java
rename to org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessor.java
index 79f27add70..4c364a2f59 100644
--- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessBuilder.java
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessor.java
@@ -9,61 +9,30 @@
import org.hl7.fhir.utilities.settings.ServerDetailsPOJO;
+/**
+ * Simple HTTP client for making requests to a server.
+ */
+public class ManagedWebAccessor extends ManagedWebAccessorBase {
-public class ManagedWebAccessBuilder {
-
- private String userAgent;
- private HTTPAuthenticationMode authenticationMode;
- private String username;
- private String password;
- private String token;
- private String accept;
- private List serverAuthDetails;
- private Map headers = new HashMap();
-
- public ManagedWebAccessBuilder(String userAgent, List 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 ManagedWebAccessBuilder withToken(String token) {
- this.authenticationMode = HTTPAuthenticationMode.TOKEN;
- this.token = token;
- return this;
+ public ManagedWebAccessor(String userAgent, List serverAuthDetails) {
+ super(userAgent, serverAuthDetails);
}
- private Map headers() {
- Map headers = new HashMap();
- headers.putAll(this.headers);
- if (authenticationMode == HTTPAuthenticationMode.TOKEN) {
- headers.put("Authorization", "Bearer " + token);
- } else if (authenticationMode == HTTPAuthenticationMode.BASIC) {
- String auth = username+":"+password;
+ private Map newHeaders() {
+ Map headers = new HashMap(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 @@ private SimpleHTTPClient setupClient(String url) throws IOException {
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 entry : this.getHeaders().entrySet()) {
+ client.addHeader(entry.getKey(), entry.getValue());
+ }
+
+ if (getUserAgent() != null) {
+ client.addHeader("User-Agent", getUserAgent());
}
- if (authenticationMode != null && authenticationMode != HTTPAuthenticationMode.NONE) {
- client.setAuthenticationMode(authenticationMode);
- switch (authenticationMode) {
+ 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 @@ private SimpleHTTPClient setupClient(String url) throws IOException {
}
}
}
- 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 HTTPResult get(String url) throws IOException {
}
}
-
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 HTTPResult post(String url, byte[] content, String contentType) throws IO
}
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");
}
}
-
}
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessorBase.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessorBase.java
new file mode 100644
index 0000000000..67e4df641d
--- /dev/null
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessorBase.java
@@ -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> {
+ @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 serverAuthDetails;
+ @Getter
+ private final Map headers = new HashMap<>();
+
+ public ManagedWebAccessorBase(String userAgent, List 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;
+ }
+}
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/SimpleHTTPClient.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/SimpleHTTPClient.java
index 0e448293b2..bf35ee9061 100644
--- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/SimpleHTTPClient.java
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/SimpleHTTPClient.java
@@ -20,27 +20,10 @@
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 headers = new ArrayList<>();
+ private List headers = new ArrayList<>();
@Getter @Setter
private HTTPAuthenticationMode authenticationMode;
@@ -58,7 +41,7 @@ public String getValue() {
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 HTTPResult get(String url, String accept) throws IOException {
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 @@ private void setHeaders(HttpURLConnection c) {
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));
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/okhttpimpl/LoggingInterceptor.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/okhttpimpl/LoggingInterceptor.java
new file mode 100644
index 0000000000..35dd0f9470
--- /dev/null
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/okhttpimpl/LoggingInterceptor.java
@@ -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 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 headerList = new ArrayList<>();
+ Map> 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();
+ }
+}
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/okhttpimpl/ProxyAuthenticator.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/okhttpimpl/ProxyAuthenticator.java
new file mode 100644
index 0000000000..1f59167d3c
--- /dev/null
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/okhttpimpl/ProxyAuthenticator.java
@@ -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();
+ }
+}
diff --git a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/RetryInterceptor.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/okhttpimpl/RetryInterceptor.java
similarity index 94%
rename from org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/RetryInterceptor.java
rename to org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/okhttpimpl/RetryInterceptor.java
index e3b6ec084e..ca71c9a019 100644
--- a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/RetryInterceptor.java
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/okhttpimpl/RetryInterceptor.java
@@ -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 RetryInterceptor(int maxRetry) {
}
@Override
- public Response intercept(Interceptor.Chain chain) throws IOException {
+ public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = null;
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java
index 0d4b2a79d6..e5c0296078 100644
--- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java
@@ -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";
@@ -1122,4 +1120,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";
}
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/PackageClient.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/PackageClient.java
index 4bf8273560..6f6feeb849 100644
--- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/PackageClient.java
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/PackageClient.java
@@ -14,7 +14,6 @@
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.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 Date getNewPackages(Date lastCalled, List updates) {
}
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());
}
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/settings/FhirSettings.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/settings/FhirSettings.java
index dc69f3fd04..060ce2195d 100644
--- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/settings/FhirSettings.java
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/settings/FhirSettings.java
@@ -4,6 +4,7 @@
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 static List getPackageServers() {
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 getTerminologyServers() {
+ getInstance();
+ if (instance.fhirSettings.getTerminologyServers() == null) {
+ return Collections.emptyList();
+ }
+ return Arrays.asList(instance.fhirSettings.getTerminologyServers().getServers().toArray(new ServerDetailsPOJO[]{}));
}
}
diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties
index 1f44ca339c..b4c87aeaa5 100644
--- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties
+++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties
@@ -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
@@ -1090,7 +1086,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}
@@ -1155,4 +1151,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}
-
\ No newline at end of file
+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}
+
diff --git a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/http/HTTPHeaderTests.java b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/http/HTTPHeaderTests.java
new file mode 100644
index 0000000000..ad86e35e8c
--- /dev/null
+++ b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/http/HTTPHeaderTests.java
@@ -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);
+ }
+}
diff --git a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessorTests.java b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessorTests.java
new file mode 100644
index 0000000000..be68a2571b
--- /dev/null
+++ b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessorTests.java
@@ -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+"\".");
+ }
+}
diff --git a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/http/ManagedWebAccessAuthTests.java b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/http/ManagedWebAccessAuthTests.java
new file mode 100644
index 0000000000..f57d93988f
--- /dev/null
+++ b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/http/ManagedWebAccessAuthTests.java
@@ -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");
+ }
+}
\ No newline at end of file
diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java
index 3684923b36..ff1d9d4de1 100644
--- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java
+++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java
@@ -71,6 +71,7 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
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 @@ protected void readParamsAndExecuteTask(CliContext cliContext, String[] args) th
if (cliContext.getFhirSettingsFile() != null) {
FhirSettings.setExplicitFilePath(cliContext.getFhirSettingsFile());
}
+ ManagedWebAccess.loadFromFHIRSettings();
FileFormat.checkCharsetAndWarnIfNotUTF8(System.out);
diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java
index 0b8f31c0c4..c598bae083 100644
--- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java
+++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java
@@ -1047,7 +1047,8 @@ private void checkElementUsage(List errors, Element element,
}
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 errors, String path, Element focus, Address fixed, String fixedSource, boolean pattern, String context) {
@@ -3209,10 +3210,10 @@ else if (Utilities.isAllWhitespace(e.primitiveValue()))
}
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 @@ private boolean sliceMatches(ValidationContext valContext, Element element, Stri
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 @@ private boolean sliceMatches(ValidationContext valContext, Element element, Stri
} 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 list = new ArrayList<>();
+ boolean ref = discriminator.endsWith(".resolve()") || discriminator.equals("resolve()");
+ for (TypeRefComponent tr : criteriaElement.getType()) {
+ list.addAll(ref ? tr.getTargetProfile() : tr.getProfile());
}
- List 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 @@ private boolean validateElement(ValidationContext valContext, List msgs = new ArrayList();
- 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));
diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/MeasureValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/MeasureValidator.java
index ce526675eb..2333cfc780 100644
--- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/MeasureValidator.java
+++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/MeasureValidator.java
@@ -237,6 +237,17 @@ public boolean validateMeasureReport(ValidationContext hostContext, List 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;
diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java
index 13ef234a0f..1acff417c8 100644
--- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java
+++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java
@@ -16,6 +16,7 @@
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.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 boolean validateStructureDefinition(List errors, Eleme
}
}
} 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 @@ private boolean validateElementDefinition(List errors, List 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 warnings = new ArrayList();
+ 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"));
@@ -595,6 +635,10 @@ private boolean validateElementDefinition(List errors, List errors, NodeStack stack, Element typeE, String tc, StructureDefinition tsd, String path, StructureDefinition sd) {
boolean ok = true;
if (tsd.hasExtension(ToolingExtensions.EXT_TYPE_PARAMETER)) {
diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/special/TxTester.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/special/TxTester.java
index 36708c84ae..896fb2d758 100644
--- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/special/TxTester.java
+++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/special/TxTester.java
@@ -4,7 +4,6 @@
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.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.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 boolean execute(String version, List modes, String filter) throws
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 @@ private boolean runTest(JsonObject suite, JsonObject test, ITerminologyClient tx
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);
}
}
diff --git a/org.hl7.fhir.validation/src/test/java/ManagedWebAccessAuthTests.java b/org.hl7.fhir.validation/src/test/java/ManagedWebAccessAuthTests.java
new file mode 100644
index 0000000000..ce57a67bc3
--- /dev/null
+++ b/org.hl7.fhir.validation/src/test/java/ManagedWebAccessAuthTests.java
@@ -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");
+ }
+}
diff --git a/pom.xml b/pom.xml
index f6bd4d8509..e408832b0c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -434,7 +434,7 @@
while if true it will use an executable. -->
true
512m
- 768m
+ 1000m
true
-J-XX:MaxRAMPercentage=50.0
diff --git a/setup-and-cache-job-template.yml b/setup-and-cache-job-template.yml
index a5100e4ed2..3469e06e69 100644
--- a/setup-and-cache-job-template.yml
+++ b/setup-and-cache-job-template.yml
@@ -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'