From 435dbbec9ba3dae756ce92de3fcd4072522cce77 Mon Sep 17 00:00:00 2001 From: dotasek Date: Mon, 21 Oct 2024 17:19:53 -0400 Subject: [PATCH 01/42] WIP start moving FhirRequestBuilder to ManagedWebAccess --- .../hl7/fhir/r5/profilemodel/PEBuilder.java | 1 - .../r5/profilemodel/PEDefinitionElement.java | 1 - .../client/TerminologyClientR5.java | 5 +- .../client/network/FhirRequestBuilder.java | 30 +++--- .../hl7/fhir/utilities/http/FhirRequest.java | 22 ++++ .../http/ManagedFhirWebAccessBuilder.java | 73 +++++++++++++ .../fhir/utilities/http/ManagedWebAccess.java | 34 ++++-- .../http/ManagedWebAccessBuilder.java | 100 +++++++----------- .../http/ManagedWebAccessBuilderBase.java | 60 +++++++++++ .../http/okhttpimpl/RetryInterceptor.java | 62 +++++++++++ 10 files changed, 293 insertions(+), 95 deletions(-) create mode 100644 org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/FhirRequest.java create mode 100644 org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java create mode 100644 org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessBuilderBase.java create mode 100644 org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/okhttpimpl/RetryInterceptor.java diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEBuilder.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEBuilder.java index d35e218d3b..a685fbb001 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEBuilder.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEBuilder.java @@ -50,7 +50,6 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; -import org.hl7.fhir.utilities.DebugUtilities; import org.hl7.fhir.utilities.Utilities; /** diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionElement.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionElement.java index d196d553c6..916b0928c7 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionElement.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionElement.java @@ -35,7 +35,6 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; -import org.hl7.fhir.utilities.DebugUtilities; public class PEDefinitionElement extends PEDefinition { 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..4254edc815 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; @@ -84,6 +80,7 @@ public String getVersion() { public TerminologyClientR5(String id, String address, String userAgent) throws URISyntaxException { this.client = new FHIRToolingClient(address, userAgent); + //FIXME set up FHIR Tooling Client to use ManagedWebAccess setClientHeaders(new ClientHeaders()); this.id = id; } 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..0ea615c8d4 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 @@ -7,8 +7,8 @@ import javax.annotation.Nonnull; +import okhttp3.*; 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; @@ -21,16 +21,10 @@ 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.http.FhirRequest; +import org.hl7.fhir.utilities.http.ManagedWebAccess; 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"; @@ -63,6 +57,16 @@ public class FhirRequestBuilder { private FhirLoggingInterceptor logger = null; private String source; + //TODO this should be the only constructor. There should be no okHttp exposure. + public FhirRequestBuilder(FhirRequest fhirRequest, String source) { + this.source = source; + + RequestBody body = RequestBody.create(fhirRequest.getBody()); + this.httpRequest = new Request.Builder() + .url(fhirRequest.getUrl()) + .method(fhirRequest.getMethod().name(), body); + } + public FhirRequestBuilder(Request.Builder httpRequest, String source) { this.httpRequest = httpRequest; this.source = source; @@ -162,11 +166,9 @@ protected static String getLocationHeader(Headers headers) { * * @return {@link OkHttpClient} instance */ + //TODO replace this. protected OkHttpClient getHttpClient() { - if (FhirSettings.isProhibitNetworkAccess()) { - throw new FHIRException("Network Access is prohibited in this context"); - } - + if (okHttpClient == null) { okHttpClient = new OkHttpClient(); } @@ -235,7 +237,7 @@ protected Request buildRequest() { public ResourceRequest execute() throws IOException { formatHeaders(httpRequest, resourceFormat, headers); - Response response = getHttpClient().newCall(httpRequest.build()).execute(); + Response response = ManagedWebAccess.httpCall(httpRequest);//getHttpClient().newCall(httpRequest.build()).execute(); T resource = unmarshalReference(response, resourceFormat, null); return new ResourceRequest(resource, response.code(), getLocationHeader(response.headers())); } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/FhirRequest.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/FhirRequest.java new file mode 100644 index 0000000000..ad1cd692c6 --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/FhirRequest.java @@ -0,0 +1,22 @@ +package org.hl7.fhir.utilities.http; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.With; + +@AllArgsConstructor +public class FhirRequest { + + public enum HttpMethod { + GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH + } + + @With @Getter + private final String url; + + @With @Getter + private HttpMethod method; + + @With @Getter + private byte[] body; +} diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java new file mode 100644 index 0000000000..abf39bc1ff --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java @@ -0,0 +1,73 @@ +package org.hl7.fhir.utilities.http; + +import lombok.With; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.hl7.fhir.utilities.settings.ServerDetailsPOJO; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public class ManagedFhirWebAccessBuilder extends ManagedWebAccessBuilderBase{ + + /** + * The singleton instance of the HttpClient, used for all requests. + */ + private static OkHttpClient okHttpClient; + + private long timeout; + private int retries; + + public ManagedFhirWebAccessBuilder withTimeout(long timeout) { + this.timeout = timeout; + return this; + } + + public ManagedFhirWebAccessBuilder withRetries(int retries) { + this.retries = retries; + return this; + } + + public ManagedFhirWebAccessBuilder(String userAgent, List serverAuthDetails) { + super(userAgent, serverAuthDetails); + } + + private void setHeaders(Request.Builder httpRequest) { + for (Map.Entry entry : this.getHeaders().entrySet()) { + httpRequest.header(entry.getKey(), entry.getValue()); + } + } + + public Response httpCall(Request.Builder httpRequest) throws IOException { + switch (ManagedWebAccess.getAccessPolicy()) { + case DIRECT: + OkHttpClient okHttpClient = getOkHttpClient(); + //TODO check and throw based on httpRequest: + // if (!ManagedWebAccess.inAllowedPaths(url)) { + // throw new IOException("The pathname '"+url+"' cannot be accessed by policy"); + // } + //TODO add auth headers to httpRequest + return okHttpClient.newCall(httpRequest.build()).execute(); + case MANAGED: + setHeaders(httpRequest); + return ManagedWebAccess.getFhirWebAccessor().httpCall(httpRequest); + case PROHIBITED: + throw new IOException("Access to the internet is not allowed by local security policy"); + default: + throw new IOException("Internal Error"); + } + } + + + + private OkHttpClient getOkHttpClient() { + if (okHttpClient == null) { + okHttpClient = new OkHttpClient(); + } + + return okHttpClient; + } + +} 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..2e2023e3f1 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 @@ -42,6 +42,9 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS import java.util.List; import java.util.Map; +import lombok.Getter; +import okhttp3.Request; +import okhttp3.Response; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.settings.ServerDetailsPOJO; @@ -57,13 +60,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 { + Response httpCall(Request.Builder 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 @@ -72,11 +79,16 @@ public enum WebAccessPolicy { private static WebAccessPolicy accessPolicy = WebAccessPolicy.DIRECT; // for legacy reasons 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,22 +109,18 @@ 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 ManagedWebAccessBuilder builder() { return new ManagedWebAccessBuilder(userAgent, serverAuthDetails); } + public static ManagedFhirWebAccessBuilder fhirBuilder() { + return new ManagedFhirWebAccessBuilder(userAgent, serverAuthDetails); + } + public static HTTPResult get(String url) throws IOException { return builder().get(url); } @@ -130,4 +138,8 @@ public static HTTPResult put(String url, byte[] content, String contentType, Str return builder().withAccept(accept).put(url, content, contentType); } + public static Response httpCall(Request.Builder httpRequest) throws IOException { + return fhirBuilder().httpCall(httpRequest); + } + } \ No newline at end of file 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/ManagedWebAccessBuilder.java index 79f27add70..a695e0007b 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/ManagedWebAccessBuilder.java @@ -7,61 +7,37 @@ import java.util.List; import java.util.Map; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; import org.hl7.fhir.utilities.settings.ServerDetailsPOJO; +/** + * Simple HTTP client for making requests to a server. + */ +public class ManagedWebAccessBuilder extends ManagedWebAccessBuilderBase { -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; + super(userAgent, 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; + return super.withAccept(accept); } - public ManagedWebAccessBuilder withToken(String token) { - this.authenticationMode = HTTPAuthenticationMode.TOKEN; - this.token = token; - return this; - } - - private Map headers() { + private Map newHeaders() { 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; + headers.putAll(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)); } - if (userAgent != null) { - headers.put("User-Agent", userAgent); + if (getUserAgent() != null) { + headers.put("User-Agent", getUserAgent()); } return headers; @@ -72,21 +48,21 @@ 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); + if (getUserAgent() != null) { + client.addHeader("User-Agent", getUserAgent()); } - if (authenticationMode != null && authenticationMode != HTTPAuthenticationMode.NONE) { - client.setAuthenticationMode(authenticationMode); - switch (authenticationMode) { + if (getAuthenticationMode() != null && 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 { @@ -109,17 +85,15 @@ 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 (getServerAuthDetails() != null) { + for (ServerDetailsPOJO t : getServerAuthDetails()) { if (url.startsWith(t.getUrl())) { return t; } @@ -132,9 +106,9 @@ public HTTPResult get(String url) throws IOException { switch (ManagedWebAccess.getAccessPolicy()) { case DIRECT: SimpleHTTPClient client = setupClient(url); - return client.get(url, accept); + return client.get(url, getAccept()); case MANAGED: - return ManagedWebAccess.getAccessor().get(url, accept, headers()); + return ManagedWebAccess.getAccessor().get(url, getAccept(), newHeaders()); case PROHIBITED: throw new IOException("Access to the internet is not allowed by local security policy"); default: @@ -142,14 +116,13 @@ public HTTPResult get(String url) throws IOException { } } - public HTTPResult post(String url, byte[] content, String contentType) throws IOException { switch (ManagedWebAccess.getAccessPolicy()) { case DIRECT: SimpleHTTPClient client = setupClient(url); - return client.post(url, contentType, content, accept); + return client.post(url, contentType, content, getAccept()); case MANAGED: - return ManagedWebAccess.getAccessor().post(url, content, contentType, accept, headers()); + return ManagedWebAccess.getAccessor().post(url, content, contentType, getAccept(), newHeaders()); case PROHIBITED: throw new IOException("Access to the internet is not allowed by local security policy"); default: @@ -161,14 +134,13 @@ public HTTPResult put(String url, byte[] content, String contentType) throws IOE switch (ManagedWebAccess.getAccessPolicy()) { case DIRECT: SimpleHTTPClient client = setupClient(url); - return client.put(url, contentType, content, accept); + return client.put(url, contentType, content, getAccept()); case MANAGED: - return ManagedWebAccess.getAccessor().put(url, content, contentType, accept, headers()); + return ManagedWebAccess.getAccessor().put(url, content, contentType, getAccept(), 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/ManagedWebAccessBuilderBase.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessBuilderBase.java new file mode 100644 index 0000000000..54f5d2a2d9 --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessBuilderBase.java @@ -0,0 +1,60 @@ +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 ManagedWebAccessBuilderBase> { + @Getter + private final String userAgent; + @Getter + private HTTPAuthenticationMode authenticationMode; + @Getter + private String username; + @Getter + private String password; + @Getter + private String token; + @Getter + private String accept; + @Getter + private final List serverAuthDetails; + @Getter + private Map headers = new HashMap(); + + public ManagedWebAccessBuilderBase(String userAgent, List serverAuthDetails) { + this.userAgent = userAgent; + this.serverAuthDetails = serverAuthDetails; + } + + @SuppressWarnings("unchecked") + final B self() { + return (B) this; + } + + public B withAccept(String accept) { + this.accept = accept; + return self(); + } + + 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(); + } +} diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/okhttpimpl/RetryInterceptor.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/okhttpimpl/RetryInterceptor.java new file mode 100644 index 0000000000..ca71c9a019 --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/okhttpimpl/RetryInterceptor.java @@ -0,0 +1,62 @@ +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. + */ +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(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 From f9ce823d7ca37f1a18bb7593d738a6ff6b9469d6 Mon Sep 17 00:00:00 2001 From: Jens Kristian Villadsen Date: Tue, 22 Oct 2024 22:37:28 +0200 Subject: [PATCH 02/42] Update PECodeGenerator.java --- .../java/org/hl7/fhir/r5/profilemodel/gen/PECodeGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/gen/PECodeGenerator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/gen/PECodeGenerator.java index 76629a3314..d477f39f11 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/gen/PECodeGenerator.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/gen/PECodeGenerator.java @@ -139,7 +139,7 @@ public void write(StringBuilder b, String copyright) { w(b, "public class "+name+" extends PEGeneratedBase {"); w(b); if (url != null) { - w(b, " private static final String CANONICAL_URL = \""+url+"\";"); + w(b, " public static final String CANONICAL_URL = \""+url+"\";"); w(b); } if (enums.length() > 0) { From a94e1bc2af427b1f840a7de54476abdbb7d73939 Mon Sep 17 00:00:00 2001 From: dotasek Date: Tue, 22 Oct 2024 17:33:50 -0400 Subject: [PATCH 03/42] WIP keep moving FhirRequestBuilder to ManagedWebAccess + fix some tests --- .../dstu3/utils/client/FHIRToolingClient.java | 14 +- .../network/FhirRequestBuilderTests.java | 6 + .../r4/utils/client/FHIRToolingClient.java | 13 +- org.hl7.fhir.r4b/pom.xml | 6 + .../r4b/utils/client/FHIRToolingClient.java | 39 +++-- .../fhir/r4b/utils/client/network/Client.java | 140 +++++++++------- .../client/network/FhirRequestBuilder.java | 42 +++-- org.hl7.fhir.r5/pom.xml | 6 + .../r5/utils/client/FHIRToolingClient.java | 93 ++++------- .../fhir/r5/utils/client/network/Client.java | 152 ++++++++++-------- .../utils/client/network/ClientHeaders.java | 28 ++-- .../client/network/FhirRequestBuilder.java | 40 +++-- .../utils/client/FHIRToolingClientTest.java | 106 ++++++------ .../client/network/ClientHeadersTest.java | 23 +-- .../network/FhirRequestBuilderTest.java | 9 +- .../hl7/fhir/utilities/http/FhirRequest.java | 18 ++- .../hl7/fhir/utilities/http/HTTPHeader.java | 15 ++ .../http/ManagedFhirWebAccessBuilder.java | 6 +- .../fhir/utilities/http/HTTPHeaderTests.java | 20 +++ 19 files changed, 436 insertions(+), 340 deletions(-) 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/test/java/org/hl7/fhir/utilities/http/HTTPHeaderTests.java 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..a596ffef2b 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,11 +3,7 @@ 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 org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.dstu3.model.CapabilityStatement; @@ -33,6 +29,7 @@ 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,7 +69,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient { private int maxResultSetSize = -1;//_count private CapabilityStatement capabilities; private Client client = new Client(); - private ArrayList
headers = new ArrayList<>(); + private List headers = new ArrayList<>(); private String username; private String password; private String userAgent; @@ -578,8 +575,9 @@ 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() { 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..7809c11189 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,9 +1,12 @@ package org.hl7.fhir.dstu3.utils.client.network; import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; import org.hl7.fhir.dstu3.formats.IParser; import org.hl7.fhir.utilities.ToolingClientLogger; +import org.hl7.fhir.utilities.http.FhirRequest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -58,6 +61,9 @@ public class FhirRequestBuilderTests { @Mock ToolingClientLogger logger; + public FhirRequestBuilderTests() throws MalformedURLException { + } + @BeforeEach public void beforeEach() { Mockito.doReturn(client).when(fhirRequestBuilder).getHttpClient(); 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..df3f3e55ac 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,10 +3,7 @@ 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 org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r4.model.Bundle; @@ -31,6 +28,7 @@ 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 @@ -74,7 +72,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient { private int maxResultSetSize = -1;// _count private CapabilityStatement capabilities; private Client client = new Client(); - private ArrayList
headers = new ArrayList<>(); + private List headers = new ArrayList<>(); private String username; private String password; private String userAgent; @@ -558,8 +556,9 @@ 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() { diff --git a/org.hl7.fhir.r4b/pom.xml b/org.hl7.fhir.r4b/pom.xml index ab4c353dd1..26025cc578 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/utils/client/FHIRToolingClient.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/FHIRToolingClient.java index 65f74dc192..66942512b0 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; /* @@ -42,6 +43,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.http.HTTPHeader; import java.io.IOException; import java.net.MalformedURLException; @@ -91,9 +93,11 @@ public class FHIRToolingClient extends FHIRBaseToolingClient{ private int maxResultSetSize = -1;// _count private CapabilityStatement capabilities; private Client client = new Client(); - private ArrayList
headers = new ArrayList<>(); + private List headers = new ArrayList<>(); private String username; private String password; + @Setter + @Getter private String userAgent; // Pass endpoint for client - URI @@ -537,42 +541,35 @@ 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(); + private Iterable generateHeaders() { + List headers = new ArrayList<>(); // Add basic auth header if it exists if (basicAuthHeaderExists()) { - builder.add(getAuthorizationHeader().toString()); + headers.add(getAuthorizationHeader()); } // Add any other headers if (this.headers != null) { - this.headers.forEach(header -> builder.add(header.toString())); + headers.addAll(this.headers); } if (!Utilities.noString(userAgent)) { - builder.add("User-Agent: " + userAgent); + headers.add(new HTTPHeader("User-Agent",userAgent)); } - return builder.build(); + return headers; } public boolean basicAuthHeaderExists() { return (username != null) && (password != null); } - public Header getAuthorizationHeader() { + public HTTPHeader 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 new HTTPHeader("Authorization", "Basic " + base64usernamePassword); } 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..42fb12c145 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,5 +1,7 @@ package org.hl7.fhir.r4b.utils.client.network; +import lombok.Getter; +import lombok.Setter; import okhttp3.Headers; import okhttp3.MediaType; import okhttp3.Request; @@ -8,9 +10,12 @@ 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.FhirRequest; +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 +23,161 @@ public class Client { public static final String DEFAULT_CHARSET = "UTF-8"; private static final long DEFAULT_TIMEOUT = 5000; + @Getter private ToolingClientLogger logger; private FhirLoggingInterceptor fhirLoggingInterceptor; + @Setter + @Getter private int retryCount; + @Setter + @Getter private long timeout = DEFAULT_TIMEOUT; - private byte[] payload; + //private byte[] payload; + @Setter @Getter private String base; - - public String getBase() { - return base; - } - - public void setBase(String base) { - this.base = base; - } - - - public ToolingClientLogger getLogger() { - return logger; - } public void setLogger(ToolingClientLogger logger) { this.logger = logger; this.fhirLoggingInterceptor = new FhirLoggingInterceptor(logger); } - public int getRetryCount() { - return retryCount; - } - - public void setRetryCount(int retryCount) { - this.retryCount = retryCount; - } - - public long getTimeout() { - return timeout; - } - - public void setTimeout(long timeout) { - this.timeout = timeout; - } - public ResourceRequest issueOptionsRequest(URI optionsUri, String resourceFormat, String message, long timeout) throws IOException { - this.payload = null; + /*FIXME delete once refactor is done Request.Builder request = new Request.Builder().method("OPTIONS", null).url(optionsUri.toURL()); - - return executeFhirRequest(request, resourceFormat, new Headers.Builder().build(), message, retryCount, timeout); +*/ + FhirRequest request = new FhirRequest() + .withUrl(optionsUri.toURL()) + .withMethod(FhirRequest.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; + Iterable headers, String message, long timeout) throws IOException { +/*FIXME delete once refactor is done Request.Builder request = new Request.Builder().url(resourceUri.toURL()); - +*/ + FhirRequest request = new FhirRequest() + .withUrl(resourceUri.toURL()) + .withMethod(FhirRequest.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; +/*FIXME delete once refactor is done RequestBody body = RequestBody.create(payload); Request.Builder request = new Request.Builder().url(resourceUri.toURL()).put(body); +*/ + FhirRequest request = new FhirRequest() + .withUrl(resourceUri.toURL()) + .withMethod(FhirRequest.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; + + /*FIXME delete once refactor is done RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload); Request.Builder request = new Request.Builder().url(resourceUri.toURL()).post(body); +*/ + FhirRequest request = new FhirRequest() + .withUrl(resourceUri.toURL()) + .withMethod(FhirRequest.HttpMethod.POST) + .withBody(payload) + .withContentType(getContentTypeWithDefaultCharset(resourceFormat)); return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout); } public boolean issueDeleteRequest(URI resourceUri) throws IOException { + /*FIXME delete once refactor is done Request.Builder request = new Request.Builder().url(resourceUri.toURL()).delete(); - return executeFhirRequest(request, null, new Headers.Builder().build(), null, retryCount, timeout) + */ + FhirRequest request = new FhirRequest() + .withUrl(resourceUri.toURL()) + .withMethod(FhirRequest.HttpMethod.DELETE); + return executeFhirRequest(request, null, Collections.emptyList(), null, retryCount, timeout) .isSuccessfulRequest(); } public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat) throws IOException { + /*FIXME delete once refactor is done Request.Builder request = new Request.Builder().url(resourceUri.toURL()); - - return executeBundleRequest(request, resourceFormat, new Headers.Builder().build(), null, retryCount, timeout); + */ + FhirRequest request = new FhirRequest() + .withUrl(resourceUri.toURL()) + .withMethod(FhirRequest.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); + /*FIXME delete once refactor is done 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); +*/ + FhirRequest request = new FhirRequest() + .withUrl(resourceUri.toURL()) + .withMethod(FhirRequest.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"); + /*FIXME delete once refactor is done RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload); Request.Builder request = new Request.Builder().url(resourceUri.toURL()).post(body); - +*/ + FhirRequest request = new FhirRequest() + .withUrl(resourceUri.toURL()) + .withMethod(FhirRequest.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(FhirRequest request, String resourceFormat, + Iterable headers, String message, int retryCount, long timeout) throws IOException { return new FhirRequestBuilder(request, base).withLogger(fhirLoggingInterceptor).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(FhirRequest request, String resourceFormat, + Iterable headers, String message, int retryCount, long timeout) throws IOException { return new FhirRequestBuilder(request, base).withLogger(fhirLoggingInterceptor).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/FhirRequestBuilder.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/FhirRequestBuilder.java index fa7a663008..014544a159 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 @@ -7,6 +7,7 @@ import javax.annotation.Nonnull; +import okhttp3.*; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.r4b.formats.IParser; import org.hl7.fhir.r4b.formats.JsonParser; @@ -18,15 +19,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.http.FhirRequest; +import org.hl7.fhir.utilities.http.HTTPHeader; 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"; @@ -41,7 +37,7 @@ public class FhirRequestBuilder { private static OkHttpClient okHttpClient; private final Request.Builder httpRequest; private String resourceFormat = null; - private Headers headers = null; + private Iterable headers = null; private String message = null; private int retryCount = 1; /** @@ -60,9 +56,14 @@ public class FhirRequestBuilder { private FhirLoggingInterceptor logger = null; private String source; - public FhirRequestBuilder(Request.Builder httpRequest, String source) { - this.httpRequest = httpRequest; + //TODO this should be the only constructor. There should be no okHttp exposure. + public FhirRequestBuilder(FhirRequest fhirRequest, String source) { this.source = source; + + RequestBody body = RequestBody.create(fhirRequest.getBody()); + this.httpRequest = new Request.Builder() + .url(fhirRequest.getUrl()) + .method(fhirRequest.getMethod().name(), body); } /** @@ -73,7 +74,7 @@ public FhirRequestBuilder(Request.Builder httpRequest, String source) { * @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) { + protected static void formatHeaders(Request.Builder request, String format, Iterable headers) { addDefaultHeaders(request, headers); if (format != null) addResourceFormatHeaders(request, format); @@ -87,8 +88,17 @@ protected static void formatHeaders(Request.Builder request, String format, Head * * @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")) { + protected static void addDefaultHeaders(Request.Builder request, Iterable headers) { + boolean hasUserAgent = false; + if (headers != null) { + for (HTTPHeader header : headers) { + if (header.getName().equalsIgnoreCase("User-Agent")) { + hasUserAgent = true; + break; + } + } + } + if (!hasUserAgent) { request.addHeader("User-Agent", "hapi-fhir-tooling-client"); } } @@ -110,8 +120,8 @@ protected static void addResourceFormatHeaders(Request.Builder request, String f * @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 void addHeaders(Request.Builder request, Iterable headers) { + headers.forEach(header -> request.addHeader(header.getName(), header.getValue())); } /** @@ -200,7 +210,7 @@ public FhirRequestBuilder withResourceFormat(String resourceFormat) { return this; } - public FhirRequestBuilder withHeaders(Headers headers) { + public FhirRequestBuilder withHeaders(Iterable headers) { this.headers = headers; return this; } diff --git a/org.hl7.fhir.r5/pom.xml b/org.hl7.fhir.r5/pom.xml index cff156f645..d81432f0e8 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/utils/client/FHIRToolingClient.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/FHIRToolingClient.java index a1a112b963..ec14c733a2 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,13 @@ package org.hl7.fhir.r5.utils.client; -import okhttp3.Headers; -import okhttp3.internal.http2.Header; +//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 +46,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,17 +94,27 @@ 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 List headers = new ArrayList<>(); + @Getter + @Setter private String username; + @Getter + @Setter private String password; + @Setter + @Getter private String userAgent; - private String acceptLang; + @Setter + private String acceptLanguage; private String contentLang; @@ -124,22 +135,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; } @@ -573,22 +572,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 +596,43 @@ 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(); + //FIXME should be in ManagedWebAccess? + private Iterable generateHeaders(boolean hasBody) { + List headers = new ArrayList<>(); // Add basic auth header if it exists if (basicAuthHeaderExists()) { - builder.add(getAuthorizationHeader().toString()); + headers.add(getAuthorizationHeader()); } // Add any other headers - if(this.headers != null) { - this.headers.forEach(header -> builder.add(header.toString())); - } + headers.addAll(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); + headers.add(new HTTPHeader("Content-Language",contentLang)); } - return builder.build(); + return headers; } public boolean basicAuthHeaderExists() { return (username != null) && (password != null); } - public Header getAuthorizationHeader() { + public HTTPHeader 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 new HTTPHeader("Authorization", "Basic " + base64usernamePassword); } public String getServerVersion() { @@ -671,10 +646,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; } 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..9178cd67d6 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.FhirRequest; +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,80 @@ public class Client { public static final String DEFAULT_CHARSET = "UTF-8"; private static final long DEFAULT_TIMEOUT = 5000; + @Getter 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; - } + @Setter @Getter + private String base; public void setLogger(ToolingClientLogger logger) { this.logger = logger; this.fhirLoggingInterceptor = new FhirLoggingInterceptor(logger); } - public int getRetryCount() { - return retryCount; - } - - public void setRetryCount(int retryCount) { - this.retryCount = retryCount; - } - - public long getTimeout() { - return timeout; - } - - public void setTimeout(long timeout) { - this.timeout = timeout; - } - public ResourceRequest issueOptionsRequest(URI optionsUri, String resourceFormat, String message, long timeout) throws IOException { - this.payload = null; + /*FIXME delete once refactor is done Request.Builder request = new Request.Builder() .method("OPTIONS", null) .url(optionsUri.toURL()); - - return executeFhirRequest(request, resourceFormat, new Headers.Builder().build(), message, retryCount, timeout); + */ + FhirRequest request = new FhirRequest() + .withUrl(optionsUri.toURL()) + .withMethod(FhirRequest.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; + /*FIXME delete once refactor is done Request.Builder request = new Request.Builder() .url(resourceUri.toURL()); + */ + FhirRequest request = new FhirRequest() + .withUrl(resourceUri.toURL()) + .withMethod(FhirRequest.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; + /*FIXME delete once refactor is done RequestBody body = RequestBody.create(payload); Request.Builder request = new Request.Builder() .url(resourceUri.toURL()) .put(body); +*/ + FhirRequest request = new FhirRequest() + .withUrl(resourceUri.toURL()) + .withMethod(FhirRequest.HttpMethod.PUT) + .withBody(payload) + .withContentType(getContentTypeWithDefaultCharset(resourceFormat)); return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout); } @@ -114,37 +103,52 @@ 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); + /*FIXME delete once refactor is done + RequestBody body = RequestBody.create(MediaType.parse(getContentTypeWithDefaultCharset(resourceFormat)), payload); Request.Builder request = new Request.Builder() .url(resourceUri.toURL()) .post(body); +*/ + FhirRequest request = new FhirRequest() + .withUrl(resourceUri.toURL()) + .withMethod(FhirRequest.HttpMethod.POST) + .withBody(payload) + .withContentType(getContentTypeWithDefaultCharset(resourceFormat)); return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout); } public boolean issueDeleteRequest(URI resourceUri) throws IOException { + /*FIXME delete once refactor is done Request.Builder request = new Request.Builder() .url(resourceUri.toURL()) .delete(); - return executeFhirRequest(request, null, new Headers.Builder().build(), null, retryCount, timeout).isSuccessfulRequest(); + */ + FhirRequest request = new FhirRequest() + .withUrl(resourceUri.toURL()) + .withMethod(FhirRequest.HttpMethod.DELETE); + return executeFhirRequest(request, null, Collections.emptyList(), null, retryCount, timeout).isSuccessfulRequest(); } public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat) throws IOException { + /*FIXME delete once refactor is done Request.Builder request = new Request.Builder() .url(resourceUri.toURL()); - - return executeBundleRequest(request, resourceFormat, new Headers.Builder().build(), null, retryCount, timeout); + */ + FhirRequest request = new FhirRequest() + .withUrl(resourceUri.toURL()) + .withMethod(FhirRequest.HttpMethod.GET); + return executeBundleRequest(request, resourceFormat, Collections.emptyList(), null, retryCount, timeout); } public Bundle issuePostFeedRequest(URI resourceUri, @@ -154,48 +158,64 @@ 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); + /*FIXME delete once refactor is done + RequestBody body = RequestBody.create(MediaType.parse(getContentTypeWithDefaultCharset(resourceFormat)), payload); Request.Builder request = new Request.Builder() .url(resourceUri.toURL()) .post(body); - - return executeBundleRequest(request, resourceFormat, new Headers.Builder().build(), null, retryCount, timeout); +*/ + FhirRequest request = new FhirRequest() + .withUrl(resourceUri.toURL()) + .withMethod(FhirRequest.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"); + /*FIXME delete once refactor is done RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload); Request.Builder request = new Request.Builder() .url(resourceUri.toURL()) .post(body); - + */ + FhirRequest request = new FhirRequest() + .withUrl(resourceUri.toURL()) + .withMethod(FhirRequest.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(FhirRequest request, + String resourceFormat, + Iterable headers, + String message, + int retryCount, + long timeout) throws IOException { return new FhirRequestBuilder(request, base) .withLogger(fhirLoggingInterceptor) .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, + public ResourceRequest executeFhirRequest(FhirRequest request, String resourceFormat, - Headers headers, + Iterable headers, String message, int retryCount, long timeout) throws IOException { @@ -204,7 +224,7 @@ public ResourceRequest executeFhirRequest(Request.Builde .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..b6cde0747e 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 @@ -2,6 +2,7 @@ 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,17 +16,18 @@ */ 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; } @@ -35,10 +37,10 @@ public ArrayList
headers() { * @param header {@link Header} 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; @@ -50,7 +52,7 @@ public ClientHeaders addHeader(Header header) throws FHIRException { * @param headerList {@link List} of {@link Header} 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; } @@ -60,10 +62,10 @@ public ClientHeaders addHeaders(List
headerList) throws FHIRException { * @param header {@link Header} 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; } @@ -73,7 +75,7 @@ public ClientHeaders removeHeader(Header header) throws FHIRException { * @param headerList {@link List} of {@link Header} 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; } @@ -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/FhirRequestBuilder.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/FhirRequestBuilder.java index 0ea615c8d4..5711083da2 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 @@ -22,6 +22,7 @@ import org.hl7.fhir.utilities.MimeType; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.http.FhirRequest; +import org.hl7.fhir.utilities.http.HTTPHeader; import org.hl7.fhir.utilities.http.ManagedWebAccess; import org.hl7.fhir.utilities.xhtml.XhtmlUtils; @@ -39,7 +40,7 @@ public class FhirRequestBuilder { private static OkHttpClient okHttpClient; private final Request.Builder httpRequest; private String resourceFormat = null; - private Headers headers = null; + private Iterable headers = null; private String message = null; private int retryCount = 1; /** @@ -61,17 +62,17 @@ public class FhirRequestBuilder { public FhirRequestBuilder(FhirRequest fhirRequest, String source) { this.source = source; - RequestBody body = RequestBody.create(fhirRequest.getBody()); + Request.Builder baseRequestBuilder = new Request.Builder(); + assert fhirRequest.getUrl() != null; + baseRequestBuilder.url(fhirRequest.getUrl()); + + + RequestBody body = fhirRequest.getBody() == null ? null : RequestBody.create(fhirRequest.getBody()); this.httpRequest = new Request.Builder() .url(fhirRequest.getUrl()) .method(fhirRequest.getMethod().name(), body); } - public FhirRequestBuilder(Request.Builder 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} @@ -80,7 +81,7 @@ public FhirRequestBuilder(Request.Builder httpRequest, String source) { * @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) { + protected static void formatHeaders(Request.Builder request, String format, Iterable headers) { addDefaultHeaders(request, headers); if (format != null) addResourceFormatHeaders(request, format); if (headers != null) addHeaders(request, headers); @@ -92,8 +93,17 @@ protected static void formatHeaders(Request.Builder request, String format, Head * * @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")) { + protected static void addDefaultHeaders(Request.Builder request, Iterable headers) { + boolean hasUserAgent = false; + if (headers != null) { + for (HTTPHeader header : headers) { + if (header.getName().equalsIgnoreCase("User-Agent")) { + hasUserAgent = true; + break; + } + } + } + if (!hasUserAgent) { request.addHeader("User-Agent", "hapi-fhir-tooling-client"); } } @@ -116,8 +126,8 @@ protected static void addResourceFormatHeaders(Request.Builder request, String f * @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 void addHeaders(Request.Builder request, Iterable headers) { + headers.forEach(header -> request.addHeader(header.getName(), header.getValue())); } /** @@ -166,7 +176,7 @@ protected static String getLocationHeader(Headers headers) { * * @return {@link OkHttpClient} instance */ - //TODO replace this. + //FIXME delete this whole method. protected OkHttpClient getHttpClient() { if (okHttpClient == null) { @@ -205,7 +215,7 @@ public FhirRequestBuilder withResourceFormat(String resourceFormat) { return this; } - public FhirRequestBuilder withHeaders(Headers headers) { + public FhirRequestBuilder withHeaders(Iterable headers) { this.headers = headers; return this; } @@ -244,7 +254,7 @@ public ResourceRequest execute() throws IOException { public Bundle executeAsBatch() throws IOException { formatHeaders(httpRequest, resourceFormat, null); - Response response = getHttpClient().newCall(httpRequest.build()).execute(); + Response response = ManagedWebAccess.httpCall(httpRequest); return unmarshalFeed(response, resourceFormat); } 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..bb7a6aea47 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.FhirRequest; +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(FhirRequest.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..3da2c3df2f 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,6 +4,7 @@ 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; @@ -15,9 +16,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() { @@ -43,7 +44,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 +53,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 +65,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 +80,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 +90,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..89c8c67268 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 @@ -4,6 +4,7 @@ import java.util.Map; import org.hl7.fhir.r5.model.OperationOutcome; +import org.hl7.fhir.utilities.http.HTTPHeader; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -67,10 +68,10 @@ void addHeaders() { String headerName2 = "headerName2"; String headerValue2 = "headerValue2"; - Headers headers = new Headers.Builder() - .add(headerName1, headerValue1) - .add(headerName2, headerValue2) - .build(); + List headers = List.of( + new HTTPHeader(headerName1, headerValue1), + new HTTPHeader(headerName2, headerValue2) + ); Request.Builder request = new Request.Builder().url("http://www.google.com"); FhirRequestBuilder.addHeaders(request, headers); diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/FhirRequest.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/FhirRequest.java index ad1cd692c6..e66873beab 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/FhirRequest.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/FhirRequest.java @@ -4,19 +4,31 @@ import lombok.Getter; import lombok.With; +import javax.annotation.Nullable; +import java.net.URL; + @AllArgsConstructor public class FhirRequest { + public FhirRequest() { + url = null; + method = HttpMethod.GET; + body = null; + } + public enum HttpMethod { GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH } - @With @Getter - private final String url; + @With @Getter @Nullable + private final URL url; @With @Getter private HttpMethod method; - @With @Getter + @With @Getter @Nullable private byte[] body; + + @With @Getter @Nullable + private String contentType; } 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..685a8a660a --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/HTTPHeader.java @@ -0,0 +1,15 @@ +package org.hl7.fhir.utilities.http; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.With; + +@AllArgsConstructor +@EqualsAndHashCode +public class HTTPHeader { + @With @Getter + private final String name; + @With @Getter + private final String value; +} diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java index abf39bc1ff..c0fb89a9b0 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java @@ -4,6 +4,7 @@ import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; +import org.hl7.fhir.utilities.http.okhttpimpl.RetryInterceptor; import org.hl7.fhir.utilities.settings.ServerDetailsPOJO; import java.io.IOException; @@ -66,8 +67,9 @@ private OkHttpClient getOkHttpClient() { if (okHttpClient == null) { okHttpClient = new OkHttpClient(); } - - return okHttpClient; + OkHttpClient.Builder builder = okHttpClient.newBuilder(); + builder.addInterceptor(new RetryInterceptor(retries)); + return builder.build(); } } 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); + } +} From fdbf0da576d2dc72fe5adb2a82e84ed1fa61f5bf Mon Sep 17 00:00:00 2001 From: dotasek Date: Wed, 23 Oct 2024 11:55:27 -0400 Subject: [PATCH 04/42] WIP move logging, retry, timeout and proxy --- .../fhir/r5/utils/client/network/Client.java | 13 ++-- .../client/network/FhirRequestBuilder.java | 30 ++++---- .../hl7/fhir/utilities/http/HTTPHeader.java | 10 ++- .../http/ManagedFhirWebAccessBuilder.java | 30 ++++++-- .../http/okhttpimpl/LoggingInterceptor.java | 69 +++++++++++++++++++ .../http/okhttpimpl/ProxyAuthenticator.java | 24 +++++++ .../hl7/fhir/validation/special/TxTester.java | 5 +- 7 files changed, 146 insertions(+), 35 deletions(-) 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 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 9178cd67d6..7c4e3e3cdb 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 @@ -20,9 +20,9 @@ public class Client { public static final String DEFAULT_CHARSET = "UTF-8"; private static final long DEFAULT_TIMEOUT = 5000; - @Getter + @Getter @Setter private ToolingClientLogger logger; - private FhirLoggingInterceptor fhirLoggingInterceptor; + @Setter @Getter private int retryCount; @Setter @Getter @@ -31,11 +31,6 @@ public class Client { @Setter @Getter private String base; - public void setLogger(ToolingClientLogger logger) { - this.logger = logger; - this.fhirLoggingInterceptor = new FhirLoggingInterceptor(logger); - } - public ResourceRequest issueOptionsRequest(URI optionsUri, String resourceFormat, String message, @@ -204,7 +199,7 @@ public Bundle executeBundleRequest(FhirRequest request, int retryCount, long timeout) throws IOException { return new FhirRequestBuilder(request, base) - .withLogger(fhirLoggingInterceptor) + .withLogger(logger) .withResourceFormat(resourceFormat) .withRetryCount(retryCount) .withMessage(message) @@ -220,7 +215,7 @@ public ResourceRequest executeFhirRequest(FhirRequest re int retryCount, long timeout) throws IOException { return new FhirRequestBuilder(request, base) - .withLogger(fhirLoggingInterceptor) + .withLogger(logger) .withResourceFormat(resourceFormat) .withRetryCount(retryCount) .withMessage(message) 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 5711083da2..82584c9e6b 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 @@ -20,18 +20,14 @@ 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.ToolingClientLogger; import org.hl7.fhir.utilities.Utilities; -import org.hl7.fhir.utilities.http.FhirRequest; -import org.hl7.fhir.utilities.http.HTTPHeader; -import org.hl7.fhir.utilities.http.ManagedWebAccess; +import org.hl7.fhir.utilities.http.*; import org.hl7.fhir.utilities.xhtml.XhtmlUtils; 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 LOCATION_HEADER = "location"; protected static final String CONTENT_LOCATION_HEADER = "content-location"; protected static final String DEFAULT_CHARSET = "UTF-8"; /** @@ -55,7 +51,8 @@ public class FhirRequestBuilder { /** * {@link FhirLoggingInterceptor} for log output. */ - private FhirLoggingInterceptor logger = null; + private ToolingClientLogger logger = null; + private String source; //TODO this should be the only constructor. There should be no okHttp exposure. @@ -66,7 +63,6 @@ public FhirRequestBuilder(FhirRequest fhirRequest, String source) { assert fhirRequest.getUrl() != null; baseRequestBuilder.url(fhirRequest.getUrl()); - RequestBody body = fhirRequest.getBody() == null ? null : RequestBody.create(fhirRequest.getBody()); this.httpRequest = new Request.Builder() .url(fhirRequest.getUrl()) @@ -163,6 +159,9 @@ protected static String getLocationHeader(Headers headers) { } } + protected ManagedFhirWebAccessBuilder getManagedWebAccessBuilder() { + return new ManagedFhirWebAccessBuilder("hapi-fhir-tooling-client", null).withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); + } /** * 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 @@ -176,7 +175,7 @@ protected static String getLocationHeader(Headers headers) { * * @return {@link OkHttpClient} instance */ - //FIXME delete this whole method. + /*FIXME delete after refactor protected OkHttpClient getHttpClient() { if (okHttpClient == null) { @@ -194,7 +193,8 @@ protected OkHttpClient getHttpClient() { .proxyAuthenticator(proxyAuthenticator) .build(); } - +*/ + /*FIXME delete after refactor @Nonnull private static Authenticator getAuthenticator() { return (route, response) -> { @@ -209,7 +209,7 @@ private static Authenticator getAuthenticator() { return response.request().newBuilder().build(); }; } - +*/ public FhirRequestBuilder withResourceFormat(String resourceFormat) { this.resourceFormat = resourceFormat; return this; @@ -230,7 +230,7 @@ public FhirRequestBuilder withRetryCount(int retryCount) { return this; } - public FhirRequestBuilder withLogger(FhirLoggingInterceptor logger) { + public FhirRequestBuilder withLogger(ToolingClientLogger logger) { this.logger = logger; return this; } @@ -247,14 +247,14 @@ protected Request buildRequest() { public ResourceRequest execute() throws IOException { formatHeaders(httpRequest, resourceFormat, headers); - Response response = ManagedWebAccess.httpCall(httpRequest);//getHttpClient().newCall(httpRequest.build()).execute(); + Response response = getManagedWebAccessBuilder().httpCall(httpRequest);//getHttpClient().newCall(httpRequest.build()).execute(); T resource = unmarshalReference(response, resourceFormat, null); return new ResourceRequest(resource, response.code(), getLocationHeader(response.headers())); } public Bundle executeAsBatch() throws IOException { formatHeaders(httpRequest, resourceFormat, null); - Response response = ManagedWebAccess.httpCall(httpRequest); + Response response = getManagedWebAccessBuilder().httpCall(httpRequest); return unmarshalFeed(response, resourceFormat); } 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 index 685a8a660a..b45e9ff7fd 100644 --- 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 @@ -5,11 +5,17 @@ import lombok.Getter; import lombok.With; -@AllArgsConstructor +import javax.annotation.Nonnull; + @EqualsAndHashCode public class HTTPHeader { - @With @Getter + @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/ManagedFhirWebAccessBuilder.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java index c0fb89a9b0..85206ce261 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java @@ -1,15 +1,18 @@ package org.hl7.fhir.utilities.http; import lombok.With; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; +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 javax.annotation.Nonnull; import java.io.IOException; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; public class ManagedFhirWebAccessBuilder extends ManagedWebAccessBuilderBase{ @@ -19,10 +22,14 @@ public class ManagedFhirWebAccessBuilder extends ManagedWebAccessBuilderBase serverAuthDetails) { super(userAgent, serverAuthDetails); } @@ -61,15 +75,17 @@ public Response httpCall(Request.Builder httpRequest) throws IOException { } } - - 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)); - return builder.build(); + 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/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.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 c0db3b37e5..0b3dc5663f 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 @@ -39,6 +39,7 @@ import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.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; @@ -206,12 +207,12 @@ private boolean runTest(JsonObject test, ITerminologyClient tx, List s 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); } } From b33de12e499fc461f18d0edde34755a96711da64 Mon Sep 17 00:00:00 2001 From: dotasek Date: Thu, 24 Oct 2024 13:57:51 -0400 Subject: [PATCH 05/42] WIP move accept to parameter --- .../TerminologyCacheManager.java | 2 +- .../TerminologyCacheManager.java | 2 +- .../fhir/utilities/http/ManagedWebAccess.java | 8 ++-- .../http/ManagedWebAccessBuilder.java | 43 +++++++++---------- .../http/ManagedWebAccessBuilderBase.java | 8 +--- .../utilities/http/ManagedWebAccessUtils.java | 17 ++++++++ .../hl7/fhir/utilities/npm/PackageClient.java | 4 +- 7 files changed, 47 insertions(+), 37 deletions(-) create mode 100644 org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessUtils.java 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..c717f43479 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 @@ -158,7 +158,7 @@ public void commit(String token) throws IOException { HTTPResult res = ManagedWebAccess.builder() .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.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..b58751dbe7 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 @@ -158,7 +158,7 @@ public void commit(String token) throws IOException { System.out.println("Sending tx-cache to "+url+" ("+Utilities.describeSize(bs.toByteArray().length)+")"); HTTPResult res = ManagedWebAccess.builder() .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.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 2e2023e3f1..981ec29434 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 @@ -126,19 +126,19 @@ public static HTTPResult get(String url) throws IOException { } public static HTTPResult get(String url, String accept) throws IOException { - return builder().withAccept(accept).get(url); + return builder().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 builder().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 builder().put(url, content, contentType, accept); } - public static Response httpCall(Request.Builder httpRequest) throws IOException { + public static Response httpCall(FhirRequest httpRequest) throws IOException { return fhirBuilder().httpCall(httpRequest); } 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/ManagedWebAccessBuilder.java index a695e0007b..b25a91ff87 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/ManagedWebAccessBuilder.java @@ -20,10 +20,6 @@ public class ManagedWebAccessBuilder extends ManagedWebAccessBuilderBase serverAuthDetails) { super(userAgent, serverAuthDetails); } - - public ManagedWebAccessBuilder withAccept(String accept) { - return super.withAccept(accept); - } private Map newHeaders() { Map headers = new HashMap(); @@ -51,7 +47,8 @@ private SimpleHTTPClient setupClient(String url) throws IOException { if (getUserAgent() != null) { client.addHeader("User-Agent", getUserAgent()); } - if (getAuthenticationMode() != null && getAuthenticationMode() != HTTPAuthenticationMode.NONE) { + if (getAuthenticationMode() != null) { + if (getAuthenticationMode() != HTTPAuthenticationMode.NONE) { client.setAuthenticationMode(getAuthenticationMode()); switch (getAuthenticationMode()) { case BASIC : @@ -65,8 +62,9 @@ private SimpleHTTPClient setupClient(String url) throws IOException { client.setApiKey(getToken()); break; } + } } else { - ServerDetailsPOJO settings = getServer(url); + ServerDetailsPOJO settings = ManagedWebAccessUtils.getServer(url, getServerAuthDetails()); if (settings != null) { switch (settings.getAuthenticationType()) { case "basic" : @@ -91,24 +89,17 @@ private SimpleHTTPClient setupClient(String url) throws IOException { return client; } - private ServerDetailsPOJO getServer(String url) { - if (getServerAuthDetails() != null) { - for (ServerDetailsPOJO t : getServerAuthDetails()) { - 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, getAccept()); + return client.get(url, accept); case MANAGED: - return ManagedWebAccess.getAccessor().get(url, getAccept(), newHeaders()); + return ManagedWebAccess.getAccessor().get(url, accept, newHeaders()); case PROHIBITED: throw new IOException("Access to the internet is not allowed by local security policy"); default: @@ -117,12 +108,16 @@ 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, getAccept()); + return client.post(url, contentType, content, accept); case MANAGED: - return ManagedWebAccess.getAccessor().post(url, content, contentType, getAccept(), newHeaders()); + 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: @@ -131,12 +126,16 @@ 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, getAccept()); + return client.put(url, contentType, content, accept); case MANAGED: - return ManagedWebAccess.getAccessor().put(url, content, contentType, getAccept(), newHeaders()); + 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: diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessBuilderBase.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessBuilderBase.java index 54f5d2a2d9..b3efd26027 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessBuilderBase.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessBuilderBase.java @@ -18,8 +18,7 @@ public abstract class ManagedWebAccessBuilderBase serverAuthDetails; @Getter @@ -35,11 +34,6 @@ final B self() { return (B) this; } - public B withAccept(String accept) { - this.accept = accept; - return self(); - } - public B withHeader(String name, String value) { headers.put(name, value); return self(); 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/npm/PackageClient.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/PackageClient.java index 4bf8273560..111b6f2fd0 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 @@ -176,13 +176,13 @@ public Date getNewPackages(Date lastCalled, List updates) { } private InputStream fetchUrl(String source, String accept) throws IOException { - ManagedWebAccessBuilder client = ManagedWebAccess.builder().withAccept(accept); + ManagedWebAccessBuilder client = ManagedWebAccess.builder(); if (server.getAuthenticationMode() == HTTPAuthenticationMode.TOKEN) { client.withToken(server.getToken()); } else if (server.getAuthenticationMode() == HTTPAuthenticationMode.BASIC) { client.withBasicAuth(server.getUsername(), server.getPassword()); } - HTTPResult res = client.get(source); + HTTPResult res = client.get(source, accept); res.checkThrowException(); return new ByteArrayInputStream(res.getContent()); } From 0b45b0b882019560b0afaf370bf7911bf77ecd61 Mon Sep 17 00:00:00 2001 From: dotasek Date: Fri, 25 Oct 2024 13:55:33 -0400 Subject: [PATCH 06/42] WIP some HTTP Header refactoring, rename FhirRequest to HTTPRequest --- .../fhir/convertors/misc/ICD11Generator.java | 2 +- .../fhir/r5/utils/client/network/Client.java | 48 +++--- .../network/FhirLoggingInterceptor.java | 70 -------- .../client/network/FhirRequestBuilder.java | 159 ++++++++---------- .../client/network/RetryInterceptor.java | 63 ------- .../utils/client/FHIRToolingClientTest.java | 4 +- .../network/FhirRequestBuilderTest.java | 69 ++++---- .../hl7/fhir/utilities/http/FhirRequest.java | 34 ---- .../fhir/utilities/http/HTTPHeaderUtil.java | 45 +++++ .../hl7/fhir/utilities/http/HTTPRequest.java | 54 ++++++ .../hl7/fhir/utilities/http/HTTPResult.java | 49 +++--- .../http/ManagedFhirWebAccessBuilder.java | 82 ++++++++- .../fhir/utilities/http/ManagedWebAccess.java | 15 +- .../http/ManagedWebAccessBuilder.java | 5 + .../http/ManagedWebAccessBuilderBase.java | 2 +- .../fhir/utilities/http/SimpleHTTPClient.java | 25 +-- .../ManagedFhirWebAccessBuilderTests.java | 42 +++++ 17 files changed, 379 insertions(+), 389 deletions(-) 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 delete mode 100644 org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/FhirRequest.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/test/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilderTests.java 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..cb5d85951c 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 @@ -395,7 +395,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.builder().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.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 7c4e3e3cdb..e4d2ab5f67 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 @@ -7,7 +7,7 @@ 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.FhirRequest; +import org.hl7.fhir.utilities.http.HTTPRequest; import org.hl7.fhir.utilities.http.HTTPHeader; import java.io.IOException; @@ -40,9 +40,9 @@ public ResourceRequest issueOptionsRequest(URI optionsUr .method("OPTIONS", null) .url(optionsUri.toURL()); */ - FhirRequest request = new FhirRequest() + HTTPRequest request = new HTTPRequest() .withUrl(optionsUri.toURL()) - .withMethod(FhirRequest.HttpMethod.OPTIONS); + .withMethod(HTTPRequest.HttpMethod.OPTIONS); return executeFhirRequest(request, resourceFormat, Collections.emptyList(), message, retryCount, timeout); } @@ -56,9 +56,9 @@ public ResourceRequest issueGetResourceRequest(URI resou .url(resourceUri.toURL()); */ - FhirRequest request = new FhirRequest() + HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) - .withMethod(FhirRequest.HttpMethod.GET); + .withMethod(HTTPRequest.HttpMethod.GET); return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout); } @@ -84,9 +84,9 @@ public ResourceRequest issuePutRequest(URI resourceUri, .url(resourceUri.toURL()) .put(body); */ - FhirRequest request = new FhirRequest() + HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) - .withMethod(FhirRequest.HttpMethod.PUT) + .withMethod(HTTPRequest.HttpMethod.PUT) .withBody(payload) .withContentType(getContentTypeWithDefaultCharset(resourceFormat)); @@ -114,9 +114,9 @@ public ResourceRequest issuePostRequest(URI resourceUri, .url(resourceUri.toURL()) .post(body); */ - FhirRequest request = new FhirRequest() + HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) - .withMethod(FhirRequest.HttpMethod.POST) + .withMethod(HTTPRequest.HttpMethod.POST) .withBody(payload) .withContentType(getContentTypeWithDefaultCharset(resourceFormat)); @@ -129,9 +129,9 @@ public boolean issueDeleteRequest(URI resourceUri) throws IOException { .url(resourceUri.toURL()) .delete(); */ - FhirRequest request = new FhirRequest() + HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) - .withMethod(FhirRequest.HttpMethod.DELETE); + .withMethod(HTTPRequest.HttpMethod.DELETE); return executeFhirRequest(request, null, Collections.emptyList(), null, retryCount, timeout).isSuccessfulRequest(); } @@ -140,9 +140,9 @@ public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat) throws Request.Builder request = new Request.Builder() .url(resourceUri.toURL()); */ - FhirRequest request = new FhirRequest() + HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) - .withMethod(FhirRequest.HttpMethod.GET); + .withMethod(HTTPRequest.HttpMethod.GET); return executeBundleRequest(request, resourceFormat, Collections.emptyList(), null, retryCount, timeout); } @@ -159,9 +159,9 @@ public Bundle issuePostFeedRequest(URI resourceUri, .url(resourceUri.toURL()) .post(body); */ - FhirRequest request = new FhirRequest() + HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) - .withMethod(FhirRequest.HttpMethod.POST) + .withMethod(HTTPRequest.HttpMethod.POST) .withBody(payload) .withContentType(getContentTypeWithDefaultCharset(resourceFormat)); return executeBundleRequest(request, resourceFormat, Collections.emptyList(), null, retryCount, timeout); @@ -180,9 +180,9 @@ public Bundle postBatchRequest(URI resourceUri, .url(resourceUri.toURL()) .post(body); */ - FhirRequest request = new FhirRequest() + HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) - .withMethod(FhirRequest.HttpMethod.POST) + .withMethod(HTTPRequest.HttpMethod.POST) .withBody(payload) .withContentType(getContentTypeWithDefaultCharset(resourceFormat)); return executeBundleRequest(request, resourceFormat, headers, message, retryCount, timeout); @@ -192,7 +192,7 @@ private static String getContentTypeWithDefaultCharset(String resourceFormat) { return resourceFormat + ";charset=" + DEFAULT_CHARSET; } - public Bundle executeBundleRequest(FhirRequest request, + public Bundle executeBundleRequest(HTTPRequest request, String resourceFormat, Iterable headers, String message, @@ -208,12 +208,12 @@ public Bundle executeBundleRequest(FhirRequest request, .executeAsBatch(); } - public ResourceRequest executeFhirRequest(FhirRequest request, - String resourceFormat, - Iterable 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) 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 82584c9e6b..b39519e6b2 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,12 +1,10 @@ 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 okhttp3.*; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.r5.formats.IParser; @@ -21,20 +19,22 @@ import org.hl7.fhir.r5.utils.client.ResourceFormat; 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.xhtml.XhtmlUtils; 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"; + + protected static final String LOCATION_HEADER = "location"; + protected static final String CONTENT_LOCATION_HEADER = "content-location"; + /** * The singleton instance of the HttpClient, used for all requests. */ - private static OkHttpClient okHttpClient; - private final Request.Builder httpRequest; + // private static OkHttpClient okHttpClient; + //private final Request.Builder httpRequest; + private final HTTPRequest httpRequest; private String resourceFormat = null; private Iterable headers = null; private String message = null; @@ -49,71 +49,53 @@ public class FhirRequestBuilder { private TimeUnit timeoutUnit = TimeUnit.MILLISECONDS; /** - * {@link FhirLoggingInterceptor} for log output. + * {@link ToolingClientLogger} for log output. */ private ToolingClientLogger logger = null; private String source; //TODO this should be the only constructor. There should be no okHttp exposure. - public FhirRequestBuilder(FhirRequest fhirRequest, String source) { + public FhirRequestBuilder(HTTPRequest httpRequest, String source) { this.source = source; - - Request.Builder baseRequestBuilder = new Request.Builder(); - assert fhirRequest.getUrl() != null; - baseRequestBuilder.url(fhirRequest.getUrl()); - - RequestBody body = fhirRequest.getBody() == null ? null : RequestBody.create(fhirRequest.getBody()); - this.httpRequest = new Request.Builder() - .url(fhirRequest.getUrl()) - .method(fhirRequest.getMethod().name(), body); + this.httpRequest = httpRequest; } /** * Adds necessary default headers, formatting headers, and any passed in {@link Headers} to the passed in - * {@link okhttp3.Request.Builder} + * {@link Request.Builder} * - * @param request {@link okhttp3.Request.Builder} to add headers to. + * @param request {@link 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, Iterable 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); + + if (format != null) getResourceFormatHeaders(request, format).forEach(allHeaders::add); + if (headers != null) headers.forEach(allHeaders::add); + return request.withHeaders(allHeaders); } /** - * Adds necessary headers for all REST requests. - *
  • User-Agent : hapi-fhir-tooling-client
  • - * - * @param request {@link Request.Builder} to add default headers to. - */ - protected static void addDefaultHeaders(Request.Builder request, Iterable headers) { - boolean hasUserAgent = false; - if (headers != null) { - for (HTTPHeader header : headers) { - if (header.getName().equalsIgnoreCase("User-Agent")) { - hasUserAgent = true; - break; - } - } - } - if (!hasUserAgent) { - 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. + * @param httpRequest {@link Request.Builder} to add default headers to. */ - 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; } /** @@ -127,10 +109,10 @@ protected static void addHeaders(Request.Builder request, Iterable h } /** - * 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. @@ -141,23 +123,7 @@ 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; - } - } + protected ManagedFhirWebAccessBuilder getManagedWebAccessBuilder() { return new ManagedFhirWebAccessBuilder("hapi-fhir-tooling-client", null).withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); @@ -241,20 +207,18 @@ 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 = getManagedWebAccessBuilder().httpCall(httpRequest);//getHttpClient().newCall(httpRequest.build()).execute(); + HTTPRequest requestWithHeaders = formatHeaders(httpRequest, resourceFormat, headers); + HTTPResult response = getManagedWebAccessBuilder().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 = getManagedWebAccessBuilder().httpCall(httpRequest); + HTTPRequest requestWithHeaders = formatHeaders(httpRequest, resourceFormat, null); + HTTPResult response = getManagedWebAccessBuilder().httpCall(requestWithHeaders); return unmarshalFeed(response, resourceFormat); } @@ -262,12 +226,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; } @@ -276,9 +240,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 { @@ -287,10 +251,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")) { @@ -310,10 +274,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); } @@ -348,7 +312,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"); } @@ -374,4 +338,17 @@ protected IParser getParser(String format) { throw new EFhirClientException(0, "Invalid format: " + format); } } + + /** + * Extracts the 'location' header from. If no value for 'location' exists, the + * value for 'content-location' is returned. If neither header exists, we return null. + */ + 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 bb7a6aea47..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 @@ -23,7 +23,7 @@ import org.hl7.fhir.r5.utils.client.network.Client; import org.hl7.fhir.r5.utils.client.network.ResourceRequest; -import org.hl7.fhir.utilities.http.FhirRequest; +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.BeforeEach; @@ -74,7 +74,7 @@ void setUp() throws IOException, URISyntaxException { ArgumentMatchers.any(), Mockito.contains("validate"), Mockito.anyLong())) .thenReturn(new ResourceRequest<>(new OperationOutcome(), 200, "location")); //BUNDLE REQ - Mockito.when(mockClient.executeBundleRequest(Mockito.any(FhirRequest.class), Mockito.anyString(), + 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"); 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 89c8c67268..c03c16bc17 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,40 +1,32 @@ 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() { + //FIXME tested here. Should get list of HTTPHeader. 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,12 +37,13 @@ void addResourceFormatHeadersGET() { @Test @DisplayName("Test resource format headers are added correctly (POST).") void addResourceFormatHeadersPOST() { + //FIXME tested here. Should get list of HTTPHeader. String testFormat = "yaml"; - Request.Builder request = new Request.Builder().url("http://www.google.com"); - request.setMethod$okhttp("POST"); - FhirRequestBuilder.addResourceFormatHeaders(request, testFormat); + HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com").withMethod(HTTPRequest.HttpMethod.POST); - Map> 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 + "."); @@ -63,6 +56,7 @@ void addResourceFormatHeadersPOST() { @Test @DisplayName("Test a list of provided headers are added correctly.") void addHeaders() { + //FIXME tested here. Should get list of HTTPHeader. String headerName1 = "headerName1"; String headerValue1 = "headerValue1"; String headerName2 = "headerName2"; @@ -74,7 +68,7 @@ void addHeaders() { ); Request.Builder request = new Request.Builder().url("http://www.google.com"); - FhirRequestBuilder.addHeaders(request, headers); + headers.forEach(header -> request.addHeader(header.getName(), header.getValue())); Map> headersMap = request.build().headers().toMultimap(); Assertions.assertNotNull(headersMap.get(headerName1), headerName1 + " header null."); @@ -121,19 +115,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)); } @@ -142,18 +139,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/FhirRequest.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/FhirRequest.java deleted file mode 100644 index e66873beab..0000000000 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/FhirRequest.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.hl7.fhir.utilities.http; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.With; - -import javax.annotation.Nullable; -import java.net.URL; - -@AllArgsConstructor -public class FhirRequest { - - public FhirRequest() { - url = null; - method = HttpMethod.GET; - body = null; - } - - public enum HttpMethod { - GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH - } - - @With @Getter @Nullable - private final URL url; - - @With @Getter - private HttpMethod method; - - @With @Getter @Nullable - private byte[] body; - - @With @Getter @Nullable - private String contentType; -} 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/ManagedFhirWebAccessBuilder.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java index 85206ce261..7179155509 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java @@ -1,6 +1,5 @@ package org.hl7.fhir.utilities.http; -import lombok.With; import okhttp3.*; import org.hl7.fhir.utilities.ToolingClientLogger; import org.hl7.fhir.utilities.http.okhttpimpl.LoggingInterceptor; @@ -8,8 +7,8 @@ import org.hl7.fhir.utilities.http.okhttpimpl.RetryInterceptor; import org.hl7.fhir.utilities.settings.ServerDetailsPOJO; -import javax.annotation.Nonnull; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -49,25 +48,88 @@ public ManagedFhirWebAccessBuilder(String userAgent, List ser super(userAgent, serverAuthDetails); } - private void setHeaders(Request.Builder httpRequest) { + protected HTTPRequest httpRequestWithDefaultHeaders(HTTPRequest request) { + List headers = new ArrayList<>(); + if (HTTPHeaderUtil.getSingleHeader(request.getHeaders(), HTTPHeaderUtil.USER_AGENT) == 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()) { - httpRequest.header(entry.getKey(), entry.getValue()); + 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 " + new String(getToken()); + headers.add(new HTTPHeader("Authorization", tokenCredential)); + break; + case APIKEY: + String apiKeyCredential = " " + new String(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 = new String(settings.getToken()); + headers.add(new HTTPHeader("Api-Key", apiKeyCredential)); + break; + } + } } + return httpRequest.withHeaders(headers); } - public Response httpCall(Request.Builder httpRequest) throws IOException { + 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); + OkHttpClient okHttpClient = getOkHttpClient(); //TODO check and throw based on httpRequest: // if (!ManagedWebAccess.inAllowedPaths(url)) { // throw new IOException("The pathname '"+url+"' cannot be accessed by policy"); // } //TODO add auth headers to httpRequest - return okHttpClient.newCall(httpRequest.build()).execute(); + Response response = okHttpClient.newCall(requestBuilder.build()).execute(); + return getHTTPResult(response); case MANAGED: - setHeaders(httpRequest); - return ManagedWebAccess.getFhirWebAccessor().httpCall(httpRequest); + 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: @@ -75,6 +137,10 @@ public Response httpCall(Request.Builder httpRequest) throws IOException { } } + 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().bytes() : null); + } + private OkHttpClient getOkHttpClient() { if (okHttpClient == null) { okHttpClient = new OkHttpClient(); 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 981ec29434..b9d818f5c1 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,26 +31,18 @@ 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 lombok.Getter; -import okhttp3.Request; import okhttp3.Response; -import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.settings.ServerDetailsPOJO; /** * see security.md - manages access to the local file system 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 @@ -68,7 +60,7 @@ public interface IWebAccessor { } public interface IFhirWebAccessor { - Response httpCall(Request.Builder httpRequest); + HTTPResult httpCall(HTTPRequest httpRequest); } public enum WebAccessPolicy { @@ -132,13 +124,12 @@ public static HTTPResult get(String url, String accept) throws IOException { public static HTTPResult post(String url, byte[] content, String contentType, String accept) throws IOException { return builder().post(url, content, contentType, accept); } - public static HTTPResult put(String url, byte[] content, String contentType, String accept) throws IOException { return builder().put(url, content, contentType, accept); } - public static Response httpCall(FhirRequest httpRequest) throws IOException { + public static HTTPResult httpCall(HTTPRequest httpRequest) throws IOException { return fhirBuilder().httpCall(httpRequest); } 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/ManagedWebAccessBuilder.java index b25a91ff87..5a7f376e1d 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/ManagedWebAccessBuilder.java @@ -44,6 +44,11 @@ private SimpleHTTPClient setupClient(String url) throws IOException { throw new IOException("The pathname '"+url+"' cannot be accessed by policy"); } SimpleHTTPClient client = new SimpleHTTPClient(); + + for (Map.Entry entry : this.getHeaders().entrySet()) { + client.addHeader(entry.getKey(), entry.getValue()); + } + if (getUserAgent() != null) { client.addHeader("User-Agent", getUserAgent()); } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessBuilderBase.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessBuilderBase.java index b3efd26027..f21f41bea5 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessBuilderBase.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessBuilderBase.java @@ -22,7 +22,7 @@ public abstract class ManagedWebAccessBuilderBase serverAuthDetails; @Getter - private Map headers = new HashMap(); + private final Map headers = new HashMap<>(); public ManagedWebAccessBuilderBase(String userAgent, List serverAuthDetails) { this.userAgent = userAgent; 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..ed6fa849c5 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); diff --git a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilderTests.java b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilderTests.java new file mode 100644 index 0000000000..072364c936 --- /dev/null +++ b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilderTests.java @@ -0,0 +1,42 @@ +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 ManagedFhirWebAccessBuilderTests { + final String expectedUserAgent = "dummy-agent"; + + @Test + @DisplayName("Test default headers are added correctly.") + void addDefaultAgentHeader() { + // FIXME move to ManagedFhirWebAccessBuilder + HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com"); + + ManagedFhirWebAccessBuilder builder = new ManagedFhirWebAccessBuilder(expectedUserAgent, null); + + HTTPRequest requestWithDefaultHeaders = builder.httpRequestWithDefaultHeaders(request); + assertRequestContainsExpectedAgentHeader(requestWithDefaultHeaders); + } + + @Test + @DisplayName("Test default headers are added correctly.") + void addDefaultBasicHeader() { + // FIXME move to ManagedFhirWebAccessBuilder + HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com"); + + ManagedFhirWebAccessBuilder builder = new ManagedFhirWebAccessBuilder(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+"\"."); + } +} From 0308d18107da3a16f3458cffc263041e17665dd4 Mon Sep 17 00:00:00 2001 From: dotasek Date: Fri, 25 Oct 2024 16:58:34 -0400 Subject: [PATCH 07/42] WIP fix removed import --- .../dstu3/utils/client/network/FhirRequestBuilderTests.java | 2 -- 1 file changed, 2 deletions(-) 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 7809c11189..19af3ea757 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 @@ -2,11 +2,9 @@ import java.io.IOException; import java.net.MalformedURLException; -import java.net.URL; import org.hl7.fhir.dstu3.formats.IParser; import org.hl7.fhir.utilities.ToolingClientLogger; -import org.hl7.fhir.utilities.http.FhirRequest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; From f82f8764cf7501384f8b0eb6c58357817e6ea058 Mon Sep 17 00:00:00 2001 From: dotasek Date: Fri, 25 Oct 2024 17:01:09 -0400 Subject: [PATCH 08/42] Tidy up code ***NO_CI*** --- .../org/hl7/fhir/utilities/http/ManagedWebAccessBuilder.java | 4 +--- .../java/org/hl7/fhir/utilities/http/SimpleHTTPClient.java | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) 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/ManagedWebAccessBuilder.java index 5a7f376e1d..fdb15e7fc9 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/ManagedWebAccessBuilder.java @@ -22,8 +22,7 @@ public ManagedWebAccessBuilder(String userAgent, List serverA } private Map newHeaders() { - Map headers = new HashMap(); - headers.putAll(this.getHeaders()); + Map headers = new HashMap(this.getHeaders()); if (getAuthenticationMode() == HTTPAuthenticationMode.TOKEN) { headers.put("Authorization", "Bearer " + getToken()); } else if (getAuthenticationMode() == HTTPAuthenticationMode.BASIC) { @@ -35,7 +34,6 @@ private Map newHeaders() { if (getUserAgent() != null) { headers.put("User-Agent", getUserAgent()); } - return headers; } 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 ed6fa849c5..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 @@ -109,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)); From b51ac15051d3b7830d85d77d61e54d4e6305fb34 Mon Sep 17 00:00:00 2001 From: dotasek Date: Mon, 28 Oct 2024 10:45:06 -0400 Subject: [PATCH 09/42] Fix for renamed class --- .../fhir/r4b/utils/client/network/Client.java | 44 +++++----- .../client/network/FhirRequestBuilder.java | 10 +-- .../utils/client/FHIRToolingClientTest.java | 84 ++++++++++--------- .../network/FhirRequestBuilderTest.java | 6 +- 4 files changed, 74 insertions(+), 70 deletions(-) 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 42fb12c145..1ec391a81b 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 @@ -2,15 +2,11 @@ import lombok.Getter; import lombok.Setter; -import okhttp3.Headers; -import okhttp3.MediaType; -import okhttp3.Request; -import okhttp3.RequestBody; 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.FhirRequest; +import org.hl7.fhir.utilities.http.HTTPRequest; import org.hl7.fhir.utilities.http.HTTPHeader; import java.io.IOException; @@ -46,9 +42,9 @@ public ResourceRequest issueOptionsRequest(URI optionsUr /*FIXME delete once refactor is done Request.Builder request = new Request.Builder().method("OPTIONS", null).url(optionsUri.toURL()); */ - FhirRequest request = new FhirRequest() + HTTPRequest request = new HTTPRequest() .withUrl(optionsUri.toURL()) - .withMethod(FhirRequest.HttpMethod.OPTIONS); + .withMethod(HTTPRequest.HttpMethod.OPTIONS); return executeFhirRequest(request, resourceFormat, Collections.emptyList(), message, retryCount, timeout); } @@ -57,9 +53,9 @@ public ResourceRequest issueGetResourceRequest(URI resou /*FIXME delete once refactor is done Request.Builder request = new Request.Builder().url(resourceUri.toURL()); */ - FhirRequest request = new FhirRequest() + HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) - .withMethod(FhirRequest.HttpMethod.GET); + .withMethod(HTTPRequest.HttpMethod.GET); return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout); } @@ -76,9 +72,9 @@ public ResourceRequest issuePutRequest(URI resourceUri, RequestBody body = RequestBody.create(payload); Request.Builder request = new Request.Builder().url(resourceUri.toURL()).put(body); */ - FhirRequest request = new FhirRequest() + HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) - .withMethod(FhirRequest.HttpMethod.PUT) + .withMethod(HTTPRequest.HttpMethod.PUT) .withBody(payload) .withContentType(getContentTypeWithDefaultCharset(resourceFormat)); @@ -99,9 +95,9 @@ public ResourceRequest issuePostRequest(URI resourceUri, RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload); Request.Builder request = new Request.Builder().url(resourceUri.toURL()).post(body); */ - FhirRequest request = new FhirRequest() + HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) - .withMethod(FhirRequest.HttpMethod.POST) + .withMethod(HTTPRequest.HttpMethod.POST) .withBody(payload) .withContentType(getContentTypeWithDefaultCharset(resourceFormat)); @@ -112,9 +108,9 @@ public boolean issueDeleteRequest(URI resourceUri) throws IOException { /*FIXME delete once refactor is done Request.Builder request = new Request.Builder().url(resourceUri.toURL()).delete(); */ - FhirRequest request = new FhirRequest() + HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) - .withMethod(FhirRequest.HttpMethod.DELETE); + .withMethod(HTTPRequest.HttpMethod.DELETE); return executeFhirRequest(request, null, Collections.emptyList(), null, retryCount, timeout) .isSuccessfulRequest(); } @@ -123,9 +119,9 @@ public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat) throws /*FIXME delete once refactor is done Request.Builder request = new Request.Builder().url(resourceUri.toURL()); */ - FhirRequest request = new FhirRequest() + HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) - .withMethod(FhirRequest.HttpMethod.GET); + .withMethod(HTTPRequest.HttpMethod.GET); return executeBundleRequest(request, resourceFormat, Collections.emptyList(), null, retryCount, timeout); } @@ -137,9 +133,9 @@ public Bundle issuePostFeedRequest(URI resourceUri, Map paramete RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload); Request.Builder request = new Request.Builder().url(resourceUri.toURL()).post(body); */ - FhirRequest request = new FhirRequest() + HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) - .withMethod(FhirRequest.HttpMethod.POST) + .withMethod(HTTPRequest.HttpMethod.POST) .withBody(payload) .withContentType(getContentTypeWithDefaultCharset(resourceFormat)); return executeBundleRequest(request, resourceFormat, Collections.emptyList(), null, retryCount, timeout); @@ -153,9 +149,9 @@ public Bundle postBatchRequest(URI resourceUri, byte[] payload, String resourceF RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload); Request.Builder request = new Request.Builder().url(resourceUri.toURL()).post(body); */ - FhirRequest request = new FhirRequest() + HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) - .withMethod(FhirRequest.HttpMethod.POST) + .withMethod(HTTPRequest.HttpMethod.POST) .withBody(payload) .withContentType(getContentTypeWithDefaultCharset(resourceFormat)); return executeBundleRequest(request, resourceFormat, headers, message, retryCount, timeout); @@ -165,7 +161,7 @@ private static String getContentTypeWithDefaultCharset(String resourceFormat) { return resourceFormat + ";charset=" + DEFAULT_CHARSET; } - public Bundle executeBundleRequest(FhirRequest request, String resourceFormat, + public Bundle executeBundleRequest(HTTPRequest request, String resourceFormat, Iterable headers, String message, int retryCount, long timeout) throws IOException { return new FhirRequestBuilder(request, base).withLogger(fhirLoggingInterceptor).withResourceFormat(resourceFormat) .withRetryCount(retryCount).withMessage(message) @@ -173,8 +169,8 @@ public Bundle executeBundleRequest(FhirRequest request, Str .withTimeout(timeout, TimeUnit.MILLISECONDS).executeAsBatch(); } - public ResourceRequest executeFhirRequest(FhirRequest request, String resourceFormat, - Iterable 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).withResourceFormat(resourceFormat) .withRetryCount(retryCount).withMessage(message) .withHeaders(headers == null ? Collections.emptyList() : headers) 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 014544a159..19536ce73a 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 @@ -19,7 +19,7 @@ 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.http.FhirRequest; +import org.hl7.fhir.utilities.http.HTTPRequest; import org.hl7.fhir.utilities.http.HTTPHeader; import org.hl7.fhir.utilities.xhtml.XhtmlUtils; @@ -57,13 +57,13 @@ public class FhirRequestBuilder { private String source; //TODO this should be the only constructor. There should be no okHttp exposure. - public FhirRequestBuilder(FhirRequest fhirRequest, String source) { + public FhirRequestBuilder(HTTPRequest HTTPRequest, String source) { this.source = source; - RequestBody body = RequestBody.create(fhirRequest.getBody()); + RequestBody body = RequestBody.create(HTTPRequest.getBody()); this.httpRequest = new Request.Builder() - .url(fhirRequest.getUrl()) - .method(fhirRequest.getMethod().name(), body); + .url(HTTPRequest.getUrl()) + .method(HTTPRequest.getMethod().name(), body); } /** 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/FhirRequestBuilderTest.java b/org.hl7.fhir.r4b/src/test/java/org/hl7/fhir/r4b/utils/client/network/FhirRequestBuilderTest.java index 8cd65e5d33..2072f4c424 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 @@ -4,6 +4,7 @@ import okhttp3.OkHttpClient; import okhttp3.Request; import org.hl7.fhir.r4b.model.OperationOutcome; +import org.hl7.fhir.utilities.http.HTTPHeader; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -54,7 +55,10 @@ void addHeaders() { String headerName2 = "headerName2"; String headerValue2 = "headerValue2"; - Headers headers = new Headers.Builder().add(headerName1, headerValue1).add(headerName2, headerValue2).build(); + List headers = List.of( + new HTTPHeader(headerName1, headerValue1), + new HTTPHeader(headerName2, headerValue2) + ); Request.Builder request = new Request.Builder().url("http://www.google.com"); FhirRequestBuilder.addHeaders(request, headers); From 555edcf329bbfea87688e85bb09292bf6806c618 Mon Sep 17 00:00:00 2001 From: dotasek Date: Mon, 28 Oct 2024 11:46:07 -0400 Subject: [PATCH 10/42] Change mem settings for build --- pom.xml | 2 +- setup-and-cache-job-template.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 19df7f687e..9117561be4 100644 --- a/pom.xml +++ b/pom.xml @@ -434,7 +434,7 @@ while if true it will use an executable. --> true 512m - 768m + 2G true -J-XX:MaxRAMPercentage=50.0 diff --git a/setup-and-cache-job-template.yml b/setup-and-cache-job-template.yml index a5100e4ed2..fbee1d83ec 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: '-Xmx2G' javaHomeOption: 'JDKVersion' jdkVersionOption: '1.11' jdkArchitectureOption: 'x64' From cbafe2a28f5c87baa59c03537713034f1583eadc Mon Sep 17 00:00:00 2001 From: dotasek Date: Mon, 28 Oct 2024 16:00:20 -0400 Subject: [PATCH 11/42] Adjust pipeline mem again --- pom.xml | 4 ++-- setup-and-cache-job-template.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 9117561be4..3e9f3ea347 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ 2.17.0 32.0.1-jre 6.4.1 - 1.6.0 + 1.6.1-SNAPSHOT 2.17.0 5.9.2 1.8.2 @@ -434,7 +434,7 @@ while if true it will use an executable. --> true 512m - 2G + 1G true -J-XX:MaxRAMPercentage=50.0 diff --git a/setup-and-cache-job-template.yml b/setup-and-cache-job-template.yml index fbee1d83ec..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: '-Xmx2G' + mavenOptions: '-Xmx1G' javaHomeOption: 'JDKVersion' jdkVersionOption: '1.11' jdkArchitectureOption: 'x64' From d8ca1cc144e83186d01cf6a1fe734b21b53af384 Mon Sep 17 00:00:00 2001 From: dotasek Date: Tue, 29 Oct 2024 10:06:42 -0400 Subject: [PATCH 12/42] Apply changes to r4b --- .../fhir/r4b/utils/client/network/Client.java | 16 +- .../network/FhirLoggingInterceptor.java | 69 --------- .../client/network/FhirRequestBuilder.java | 139 ++++++++---------- .../client/network/RetryInterceptor.java | 67 --------- .../network/FhirRequestBuilderTest.java | 63 ++++---- 5 files changed, 99 insertions(+), 255 deletions(-) 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 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 1ec391a81b..1e17be18dc 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 @@ -19,24 +19,20 @@ public class Client { public static final String DEFAULT_CHARSET = "UTF-8"; private static final long DEFAULT_TIMEOUT = 5000; - @Getter + @Getter @Setter private ToolingClientLogger logger; - private FhirLoggingInterceptor fhirLoggingInterceptor; + @Setter @Getter private int retryCount; + @Setter @Getter private long timeout = DEFAULT_TIMEOUT; - //private byte[] payload; + @Setter @Getter private String base; - public void setLogger(ToolingClientLogger logger) { - this.logger = logger; - this.fhirLoggingInterceptor = new FhirLoggingInterceptor(logger); - } - public ResourceRequest issueOptionsRequest(URI optionsUri, String resourceFormat, String message, long timeout) throws IOException { /*FIXME delete once refactor is done @@ -163,7 +159,7 @@ private static String getContentTypeWithDefaultCharset(String resourceFormat) { public Bundle executeBundleRequest(HTTPRequest request, String resourceFormat, Iterable headers, String message, int retryCount, long timeout) throws IOException { - return new FhirRequestBuilder(request, base).withLogger(fhirLoggingInterceptor).withResourceFormat(resourceFormat) + return new FhirRequestBuilder(request, base).withLogger(logger).withResourceFormat(resourceFormat) .withRetryCount(retryCount).withMessage(message) .withHeaders(headers == null ? Collections.emptyList() : headers) .withTimeout(timeout, TimeUnit.MILLISECONDS).executeAsBatch(); @@ -171,7 +167,7 @@ public Bundle executeBundleRequest(HTTPRequest request, Str public ResourceRequest executeFhirRequest(HTTPRequest request, String resourceFormat, Iterable headers, String message, int retryCount, long timeout) throws IOException { - return new FhirRequestBuilder(request, base).withLogger(fhirLoggingInterceptor).withResourceFormat(resourceFormat) + return new FhirRequestBuilder(request, base).withLogger(logger).withResourceFormat(resourceFormat) .withRetryCount(retryCount).withMessage(message) .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/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 19536ce73a..6fa69e3540 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,6 +1,7 @@ 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; @@ -19,8 +20,8 @@ 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.http.HTTPRequest; -import org.hl7.fhir.utilities.http.HTTPHeader; +import org.hl7.fhir.utilities.ToolingClientLogger; +import org.hl7.fhir.utilities.http.*; import org.hl7.fhir.utilities.xhtml.XhtmlUtils; public class FhirRequestBuilder { @@ -34,8 +35,8 @@ public class FhirRequestBuilder { /** * The singleton instance of the HttpClient, used for all requests. */ - private static OkHttpClient okHttpClient; - private final Request.Builder httpRequest; + //private static OkHttpClient okHttpClient; + private final HTTPRequest httpRequest; private String resourceFormat = null; private Iterable headers = null; private String message = null; @@ -51,19 +52,15 @@ 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; //TODO this should be the only constructor. There should be no okHttp exposure. - public FhirRequestBuilder(HTTPRequest HTTPRequest, String source) { + public FhirRequestBuilder(HTTPRequest httpRequest, String source) { this.source = source; - - RequestBody body = RequestBody.create(HTTPRequest.getBody()); - this.httpRequest = new Request.Builder() - .url(HTTPRequest.getUrl()) - .method(HTTPRequest.getMethod().name(), body); + this.httpRequest = httpRequest; } /** @@ -74,43 +71,25 @@ public FhirRequestBuilder(HTTPRequest HTTPRequest, String source) { * @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, Iterable 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, Iterable headers) { - boolean hasUserAgent = false; - if (headers != null) { - for (HTTPHeader header : headers) { - if (header.getName().equalsIgnoreCase("User-Agent")) { - hasUserAgent = true; - break; - } - } - } - if (!hasUserAgent) { - 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. - */ - protected static void addResourceFormatHeaders(Request.Builder request, String format) { - request.addHeader("Accept", format); - 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; } /** @@ -148,15 +127,21 @@ protected static boolean hasError(OperationOutcome oo) { * @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; + /** + * Extracts the 'location' header from. If no value for 'location' exists, the + * value for 'content-location' is returned. If neither header exists, we return null. + */ + 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); + } + + protected ManagedFhirWebAccessBuilder getManagedWebAccessBuilder() { + return new ManagedFhirWebAccessBuilder("hapi-fhir-tooling-client", null).withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); } /** @@ -176,6 +161,7 @@ protected static String getLocationHeader(Headers headers) { * * @return {@link OkHttpClient} instance */ + /*FIXME remove after refactor protected OkHttpClient getHttpClient() { if (okHttpClient == null) { okHttpClient = new OkHttpClient(); @@ -191,7 +177,7 @@ protected OkHttpClient getHttpClient() { return builder.connectTimeout(timeout, timeoutUnit).writeTimeout(timeout, timeoutUnit) .readTimeout(timeout, timeoutUnit).proxyAuthenticator(proxyAuthenticator).build(); } - +*/ @Nonnull private static Authenticator getAuthenticator() { return (route, response) -> { @@ -225,7 +211,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 +222,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 = getManagedWebAccessBuilder().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 = getManagedWebAccessBuilder().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 +252,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 +263,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")) { @@ -305,10 +286,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); } @@ -343,7 +324,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/network/FhirRequestBuilderTest.java b/org.hl7.fhir.r4b/src/test/java/org/hl7/fhir/r4b/utils/client/network/FhirRequestBuilderTest.java index 2072f4c424..b588e331e2 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,50 +1,52 @@ 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); + @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); - 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\"."); + 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.") - void addResourceFormatHeaders() { + @DisplayName("Test resource format headers are added correctly (POST).") + void addResourceFormatHeadersPOST() { + //FIXME tested here. Should get list of HTTPHeader. 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.POST); - 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 + "."); + "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.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 @@ -119,7 +121,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)); } @@ -127,8 +129,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)); } @@ -137,15 +138,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 From fe58a8a2c51c8cd4e4211cbaca9c7cb37063098c Mon Sep 17 00:00:00 2001 From: dotasek Date: Tue, 29 Oct 2024 11:09:47 -0400 Subject: [PATCH 13/42] Actually add headers from HTTPRequest to OkHttp request --- .../hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java index 7179155509..d3f612f94b 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java @@ -118,6 +118,9 @@ public HTTPResult httpCall(HTTPRequest httpRequest) throws IOException { .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(url)) { From a5ef974c3ddcb6e43f3858dc647bcbfe1edcfa33 Mon Sep 17 00:00:00 2001 From: dotasek Date: Tue, 29 Oct 2024 17:07:41 -0400 Subject: [PATCH 14/42] Use ManagedFhirWebAccess in r4 --- .../utils/client/network/ClientHeaders.java | 37 ++-- org.hl7.fhir.r4/pom.xml | 13 +- .../r4/utils/client/FHIRToolingClient.java | 129 ++++++-------- .../fhir/r4/utils/client/network/Client.java | 145 +++++++++------- .../utils/client/network/ClientHeaders.java | 32 ++-- .../client/network/FhirRequestBuilder.java | 158 ++++++++---------- .../hl7/fhir/r4/utils/client/ClientTest.java | 143 ++++++++++++++++ .../r4b/utils/client/FHIRToolingClient.java | 87 +++++----- .../utils/client/network/ClientHeaders.java | 35 ++-- .../client/network/ClientHeadersTest.java | 24 +-- .../r5/utils/client/FHIRToolingClient.java | 13 +- .../utils/client/network/ClientHeaders.java | 12 +- pom.xml | 2 +- 13 files changed, 475 insertions(+), 355 deletions(-) create mode 100644 org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/ClientTest.java 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.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 df3f3e55ac..563a268f80 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 @@ -5,6 +5,8 @@ import java.net.URISyntaxException; 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; @@ -26,8 +28,6 @@ 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; /** @@ -68,16 +68,25 @@ 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 List headers = new ArrayList<>(); + @Getter @Setter private String username; + @Getter @Setter private String password; + @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 @@ -94,14 +103,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(); @@ -113,10 +114,6 @@ public String getPreferredResourceFormat() { return preferredResourceFormat.getHeader(); } - public void setPreferredResourceFormat(ResourceFormat resourceFormat) { - preferredResourceFormat = resourceFormat; - } - public int getMaximumRecordCount() { return maxResultSetSize; } @@ -129,7 +126,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); } @@ -140,7 +137,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); } @@ -152,7 +149,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); @@ -165,7 +162,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(), @@ -182,7 +179,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(), @@ -200,7 +197,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(), @@ -218,7 +215,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(), @@ -242,7 +239,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(), @@ -272,7 +269,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(), @@ -310,10 +307,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()) { @@ -349,7 +346,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(), @@ -406,7 +403,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); } @@ -423,7 +420,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); } @@ -440,7 +437,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); } @@ -461,7 +458,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(), @@ -492,7 +489,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()); @@ -513,7 +510,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()); @@ -524,22 +521,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(); } @@ -561,46 +542,38 @@ public void setClientHeaders(Iterable headers) { headers.forEach(this.headers::add); } - private Headers generateHeaders() { - Headers.Builder builder = new Headers.Builder(); + //FIXME should be in ManagedWebAccess? + private Iterable generateHeaders(boolean hasBody) { + List headers = new ArrayList<>(); // Add basic auth header if it exists if (basicAuthHeaderExists()) { - builder.add(getAuthorizationHeader().toString()); + headers.add(getAuthorizationHeader()); } // Add any other headers - if (this.headers != null) { - this.headers.forEach(header -> builder.add(header.toString())); - } + headers.addAll(this.headers); if (!Utilities.noString(userAgent)) { - builder.add("User-Agent: " + userAgent); + headers.add(new HTTPHeader("User-Agent",userAgent)); } - if (!Utilities.noString(acceptLang)) { - builder.add("Accept-Language: "+acceptLang); + if (!Utilities.noString(acceptLanguage)) { + headers.add(new HTTPHeader("Accept-Language", acceptLanguage)); } - if (!Utilities.noString(contentLang)) { - builder.add("Content-Language: "+contentLang); + + if (hasBody && !Utilities.noString(contentLanguage)) { + headers.add(new HTTPHeader("Content-Language",contentLanguage)); } - - return builder.build(); + + return headers; } public boolean basicAuthHeaderExists() { return (username != null) && (password != null); } - public Header getAuthorizationHeader() { + public HTTPHeader 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 new HTTPHeader("Authorization", "Basic " + base64usernamePassword); } public String getServerVersion() { @@ -608,14 +581,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)); @@ -626,7 +591,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..75549e5af4 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,179 @@ 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 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 { + /*FIXME delete once refactor is done 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 { + /*FIXME delete once refactor is done + 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, 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"); + /*FIXME delete once refactor is done 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"); + /*FIXME delete once refactor is done 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 { + /*FIXME delete once refactor is done 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 { + /*FIXME delete once refactor is done 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); + + /*FIXME delete once refactor is done 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"); + /*FIXME delete once refactor is done 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, Collections.emptyList(), message, retryCount, timeout); + } - return executeBundleRequest(request, resourceFormat, new Headers.Builder().build(), 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/FhirRequestBuilder.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilder.java index a9b0d78adb..1a9a7c6a12 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,15 @@ 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,15 +21,14 @@ 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 { @@ -43,10 +41,9 @@ public class FhirRequestBuilder { /** * 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,14 +57,17 @@ 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; } /** @@ -78,12 +78,25 @@ public FhirRequestBuilder(Request.Builder httpRequest, String source) { * @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); + 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; } /** @@ -136,62 +149,27 @@ 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; } + return HTTPHeaderUtil.getSingleHeader(headers, CONTENT_LOCATION_HEADER); + } - 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(); + protected ManagedFhirWebAccessBuilder getManagedWebAccessBuilder() { + return new ManagedFhirWebAccessBuilder("hapi-fhir-tooling-client", null).withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); } + @Nonnull private static Authenticator getAuthenticator() { return (route, response) -> { @@ -210,7 +188,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 +203,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 +214,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 = getManagedWebAccessBuilder().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 = getManagedWebAccessBuilder().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 +247,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 +258,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 +275,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,7 +319,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.r4/src/test/java/org/hl7/fhir/r4/utils/client/ClientTest.java b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/ClientTest.java new file mode 100644 index 0000000000..759170025e --- /dev/null +++ b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/ClientTest.java @@ -0,0 +1,143 @@ +package org.hl7.fhir.r4.utils.client; + +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.hl7.fhir.r4.utils.client.network.ByteUtils; +import org.hl7.fhir.r4.utils.client.network.Client; +import org.hl7.fhir.r4.utils.client.network.ResourceRequest; +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.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 66942512b0..9c247bf9d9 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 @@ -92,14 +92,28 @@ public class FHIRToolingClient extends FHIRBaseToolingClient{ private ResourceFormat preferredResourceFormat; private int maxResultSetSize = -1;// _count private CapabilityStatement capabilities; + @Getter + @Setter private Client client = new Client(); private List headers = new ArrayList<>(); + @Getter + @Setter private String username; + + @Getter + @Setter private String password; + @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; @@ -115,14 +129,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(); @@ -150,7 +156,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); } @@ -161,7 +167,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); } @@ -173,7 +179,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); @@ -185,7 +191,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(), @@ -202,7 +208,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(), @@ -219,7 +225,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(), @@ -242,7 +248,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(), @@ -271,7 +277,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(), @@ -309,10 +315,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()) { @@ -338,7 +344,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); @@ -352,7 +358,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(), @@ -410,7 +416,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()); @@ -425,7 +431,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(); } @@ -447,7 +453,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()); @@ -490,7 +496,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()); @@ -501,22 +507,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(); } @@ -546,19 +536,26 @@ public void setClientHeaders(Iterable headers) { headers.forEach(this.headers::add); } - private Iterable generateHeaders() { + private Iterable generateHeaders(boolean hasBody) { List headers = new ArrayList<>(); // Add basic auth header if it exists if (basicAuthHeaderExists()) { headers.add(getAuthorizationHeader()); } // Add any other headers - if (this.headers != null) { - headers.addAll(this.headers); - } + headers.addAll(this.headers); if (!Utilities.noString(userAgent)) { headers.add(new HTTPHeader("User-Agent",userAgent)); } + + if (!Utilities.noString(acceptLanguage)) { + headers.add(new HTTPHeader("Accept-Language", acceptLanguage)); + } + + if (hasBody && !Utilities.noString(contentLanguage)) { + headers.add(new HTTPHeader("Content-Language",contentLanguage)); + } + return headers; } 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/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.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 ec14c733a2..182249f3e9 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 @@ -112,10 +112,11 @@ public class FHIRToolingClient extends FHIRBaseToolingClient { @Getter private String userAgent; - @Setter private String acceptLanguage; - private String contentLang; + + @Setter + private String contentLanguage; private int useCount; @@ -618,8 +619,8 @@ private Iterable generateHeaders(boolean hasBody) { headers.add(new HTTPHeader("Accept-Language", acceptLanguage)); } - if (hasBody && !Utilities.noString(contentLang)) { - headers.add(new HTTPHeader("Content-Language",contentLang)); + if (hasBody && !Utilities.noString(contentLanguage)) { + headers.add(new HTTPHeader("Content-Language", contentLanguage)); } return headers; @@ -646,10 +647,6 @@ public String getServerVersion() { return capabilities == null ? null : capabilities.getSoftware().getVersion(); } - 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/ClientHeaders.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/ClientHeaders.java index b6cde0747e..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,6 +1,6 @@ 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; @@ -34,7 +34,7 @@ public List 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(HTTPHeader header) throws FHIRException { @@ -49,7 +49,7 @@ public ClientHeaders addHeader(HTTPHeader 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 { @@ -59,7 +59,7 @@ public ClientHeaders addHeaders(List headerList) throws FHIRExceptio /** * 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(HTTPHeader header) throws FHIRException { @@ -72,7 +72,7 @@ public ClientHeaders removeHeader(HTTPHeader 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 { @@ -81,7 +81,7 @@ public ClientHeaders removeHeaders(List headerList) throws FHIRExcep } /** - * Clears all stored {@link Header}. + * Clears all stored {@link HTTPHeader}. */ public ClientHeaders clearHeaders() { headers.clear(); diff --git a/pom.xml b/pom.xml index 3e9f3ea347..6e48dda49f 100644 --- a/pom.xml +++ b/pom.xml @@ -434,7 +434,7 @@ while if true it will use an executable. --> true 512m - 1G + 1g true -J-XX:MaxRAMPercentage=50.0 From 2fdfaf86029f977c6966042c2bc9ad96833eb86e Mon Sep 17 00:00:00 2001 From: dotasek Date: Tue, 29 Oct 2024 18:32:58 -0400 Subject: [PATCH 15/42] Removing leftover okttp usage --- .../client/network/FhirRequestBuilder.java | 59 +------ .../client/network/ClientHeadersTest.java | 104 ++++++++++++ .../client/{ => network}/ClientTest.java | 5 +- .../network/FhirRequestBuilderTest.java | 153 ++++++++++++++++++ .../client/network/FhirRequestBuilder.java | 69 +------- .../network/FhirRequestBuilderTest.java | 27 +--- .../client/network/FhirRequestBuilder.java | 22 +-- .../network/FhirRequestBuilderTest.java | 4 +- 8 files changed, 272 insertions(+), 171 deletions(-) create mode 100644 org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/network/ClientHeadersTest.java rename org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/{ => network}/ClientTest.java (96%) create mode 100644 org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilderTest.java 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 1a9a7c6a12..e849e15f2f 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 @@ -25,16 +25,8 @@ import org.hl7.fhir.utilities.http.*; import org.hl7.fhir.utilities.xhtml.XhtmlUtils; -import okhttp3.Authenticator; -import okhttp3.Credentials; -import okhttp3.Headers; -import okhttp3.Request; - 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"; @@ -72,11 +64,11 @@ public FhirRequestBuilder(HTTPRequest httpRequest, String source) { /** * 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 okhttp3.Request.Builder} * * @param request {@link okhttp3.Request.Builder} to add headers to. * @param format Expected {@link Resource} format. - * @param headers Any additional {@link Headers} to add to the request. + * @param headers Any additional {@link HTTPHeader}s to add to the request. */ protected static HTTPRequest formatHeaders(HTTPRequest request, String format, Iterable headers) { List allHeaders = new ArrayList<>(); @@ -99,39 +91,6 @@ protected static Iterable getResourceFormatHeaders(HTTPRequest httpR return 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) { - headers.forEach(header -> request.addHeader(header.getFirst(), header.getSecond())); - } - /** * Returns true if any of the * {@link org.hl7.fhir.r4.model.OperationOutcome.OperationOutcomeIssueComponent} @@ -169,20 +128,6 @@ protected ManagedFhirWebAccessBuilder getManagedWebAccessBuilder() { return new ManagedFhirWebAccessBuilder("hapi-fhir-tooling-client", null).withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); } - - @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(); - }; - } - public FhirRequestBuilder withResourceFormat(String resourceFormat) { this.resourceFormat = resourceFormat; return this; 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/ClientTest.java b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/network/ClientTest.java similarity index 96% rename from org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/ClientTest.java rename to org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/network/ClientTest.java index 759170025e..19cc74d1d5 100644 --- a/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/ClientTest.java +++ b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/network/ClientTest.java @@ -1,4 +1,4 @@ -package org.hl7.fhir.r4.utils.client; +package org.hl7.fhir.r4.utils.client.network; import okhttp3.HttpUrl; import okhttp3.mockwebserver.MockWebServer; @@ -7,9 +7,6 @@ import org.hl7.fhir.r4.context.HTMLClientLogger; import org.hl7.fhir.r4.formats.JsonParser; import org.hl7.fhir.r4.model.*; -import org.hl7.fhir.r4.utils.client.network.ByteUtils; -import org.hl7.fhir.r4.utils.client.network.Client; -import org.hl7.fhir.r4.utils.client.network.ResourceRequest; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; 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..f5b921eecf --- /dev/null +++ b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilderTest.java @@ -0,0 +1,153 @@ +package org.hl7.fhir.r4.utils.client.network; + +import okhttp3.Request; +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() { + //FIXME tested here. Should get list of HTTPHeader. + 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 a list of provided headers are added correctly.") + void addHeaders() { + String headerName1 = "headerName1"; + String headerValue1 = "headerValue1"; + String headerName2 = "headerName2"; + String headerValue2 = "headerValue2"; + + List headers = List.of( + new HTTPHeader(headerName1, headerValue1), + new HTTPHeader(headerName2, headerValue2) + ); + + 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() { + 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/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 6fa69e3540..b0b2e94bbf 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 @@ -8,7 +8,7 @@ import javax.annotation.Nonnull; -import okhttp3.*; + import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.r4b.formats.IParser; import org.hl7.fhir.r4b.formats.JsonParser; @@ -32,10 +32,7 @@ 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 HTTPRequest httpRequest; private String resourceFormat = null; private Iterable headers = null; @@ -65,11 +62,11 @@ public FhirRequestBuilder(HTTPRequest httpRequest, String source) { /** * Adds necessary default headers, formatting headers, and any passed in - * {@link Headers} to the passed in {@link okhttp3.Request.Builder} + * {@link HTTPHeader} to the passed in {@link okhttp3.Request.Builder} * * @param request {@link okhttp3.Request.Builder} to add headers to. * @param format Expected {@link Resource} format. - * @param headers Any additional {@link Headers} to add to the request. + * @param headers Any additional {@link HTTPHeader} to add to the request. */ protected static HTTPRequest formatHeaders(HTTPRequest request, String format, Iterable headers) { List allHeaders = new ArrayList<>(); @@ -92,17 +89,6 @@ protected static Iterable getResourceFormatHeaders(HTTPRequest httpR 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, Iterable headers) { - headers.forEach(header -> request.addHeader(header.getName(), header.getValue())); - } - /** * Returns true if any of the * {@link org.hl7.fhir.r4b.model.OperationOutcome.OperationOutcomeIssueComponent} @@ -144,53 +130,6 @@ protected ManagedFhirWebAccessBuilder getManagedWebAccessBuilder() { return new ManagedFhirWebAccessBuilder("hapi-fhir-tooling-client", null).withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); } - /** - * 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 - */ - /*FIXME remove after refactor - protected OkHttpClient getHttpClient() { - if (okHttpClient == null) { - okHttpClient = new OkHttpClient(); - } - - Authenticator proxyAuthenticator = getAuthenticator(); - - OkHttpClient.Builder builder = okHttpClient.newBuilder(); - if (logger != null) - builder.addInterceptor(logger); - builder.addInterceptor(new RetryInterceptor(retryCount)); - - return builder.connectTimeout(timeout, timeoutUnit).writeTimeout(timeout, timeoutUnit) - .readTimeout(timeout, timeoutUnit).proxyAuthenticator(proxyAuthenticator).build(); - } -*/ - @Nonnull - private static Authenticator getAuthenticator() { - return (route, response) -> { - final String httpProxyUser = System.getProperty(HTTP_PROXY_USER); - final String httpProxyPass = System.getProperty(HTTP_PROXY_PASS); - if (httpProxyUser != null && httpProxyPass != null) { - String credential = Credentials.basic(httpProxyUser, httpProxyPass); - return response.request().newBuilder().header(HEADER_PROXY_AUTH, credential).build(); - } - return response.request().newBuilder().build(); - }; - } - public FhirRequestBuilder withResourceFormat(String resourceFormat) { this.resourceFormat = resourceFormat; return this; 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 b588e331e2..a1004d6dfc 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,6 +1,6 @@ package org.hl7.fhir.r4b.utils.client.network; -import okhttp3.Request; + import org.hl7.fhir.r4b.model.OperationOutcome; import org.hl7.fhir.utilities.http.HTTPHeader; import org.hl7.fhir.utilities.http.HTTPHeaderUtil; @@ -49,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"; - - List headers = List.of( - new HTTPHeader(headerName1, headerValue1), - new HTTPHeader(headerName2, headerValue2) - ); - - 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() { 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 b39519e6b2..e42b857935 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 @@ -5,7 +5,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; -import okhttp3.*; + import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.r5.formats.IParser; import org.hl7.fhir.r5.formats.JsonParser; @@ -62,12 +62,12 @@ public FhirRequestBuilder(HTTPRequest httpRequest, String source) { } /** - * Adds necessary default headers, formatting headers, and any passed in {@link Headers} to the passed in - * {@link Request.Builder} + * Adds necessary default headers, formatting headers, and any passed in {@link HTTPHeader}s to the passed in + * {@link HTTPRequest} * - * @param request {@link 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 HTTPRequest formatHeaders(HTTPRequest request, String format, Iterable headers) { List allHeaders = new ArrayList<>(); @@ -84,7 +84,7 @@ protected static HTTPRequest formatHeaders(HTTPRequest request, String format, I /** * Adds necessary headers for the given resource format provided. * - * @param httpRequest {@link Request.Builder} to add default headers to. + * @param httpRequest {@link HTTPRequest} to add default headers to. */ protected static Iterable getResourceFormatHeaders(HTTPRequest httpRequest, String format) { List headers = new ArrayList<>(); @@ -98,16 +98,6 @@ protected static Iterable getResourceFormatHeaders(HTTPRequest httpR 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, Iterable headers) { - headers.forEach(header -> request.addHeader(header.getName(), header.getValue())); - } - /** * Returns true if any of the {@link OperationOutcome.OperationOutcomeIssueComponent} within the * provided {@link OperationOutcome} have an {@link OperationOutcome.IssueSeverity} of 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 c03c16bc17..e7e3235282 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 @@ -13,8 +13,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import okhttp3.Request; - class FhirRequestBuilderTest { @Test @@ -67,7 +65,7 @@ void addHeaders() { new HTTPHeader(headerName2, headerValue2) ); - Request.Builder request = new Request.Builder().url("http://www.google.com"); + HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com"); headers.forEach(header -> request.addHeader(header.getName(), header.getValue())); Map> headersMap = request.build().headers().toMultimap(); From 2f95e3fe9fc030c2d8b7ade473b2d64310932d7e Mon Sep 17 00:00:00 2001 From: dotasek Date: Wed, 30 Oct 2024 09:30:09 -0400 Subject: [PATCH 16/42] Remove unused classes and okhttp usages and add tests for r4 --- .../fhir/r4/utils/client/network/Client.java | 4 - .../network/FhirLoggingInterceptor.java | 74 ------ .../client/network/RetryInterceptor.java | 67 ------ .../utils/client/FhirToolingClientTest.java | 225 ++++++++++++++++++ .../network/FhirRequestBuilderTest.java | 26 +- .../r5/utils/client/FHIRToolingClient.java | 2 - .../client/network/ClientHeadersTest.java | 2 - .../network/FhirRequestBuilderTest.java | 26 -- .../hl7/fhir/validation/special/TxTester.java | 5 - pom.xml | 2 +- 10 files changed, 227 insertions(+), 206 deletions(-) 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 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 75549e5af4..0835cda2a5 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 @@ -13,10 +13,6 @@ 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; 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/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..1fdc645fb1 --- /dev/null +++ b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/FhirToolingClientTest.java @@ -0,0 +1,225 @@ +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/FhirRequestBuilderTest.java b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilderTest.java index f5b921eecf..77e6d473fb 100644 --- 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 @@ -1,6 +1,7 @@ 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; @@ -48,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"; - - List headers = List.of( - new HTTPHeader(headerName1, headerValue1), - new HTTPHeader(headerName2, headerValue2) - ); - - 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() { 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 182249f3e9..696cd56cdf 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,7 +1,5 @@ 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; 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 3da2c3df2f..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 @@ -10,8 +10,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import okhttp3.internal.http2.Header; - class ClientHeadersTest { ClientHeaders clientHeaders; 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 e7e3235282..ffcf8ab164 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 @@ -51,32 +51,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() { - //FIXME tested here. Should get list of HTTPHeader. - String headerName1 = "headerName1"; - String headerValue1 = "headerValue1"; - String headerName2 = "headerName2"; - String headerValue2 = "headerValue2"; - - List headers = List.of( - new HTTPHeader(headerName1, headerValue1), - new HTTPHeader(headerName2, headerValue2) - ); - - HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com"); - headers.forEach(header -> request.addHeader(header.getName(), header.getValue())); - - 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() { 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 0b3dc5663f..12d3def6eb 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 @@ -1,7 +1,6 @@ package org.hl7.fhir.validation.special; import java.io.File; -import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -20,7 +19,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; @@ -34,7 +32,6 @@ 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; @@ -45,8 +42,6 @@ import org.hl7.fhir.utilities.json.model.JsonObject; import org.hl7.fhir.utilities.json.parser.JsonParser; -import okhttp3.internal.http2.Header; - public class TxTester { diff --git a/pom.xml b/pom.xml index 6e48dda49f..e408832b0c 100644 --- a/pom.xml +++ b/pom.xml @@ -434,7 +434,7 @@ while if true it will use an executable. --> true 512m - 1g + 1000m true -J-XX:MaxRAMPercentage=50.0 From eb7144053df33e89791a2420f6ed54ddb56571b9 Mon Sep 17 00:00:00 2001 From: dotasek Date: Wed, 30 Oct 2024 13:11:51 -0400 Subject: [PATCH 17/42] Make dstu3 use ManagedFhirWebAccess --- org.hl7.fhir.dstu3/pom.xml | 13 + .../dstu3/utils/client/FHIRToolingClient.java | 79 +++--- .../dstu3/utils/client/network/Client.java | 96 ++++--- .../client/network/FhirRequestBuilder.java | 241 ++++-------------- .../client/network/RetryInterceptor.java | 62 ----- .../utils/client/FhirToolingClientTest.java | 225 ++++++++++++++++ .../utils/client/network/ClientTest.java | 169 ++++++++++++ .../network/FhirRequestBuilderTests.java | 179 +++++++------ 8 files changed, 665 insertions(+), 399 deletions(-) delete mode 100644 org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/RetryInterceptor.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 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 a596ffef2b..90c319d715 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 @@ -5,12 +5,12 @@ import java.net.URISyntaxException; import java.util.*; +import lombok.Getter; 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; @@ -27,8 +27,6 @@ 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; /** @@ -74,8 +72,9 @@ public class FHIRToolingClient extends FHIRBaseToolingClient { private String password; private String userAgent; private EnumSet allowedVersions; - private String acceptLang; - private String contentLang; + @Getter + private String acceptLanguage; + private String contentLanguage; private int useCount; //Pass endpoint for client - URI @@ -147,7 +146,7 @@ public Parameters getTerminologyCapabilities() { try { capabilities = (Parameters) client.issueGetResourceRequest(resourceAddress.resolveMetadataTxCaps(), withVer(getPreferredResourceFormat(), "3.0"), - generateHeaders(), + generateHeaders(false), "TerminologyCapabilities", timeoutNormal).getReference(); } catch (Exception e) { @@ -161,7 +160,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) { @@ -175,7 +174,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) { @@ -190,7 +189,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()) { @@ -208,7 +207,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()) { @@ -226,7 +225,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()) { @@ -250,7 +249,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()) { @@ -278,7 +277,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()) { @@ -317,13 +316,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()); @@ -360,7 +359,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()); @@ -417,7 +416,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) { @@ -436,7 +435,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) { @@ -455,7 +454,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) { @@ -476,7 +475,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()) { @@ -501,7 +500,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()) { @@ -523,7 +522,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()) { @@ -580,36 +579,38 @@ public void setClientHeaders(Iterable headers) { headers.forEach(this.headers::add); } - private Headers generateHeaders() { - Headers.Builder builder = new Headers.Builder(); + //FIXME should be in ManagedWebAccess? + private Iterable generateHeaders(boolean hasBody) { + List headers = new ArrayList<>(); // Add basic auth header if it exists if (basicAuthHeaderExists()) { - builder.add(getAuthorizationHeader().toString()); + headers.add(getAuthorizationHeader()); } // Add any other headers - if(this.headers != null) { - this.headers.forEach(header -> builder.add(header.toString())); - } + headers.addAll(this.headers); if (!Utilities.noString(userAgent)) { - builder.add("User-Agent: "+userAgent); - } - if (!Utilities.noString(acceptLang)) { - builder.add("Accept-Language: "+acceptLang); + headers.add(new HTTPHeader("User-Agent",userAgent)); } - if (!Utilities.noString(contentLang)) { - builder.add("Content-Language: "+contentLang); + + if (!Utilities.noString(acceptLanguage)) { + headers.add(new HTTPHeader("Accept-Language", acceptLanguage)); } - return builder.build(); + + if (hasBody && !Utilities.noString(contentLanguage)) { + headers.add(new HTTPHeader("Content-Language",contentLanguage)); + } + + return headers; } public boolean basicAuthHeaderExists() { return (username != null) && (password != null); } - public Header getAuthorizationHeader() { + public HTTPHeader getAuthorizationHeader() { String usernamePassword = username + ":" + password; String base64usernamePassword = Base64.getEncoder().encodeToString(usernamePassword.getBytes()); - return new Header("Authorization", "Basic " + base64usernamePassword); + return new HTTPHeader("Authorization", "Basic " + base64usernamePassword); } public String getUserAgent() { @@ -626,10 +627,10 @@ public String getServerVersion() { } public void setAcceptLanguage(String lang) { - this.acceptLang = lang; + this.acceptLanguage = lang; } public void setContentLanguage(String lang) { - this.contentLang = lang; + this.contentLanguage = lang; } public int getUseCount() { 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..6ce3da987d 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,30 @@ public ResourceRequest issueOptionsRequest(URI optionsUr String resourceFormat, String message, long timeout) throws IOException { + /*FIXME delete after refactor Request.Builder request = new Request.Builder() .method("OPTIONS", null) .url(optionsUri.toURL()); + */ + HTTPRequest request = new HTTPRequest() + .withUrl(optionsUri.toURL()) + .withMethod(HTTPRequest.HttpMethod.OPTIONS); - return executeFhirRequest(request, resourceFormat, new Headers.Builder().build(), message, retryCount, timeout); + 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 { + /*FIXME delete after refactor 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 +95,26 @@ 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"); + /*FIXME delete after refactor 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 +123,50 @@ 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"); + /*FIXME delete after refactor 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 { + /*FIXME delete after refactor 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 { + /*FIXME delete after refactor 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 +176,18 @@ public Bundle issuePostFeedRequest(URI resourceUri, String resourceFormat) throws IOException { String boundary = "----WebKitFormBoundarykbMUo6H8QaUnYtRy"; byte[] payload = ByteUtils.encodeFormSubmission(parameters, resourceName, resource, boundary); + /*FIXME delete after refactor 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 executeBundleRequest(request, resourceFormat, new Headers.Builder().build(), null, retryCount, timeout); + return executeBundleRequest(request, resourceFormat, Collections.emptyList(), null, retryCount, timeout); } public Bundle postBatchRequest(URI resourceUri, @@ -163,17 +196,22 @@ public Bundle postBatchRequest(URI resourceUri, String message, int timeout) throws IOException { if (payload == null) throw new EFhirClientException("POST requests require a non-null payload"); + /*FIXME delete after refactor 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 +220,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/FhirRequestBuilder.java b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/FhirRequestBuilder.java index ad15ae999a..86f5b98062 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 + * Adds necessary default headers, formatting headers, and any passed in {@link HTTPHeader}s 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. - */ - 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 ManagedFhirWebAccessBuilder getManagedWebAccessBuilder() { + return new ManagedFhirWebAccessBuilder("hapi-fhir-tooling-client", null).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 = getManagedWebAccessBuilder().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 = getManagedWebAccessBuilder().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); @@ -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/main/java/org/hl7/fhir/dstu3/utils/client/network/RetryInterceptor.java b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/RetryInterceptor.java deleted file mode 100644 index e3b6ec084e..0000000000 --- a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/RetryInterceptor.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.hl7.fhir.dstu3.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.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 19af3ea757..d2f85b5202 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,111 +1,128 @@ package org.hl7.fhir.dstu3.utils.client.network; -import java.io.IOException; -import java.net.MalformedURLException; - -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 java.util.Collections; +import java.util.List; +import java.util.Map; + +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 = "{}"; - - Response response = new Response.Builder() - .request(mockRequest) - .protocol(Protocol.HTTP_2) - .code(200) // status code - .message("") - .body(ResponseBody.create(RESPONSE_BODY_STRING, - MediaType.get("application/json; charset=utf-8") - )) - .addHeader("Content-Type", "") - .build(); - - final Request.Builder requestBuilder = new Request.Builder() - .url(DUMMY_URL); - - final FhirRequestBuilder fhirRequestBuilder = Mockito.spy(new FhirRequestBuilder(requestBuilder, "http://local/local")); - - @Mock - OkHttpClient client; + @Test + @DisplayName("Test resource format headers are added correctly (GET).") + void addResourceFormatHeadersGET() { + //FIXME tested here. Should get list of HTTPHeader. + String testFormat = "yaml"; + HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com").withMethod(HTTPRequest.HttpMethod.GET); - @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 + "."); - public FhirRequestBuilderTests() throws MalformedURLException { + Assertions.assertNull(headersMap.get("Content-Type"), "Content-Type header not null."); } - @BeforeEach - public void beforeEach() { - Mockito.doReturn(client).when(fhirRequestBuilder).getHttpClient(); - fhirRequestBuilder.withLogger(logger); + @Test + @DisplayName("Test resource format headers are added correctly (POST).") + void addResourceFormatHeadersPOST() { + //FIXME tested here. Should get list of HTTPHeader. + 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 + "\"."); } - @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())); } + } From c5aaf431212b7d62362ed2b771a5cd651648822f Mon Sep 17 00:00:00 2001 From: dotasek Date: Wed, 30 Oct 2024 19:08:38 -0400 Subject: [PATCH 18/42] Make sure we get response headers --- .../utilities/http/ManagedFhirWebAccessBuilder.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java index d3f612f94b..78ec0ca81d 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java @@ -141,7 +141,15 @@ public HTTPResult httpCall(HTTPRequest httpRequest) throws IOException { } 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().bytes() : null); + return new HTTPResult(execute.request().url().toString(), execute.code(), execute.message(), execute.header("Content-Type"), execute.body() != null ? 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() { From a76078a5a55c41976213d9e4db1a8f2cb44cfed7 Mon Sep 17 00:00:00 2001 From: dotasek Date: Fri, 1 Nov 2024 09:12:37 -0400 Subject: [PATCH 19/42] Add test coverage (some failing) for DSTU2 ClientUtils --- org.hl7.fhir.dstu2/pom.xml | 24 ++ .../fhir/dstu2/utils/client/ClientUtils.java | 106 ++---- .../dstu2/utils/client/FHIRToolingClient.java | 260 +------------- .../dstu2/utils/client/ResourceRequest.java | 15 +- .../dstu2/utils/client/ClientUtilsTest.java | 330 ++++++++++++++++++ 5 files changed, 392 insertions(+), 343 deletions(-) create mode 100644 org.hl7.fhir.dstu2/src/test/java/org/hl7/fhir/dstu2/utils/client/ClientUtilsTest.java 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..df72116cd0 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 @@ -48,6 +48,8 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS import java.util.Locale; import java.util.Map; +import lombok.Getter; +import lombok.Setter; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; @@ -98,47 +100,37 @@ public class ClientUtils { public static final String HEADER_LOCATION = "location"; private static boolean debugging = false; + @Getter + @Setter private HttpHost proxy; - 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; - } + @Getter + @Setter + private int timeout = 5000; - public void setTimeout(int timeout) { - this.timeout = timeout; - } + @Getter + @Setter + private String username; - public String getUsername() { - return username; - } + @Getter + @Setter + private String password; - public void setUsername(String username) { - this.username = username; - } + @Setter + @Getter + private ToolingClientLogger logger; - public String getPassword() { - return password; - } + @Setter + @Getter + private int retryCount; - public void setPassword(String password) { - this.password = password; - } + @Getter + @Setter + private String userAgent; + @Setter + private String acceptLanguage; + @Setter + private String contentLanguage; public ResourceRequest issueOptionsRequest(URI optionsUri, String resourceFormat, int timeoutLoading) { @@ -247,7 +239,6 @@ protected ResourceRequest issueResourceRequest(String re /** * @param resourceFormat - * @param options * @return */ protected ResourceRequest issueResourceRequest(String resourceFormat, HttpUriRequest request, @@ -257,7 +248,6 @@ protected ResourceRequest issueResourceRequest(String re /** * @param resourceFormat - * @param options * @return */ protected ResourceRequest issueResourceRequest(String resourceFormat, HttpUriRequest request, @@ -296,11 +286,11 @@ protected void configureFhirRequest(HttpRequest request, String format, List 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,101 +425,6 @@ 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 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..ac8c0ec7c2 --- /dev/null +++ b/org.hl7.fhir.dstu2/src/test/java/org/hl7/fhir/dstu2/utils/client/ClientUtilsTest.java @@ -0,0 +1,330 @@ +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() { + /* FIXME restore this after refactor + 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() { + /*FIXME restore this after refactor + 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 + public void testResourceFormat() { + ResourceFormat format = ResourceFormat.RESOURCE_XML; + assertThat(format.getHeader()).isEqualTo("application/xml+fhir"); + format = ResourceFormat.RESOURCE_JSON; + assertThat(format.getHeader()).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); + resourceRequest.addSuccessStatus(200); + assertThat(resourceRequest.getLocation()).isEqualTo(DUMMY_LOCATION); + assertTrue(resourceRequest.isSuccessfulRequest()); + assertTrue(patient.equalsDeep(resourceRequest.getPayload())); + } + + @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); + resourceRequest.addSuccessStatus(200); + assertThat(resourceRequest.getLocation()).isEqualTo(DUMMY_LOCATION); + assertTrue(resourceRequest.isSuccessfulRequest()); + assertTrue(patient.equalsDeep(resourceRequest.getPayload())); + } + + @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); + resourceRequest.addSuccessStatus(200); + assertThat(resourceRequest.getLocation()).isNull(); + assertTrue(resourceRequest.isSuccessfulRequest()); + assertTrue(patient.equalsDeep(resourceRequest.getPayload())); + } + + @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); + 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); + 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); + } +} From 11a11da7c3f618d8117e3b92c3386bdd1465920a Mon Sep 17 00:00:00 2001 From: dotasek Date: Fri, 1 Nov 2024 12:40:48 -0400 Subject: [PATCH 20/42] Add FHIRToolingClient tests --- .../dstu2/utils/client/FHIRToolingClient.java | 15 +- .../dstu2/utils/client/ResourceAddress.java | 5 +- .../dstu2/utils/client/ClientUtilsTest.java | 50 ++++-- .../utils/client/FHIRToolingClientTest.java | 167 ++++++++++++++++++ .../utils/client/FhirToolingClientTest.java | 1 - 5 files changed, 215 insertions(+), 23 deletions(-) create mode 100644 org.hl7.fhir.dstu2/src/test/java/org/hl7/fhir/dstu2/utils/client/FHIRToolingClientTest.java 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 c62f1cb75d..f21856d948 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,12 +95,17 @@ public class FHIRToolingClient extends FHIRBaseToolingClient { private HttpHost proxy; private int maxResultSetSize = -1;// _count private Conformance conf; - private ClientUtils utils = new ClientUtils(); + private ClientUtils utils = null; private int useCount; + protected ClientUtils getClientUtils() { + return new ClientUtils(); + } + // Pass enpoint for client - URI public FHIRToolingClient(String baseServiceUrl, String userAgent) throws URISyntaxException { preferredResourceFormat = ResourceFormat.RESOURCE_XML; + utils = getClientUtils(); utils.setUserAgent(userAgent); detectProxy(); initialize(baseServiceUrl); @@ -109,6 +114,7 @@ public FHIRToolingClient(String baseServiceUrl, String userAgent) throws URISynt public FHIRToolingClient(String baseServiceUrl, String userAgent, String username, String password) throws URISyntaxException { preferredResourceFormat = ResourceFormat.RESOURCE_XML; + utils = getClientUtils(); utils.setUserAgent(userAgent); utils.setUsername(username); utils.setPassword(password); @@ -324,9 +330,12 @@ public T update(Class resourceClass, T resource, String 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"), + headers, + timeoutOperation); result.addErrorStatus(410);// gone result.addErrorStatus(404);// unknown result.addErrorStatus(405); 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/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 index ac8c0ec7c2..72ed5bebe6 100644 --- 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 @@ -100,11 +100,29 @@ void addResourceFormatHeadersPOST() { } @Test - public void testResourceFormat() { - ResourceFormat format = ResourceFormat.RESOURCE_XML; - assertThat(format.getHeader()).isEqualTo("application/xml+fhir"); - format = ResourceFormat.RESOURCE_JSON; - assertThat(format.getHeader()).isEqualTo("application/json+fhir"); + 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 @@ -142,10 +160,8 @@ void testIssueGetResourceRequest_withContentLocation() throws IOException { .addHeader("Content-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 @@ -159,10 +175,7 @@ void testIssueGetResourceRequest_ReturnsLocationHeaderWhenBothSet() throws IOExc .addHeader("Content-Location", "Wrong wrong wrong") ); 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 @@ -174,17 +187,18 @@ void testIssueGetResourceRequest_ReturnsNullWhenNoHeadersSet() throws IOExceptio .addHeader("Content-Type", "application/json+fhir") ); ResourceRequest resourceRequest = clientUtils.issueGetResourceRequest(serverUrl.uri(), "application/json+fhir", TIMEOUT); - resourceRequest.addSuccessStatus(200); assertThat(resourceRequest.getLocation()).isNull(); - assertTrue(resourceRequest.isSuccessfulRequest()); - assertTrue(patient.equalsDeep(resourceRequest.getPayload())); } @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) + 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); @@ -201,7 +215,11 @@ public void testIssuePostRequest() throws IOException, InterruptedException { 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) + 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); 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..9386d2d6d0 --- /dev/null +++ b/org.hl7.fhir.dstu2/src/test/java/org/hl7/fhir/dstu2/utils/client/FHIRToolingClientTest.java @@ -0,0 +1,167 @@ +package org.hl7.fhir.dstu2.utils.client; + +import org.hl7.fhir.dstu2.model.*; +import org.hl7.fhir.utilities.http.HTTPHeader; +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.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 index 1fdc645fb1..43de724c14 100644 --- 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 @@ -33,7 +33,6 @@ public class FhirToolingClientTest { @Captor private ArgumentCaptor> headersArgumentCaptor; - @BeforeEach void setUp() throws IOException, URISyntaxException { MockitoAnnotations.openMocks(this); From ac7c7332d3ad504c2b86ffcb3a23a3df0409c905 Mon Sep 17 00:00:00 2001 From: dotasek Date: Mon, 4 Nov 2024 17:49:31 -0500 Subject: [PATCH 21/42] Use ManagedFhirWebAccess for DSTU2 --- .../fhir/dstu2/utils/client/ClientUtils.java | 351 +++++++----------- .../dstu2/utils/client/FHIRToolingClient.java | 38 +- .../dstu2/utils/client/ClientUtilsTest.java | 2 + .../utils/client/FHIRToolingClientTest.java | 1 - .../http/ManagedFhirWebAccessBuilder.java | 8 +- .../hl7/fhir/validation/special/TxTester.java | 7 +- 6 files changed, 155 insertions(+), 252 deletions(-) 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 df72116cd0..18a790b096 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 @@ -41,36 +41,16 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS import java.nio.charset.StandardCharsets; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.Map; +import java.util.*; +import java.util.concurrent.TimeUnit; import lombok.Getter; import lombok.Setter; + import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.http.Header; -import org.apache.http.HttpEntityEnclosingRequest; -import org.apache.http.HttpHost; -import org.apache.http.HttpRequest; -import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpOptions; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.conn.params.ConnRoutePNames; -import org.apache.http.entity.ByteArrayEntity; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.params.HttpConnectionParams; -import org.apache.http.params.HttpParams; + import org.hl7.fhir.dstu2.formats.IParser; import org.hl7.fhir.dstu2.formats.IParser.OutputStyle; import org.hl7.fhir.dstu2.formats.JsonParser; @@ -86,8 +66,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. @@ -95,15 +78,17 @@ 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; +/* @Getter @Setter private HttpHost proxy; - +*/ @Getter @Setter private int timeout = 5000; @@ -131,6 +116,11 @@ public class ClientUtils { private String acceptLanguage; @Setter private String contentLanguage; + private final TimeUnit timeoutUnit = TimeUnit.MILLISECONDS; + + protected ManagedFhirWebAccessBuilder getManagedWebAccessBuilder() { + return new ManagedFhirWebAccessBuilder("hapi-fhir-tooling-client", null).withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); + } public ResourceRequest issueOptionsRequest(URI optionsUri, String resourceFormat, int timeoutLoading) { @@ -138,8 +128,13 @@ public ResourceRequest issueOptionsRequest(URI optionsUr throw new FHIRException("Network Access is prohibited in this context"); } + /*FIXME delete this after refactor 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, @@ -147,17 +142,25 @@ public ResourceRequest issueGetResourceRequest(URI resou if (FhirSettings.isProhibitNetworkAccess()) { throw new FHIRException("Network Access is prohibited in this context"); } + /*FIXME delete this after refactor 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, @@ -165,17 +168,29 @@ public ResourceRequest issuePutRequest(URI resourceUri, if (FhirSettings.isProhibitNetworkAccess()) { throw new FHIRException("Network Access is prohibited in this context"); } + /*FIXME delete this after refactor 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"); } + /*FIXME delete this after refactor 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, @@ -187,30 +202,42 @@ public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat) { if (FhirSettings.isProhibitNetworkAccess()) { throw new FHIRException("Network Access is prohibited in this context"); } + /*FIXME delete this after refactor HttpGet httpget = new HttpGet(resourceUri); - configureFhirRequest(httpget, resourceFormat); - HttpResponse response = sendRequest(httpget); + */ + HTTPRequest httpRequest = new HTTPRequest() + .withMethod(HTTPRequest.HttpMethod.GET) + .withUrl(resourceUri.toString()); + Iterable headers = getFhirHeaders(resourceFormat); + HTTPResult response = sendRequest(httpRequest.withHeaders(headers)); return unmarshalReference(response, resourceFormat); } - private void setAuth(HttpRequest httpget) { + private Iterable getAuthHeaders() { 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); + return Arrays.asList(new HTTPHeader[]{new HTTPHeader("Authorization", "Basic " + b64)}); } catch (UnsupportedEncodingException e) { } } + return Collections.emptyList(); } public Bundle postBatchRequest(URI resourceUri, byte[] payload, String resourceFormat, int timeoutLoading) { if (FhirSettings.isProhibitNetworkAccess()) { throw new FHIRException("Network Access is prohibited in this context"); } + /*FIXME delete this after refactor 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(resourceFormat); + HTTPResult response = sendPayload(httpRequest.withHeaders(headers)); return unmarshalFeed(response, resourceFormat); } @@ -218,9 +245,14 @@ public boolean issueDeleteRequest(URI resourceUri) { if (FhirSettings.isProhibitNetworkAccess()) { throw new FHIRException("Network Access is prohibited in this context"); } + /*FIXME delete this after refactor 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; @@ -232,123 +264,68 @@ 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 * @return */ - protected ResourceRequest issueResourceRequest(String resourceFormat, HttpUriRequest request, - byte[] payload, int timeoutLoading) { - return issueResourceRequest(resourceFormat, request, payload, null, timeoutLoading); - } - - /** - * @param resourceFormat - * @return - */ - 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(resourceFormat, headers); + try { + + HTTPResult response = getManagedWebAccessBuilder().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. * - * @param request + * @param format */ - protected void configureFhirRequest(HttpRequest request, String format) { - configureFhirRequest(request, format, null); + protected Iterable getFhirHeaders(String format) { + return getFhirHeaders(format, null); } /** * Method adds required request headers. TODO handle JSON request as well. * - * @param request + * @param format */ - protected void configureFhirRequest(HttpRequest request, String format, List
    headers) { + protected Iterable getFhirHeaders(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(acceptLanguage)) { - request.addHeader("Accept-Language", acceptLanguage); + configuredHeaders.add(new HTTPHeader("Accept-Language", acceptLanguage)); } if (!Utilities.noString(contentLanguage)) { - request.addHeader("Content-Language", acceptLanguage); + configuredHeaders.add(new HTTPHeader("Content-Language", acceptLanguage)); } if (format != null) { - request.addHeader("Accept", format); - request.addHeader("Content-Type", format + ";charset=" + DEFAULT_CHARSET); + configuredHeaders.add(new HTTPHeader("Accept", format)); + configuredHeaders.add(new HTTPHeader("Content-Type", format + ";charset=" + DEFAULT_CHARSET)); } - if (headers != null) { - for (Header header : headers) { - request.addHeader(header); - } - } - setAuth(request); - } + Iterable authHeaders = getAuthHeaders(); + authHeaders.forEach(configuredHeaders::add); - /** - * Method posts request payload - * - * @param request - * @param payload - * @return - */ - @SuppressWarnings({ "resource", "deprecation" }) - protected HttpResponse sendPayload(HttpEntityEnclosingRequestBase request, byte[] payload, HttpHost proxy, - int timeoutLoading) { - if (FhirSettings.isProhibitNetworkAccess()) { - throw new FHIRException("Network Access is prohibited in this context"); - } - HttpResponse response = null; - boolean ok = false; - long t = System.currentTimeMillis(); - int tryCount = 0; - while (!ok) { - try { - tryCount++; - HttpClient httpclient = new DefaultHttpClient(); - HttpParams params = httpclient.getParams(); - HttpConnectionParams.setConnectionTimeout(params, timeout); - HttpConnectionParams.setSoTimeout(params, timeout * timeoutLoading); - - if (proxy != null) { - httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); - } - request.setEntity(new ByteArrayEntity(payload)); - log(request); - response = httpclient.execute(request); - ok = true; - } catch (IOException ioe) { - System.out.println(ioe.getMessage() + " (" + (System.currentTimeMillis() - t) + "ms / " - + Utilities.describeSize(payload.length) + ")"); - if (tryCount <= retryCount || (tryCount < 3 && ioe instanceof org.apache.http.conn.ConnectTimeoutException)) { - ok = false; - } else { - throw new EFhirClientException("Error sending HTTP Post/Put Payload to " + "??" + ": " + ioe.getMessage(), - ioe); - } - } + if (headers != null) { + headers.forEach(configuredHeaders::add); } - return response; + return configuredHeaders; } /** @@ -356,28 +333,21 @@ protected HttpResponse sendPayload(HttpEntityEnclosingRequestBase request, byte[ * @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 = getManagedWebAccessBuilder().httpCall(request); + return response; } catch (IOException ioe) { if (ClientUtils.debugging) { ioe.printStackTrace(); } throw new EFhirClientException("Error sending Http Request: " + ioe.getMessage(), ioe); } - return response; } /** @@ -387,13 +357,12 @@ protected HttpResponse sendRequest(HttpUriRequest request) { * @return */ @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; } @@ -415,15 +384,15 @@ protected T unmarshalReference(HttpResponse response, Strin * @param response * @return */ - 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)) { @@ -451,14 +420,13 @@ protected boolean hasError(OperationOutcome oo) { 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); } /***************************************************************** @@ -575,12 +543,19 @@ protected IParser getParser(String format) { public Bundle issuePostFeedRequest(URI resourceUri, Map parameters, String resourceName, Resource resource, String resourceFormat) throws IOException { + /*FIXME delete this after refactor HttpPost httppost = new HttpPost(resourceUri); + */ + + HTTPRequest httppost = 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(null); + HTTPResult response = sendPayload(httppost.withBody(encodeFormSubmission(parameters, resourceName, resource, boundary))); return unmarshalFeed(response, resourceFormat); } @@ -614,69 +589,19 @@ private byte[] encodeFormSubmission(Map parameters, String resou * Method posts request payload * * @param request - * @param payload * @return */ - 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 = getManagedWebAccessBuilder().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; - } - /** * Used for debugging * 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 f21856d948..7460d806c3 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 @@ -107,7 +107,6 @@ public FHIRToolingClient(String baseServiceUrl, String userAgent) throws URISynt preferredResourceFormat = ResourceFormat.RESOURCE_XML; utils = getClientUtils(); utils.setUserAgent(userAgent); - detectProxy(); initialize(baseServiceUrl); } @@ -118,30 +117,12 @@ public FHIRToolingClient(String baseServiceUrl, String userAgent, String usernam 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; @@ -292,11 +273,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); @@ -329,12 +310,11 @@ public T update(Class resourceClass, T resource, String recordUse(); ResourceRequest result = null; try { - List
    headers = null; result = utils.issuePutRequest( resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id), utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())), withVer(getPreferredResourceFormat(), "1.0"), - headers, + null, timeoutOperation); result.addErrorStatus(410);// gone result.addErrorStatus(404);// unknown @@ -535,13 +515,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); @@ -563,11 +543,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); @@ -586,11 +566,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); 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 index 72ed5bebe6..cbae5f1af5 100644 --- 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 @@ -319,6 +319,7 @@ void test_get_retries_with_timeout() throws IOException, URISyntaxException { 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."); @@ -336,6 +337,7 @@ void test_get_retries_with_unsuccessful_response() throws IOException, URISyntax 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."); 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 index 9386d2d6d0..587fdb9f29 100644 --- 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 @@ -1,7 +1,6 @@ package org.hl7.fhir.dstu2.utils.client; import org.hl7.fhir.dstu2.model.*; -import org.hl7.fhir.utilities.http.HTTPHeader; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.*; diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java index 78ec0ca81d..b5ca2ecd4f 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java @@ -75,11 +75,11 @@ protected HTTPRequest requestWithManagedHeaders(HTTPRequest httpRequest) { headers.add(new HTTPHeader("Authorization", basicCredential)); break; case TOKEN: - String tokenCredential = "Bearer " + new String(getToken()); + String tokenCredential = "Bearer " + getToken(); headers.add(new HTTPHeader("Authorization", tokenCredential)); break; case APIKEY: - String apiKeyCredential = " " + new String(getToken()); + String apiKeyCredential = getToken(); headers.add(new HTTPHeader("Api-Key", apiKeyCredential)); break; } @@ -97,7 +97,7 @@ protected HTTPRequest requestWithManagedHeaders(HTTPRequest httpRequest) { headers.add(new HTTPHeader("Authorization", tokenCredential)); break; case "apikey": - String apiKeyCredential = new String(settings.getToken()); + String apiKeyCredential = settings.getToken(); headers.add(new HTTPHeader("Api-Key", apiKeyCredential)); break; } @@ -141,7 +141,7 @@ public HTTPResult httpCall(HTTPRequest httpRequest) throws IOException { } 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().bytes() : null, getHeadersFromResponse(execute)); + 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) { 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 fe9eefa49f..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 @@ -39,10 +39,7 @@ 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.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; @@ -130,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 { From f1d5f714d351a62a5879dea7499716b1d612f56b Mon Sep 17 00:00:00 2001 From: dotasek Date: Tue, 5 Nov 2024 09:08:40 -0500 Subject: [PATCH 22/42] WIP restore format header tests - turns out they break --- .../hl7/fhir/dstu2/utils/client/ClientUtils.java | 15 +++++++++++---- .../fhir/dstu2/utils/client/ClientUtilsTest.java | 16 ++++++++-------- 2 files changed, 19 insertions(+), 12 deletions(-) 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 18a790b096..3c635b263b 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 @@ -315,10 +315,9 @@ protected Iterable getFhirHeaders(String format, Iterable headers = FhirRequestBuilder.getResourceFormatHeaders(request, testFormat); + Iterable headers = ClientUtils.getResourceFormatHeaders(testFormat); Map> headersMap = HTTPHeaderUtil.getMultimap(headers); Assertions.assertNotNull(headersMap.get("Accept"), "Accept header null."); @@ -75,17 +75,17 @@ void addResourceFormatHeadersGET() { Assertions.assertNull(headersMap.get("Content-Type"), "Content-Type header null."); - */ + } @Test @DisplayName("Test resource format headers are added correctly (POST).") void addResourceFormatHeadersPOST() { - /*FIXME restore this after refactor + String testFormat = "yaml"; HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com").withMethod(HTTPRequest.HttpMethod.POST); - Iterable headers = FhirRequestBuilder.getResourceFormatHeaders(request, testFormat); + Iterable headers = ClientUtils.getResourceFormatHeaders(testFormat); Map> headersMap = HTTPHeaderUtil.getMultimap(headers); Assertions.assertNotNull(headersMap.get("Accept"), "Accept header null."); @@ -93,10 +93,10 @@ void addResourceFormatHeadersPOST() { "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.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 From 07b31d94fd6e9d89053fc83bc496b90323ec681c Mon Sep 17 00:00:00 2001 From: dotasek Date: Tue, 5 Nov 2024 10:20:06 -0500 Subject: [PATCH 23/42] Fix format header issues --- .../fhir/dstu2/utils/client/ClientUtils.java | 37 +++++++++++-------- .../dstu2/utils/client/ClientUtilsTest.java | 4 +- 2 files changed, 23 insertions(+), 18 deletions(-) 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 3c635b263b..32cdcf79d2 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 @@ -208,7 +208,7 @@ public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat) { HTTPRequest httpRequest = new HTTPRequest() .withMethod(HTTPRequest.HttpMethod.GET) .withUrl(resourceUri.toString()); - Iterable headers = getFhirHeaders(resourceFormat); + Iterable headers = getFhirHeaders(httpRequest, resourceFormat); HTTPResult response = sendRequest(httpRequest.withHeaders(headers)); return unmarshalReference(response, resourceFormat); } @@ -236,7 +236,7 @@ public Bundle postBatchRequest(URI resourceUri, byte[] payload, String resourceF .withMethod(HTTPRequest.HttpMethod.POST) .withUrl(resourceUri.toString()) .withBody(payload); - Iterable headers = getFhirHeaders(resourceFormat); + Iterable headers = getFhirHeaders(httpRequest, resourceFormat); HTTPResult response = sendPayload(httpRequest.withHeaders(headers)); return unmarshalFeed(response, resourceFormat); } @@ -277,7 +277,7 @@ protected ResourceRequest issueResourceRequest(String re if (FhirSettings.isProhibitNetworkAccess()) { throw new FHIRException("Network Access is prohibited in this context"); } - Iterable configuredHeaders = getFhirHeaders(resourceFormat, headers); + Iterable configuredHeaders = getFhirHeaders(request, resourceFormat, headers); try { HTTPResult response = getManagedWebAccessBuilder().httpCall(request.withHeaders(configuredHeaders)); @@ -294,8 +294,8 @@ protected ResourceRequest issueResourceRequest(String re * * @param format */ - protected Iterable getFhirHeaders(String format) { - return getFhirHeaders(format, null); + protected Iterable getFhirHeaders(HTTPRequest httpRequest, String format) { + return getFhirHeaders(httpRequest, format, null); } /** @@ -303,7 +303,7 @@ protected Iterable getFhirHeaders(String format) { * * @param format */ - protected Iterable getFhirHeaders(String format, Iterable headers) { + protected Iterable getFhirHeaders(HTTPRequest httpRequest, String format, Iterable headers) { List configuredHeaders = new ArrayList<>(); if (!Utilities.noString(userAgent)) { configuredHeaders.add(new HTTPHeader("User-Agent", userAgent)); @@ -315,7 +315,7 @@ protected Iterable getFhirHeaders(String format, Iterable resourceFormatHeaders = getResourceFormatHeaders(format); + Iterable resourceFormatHeaders = getResourceFormatHeaders(httpRequest, format); resourceFormatHeaders.forEach(configuredHeaders::add); Iterable authHeaders = getAuthHeaders(); @@ -328,11 +328,16 @@ protected Iterable getFhirHeaders(String format, Iterable getResourceFormatHeaders(String format) { - return Arrays.asList( - new HTTPHeader("Accept", format), - new HTTPHeader("Content-Type", format + ";charset=" + DEFAULT_CHARSET) - ); + 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)); + } + return headers; } /** @@ -551,18 +556,18 @@ protected IParser getParser(String format) { public Bundle issuePostFeedRequest(URI resourceUri, Map parameters, String resourceName, Resource resource, String resourceFormat) throws IOException { /*FIXME delete this after refactor - HttpPost httppost = new HttpPost(resourceUri); + HttpPost httpRequest = new HttpPost(resourceUri); */ - HTTPRequest httppost = new HTTPRequest() + HTTPRequest httpRequest = new HTTPRequest() .withMethod(HTTPRequest.HttpMethod.POST) .withUrl(resourceUri.toString()); String boundary = "----WebKitFormBoundarykbMUo6H8QaUnYtRy"; List headers = new ArrayList<>(); headers.add(new HTTPHeader("Content-Type", "multipart/form-data; boundary=" + boundary)); headers.add(new HTTPHeader("Accept", resourceFormat)); - this.getFhirHeaders(null); - HTTPResult response = sendPayload(httppost.withBody(encodeFormSubmission(parameters, resourceName, resource, boundary))); + this.getFhirHeaders(httpRequest, null); + HTTPResult response = sendPayload(httpRequest.withBody(encodeFormSubmission(parameters, resourceName, resource, boundary))); return unmarshalFeed(response, resourceFormat); } 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 index a9dc45265c..f659af3121 100644 --- 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 @@ -66,7 +66,7 @@ void addResourceFormatHeadersGET() { String testFormat = "yaml"; HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com").withMethod(HTTPRequest.HttpMethod.GET); - Iterable headers = ClientUtils.getResourceFormatHeaders(testFormat); + Iterable headers = ClientUtils.getResourceFormatHeaders(request, testFormat); Map> headersMap = HTTPHeaderUtil.getMultimap(headers); Assertions.assertNotNull(headersMap.get("Accept"), "Accept header null."); @@ -85,7 +85,7 @@ void addResourceFormatHeadersPOST() { String testFormat = "yaml"; HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com").withMethod(HTTPRequest.HttpMethod.POST); - Iterable headers = ClientUtils.getResourceFormatHeaders(testFormat); + Iterable headers = ClientUtils.getResourceFormatHeaders(request, testFormat); Map> headersMap = HTTPHeaderUtil.getMultimap(headers); Assertions.assertNotNull(headersMap.get("Accept"), "Accept header null."); From b6ef81c9f37b5b5f3a23f74e0d0eede3e4c1a0c8 Mon Sep 17 00:00:00 2001 From: dotasek Date: Wed, 6 Nov 2024 09:47:27 -0500 Subject: [PATCH 24/42] Switch to static fhirBuilder for all FhirRequestBuilders + Fix null user agent issue + Load ManagedWebAccess from FHIR settings + Add terminology servers to FhirSettings --- .../fhir/dstu2/utils/client/ClientUtils.java | 2 +- .../client/network/FhirRequestBuilder.java | 2 +- .../client/network/FhirRequestBuilder.java | 2 +- .../client/network/FhirRequestBuilder.java | 2 +- .../client/network/FhirRequestBuilder.java | 53 +------------------ .../client/ManagedWebAccessAuthTests.java | 11 ++++ .../http/ManagedFhirWebAccessBuilder.java | 3 +- .../fhir/utilities/http/ManagedWebAccess.java | 24 +++++++-- .../fhir/utilities/settings/FhirSettings.java | 11 +++- .../org/hl7/fhir/validation/ValidatorCli.java | 2 + .../test/java/ManagedWebAccessAuthTests.java | 9 ++++ 11 files changed, 59 insertions(+), 62 deletions(-) create mode 100644 org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/ManagedWebAccessAuthTests.java create mode 100644 org.hl7.fhir.validation/src/test/java/ManagedWebAccessAuthTests.java 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 32cdcf79d2..52059fd746 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 @@ -119,7 +119,7 @@ public class ClientUtils { private final TimeUnit timeoutUnit = TimeUnit.MILLISECONDS; protected ManagedFhirWebAccessBuilder getManagedWebAccessBuilder() { - return new ManagedFhirWebAccessBuilder("hapi-fhir-tooling-client", null).withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); + return ManagedWebAccess.fhirBuilder().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); } public ResourceRequest issueOptionsRequest(URI optionsUri, String resourceFormat, 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 86f5b98062..d632679490 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 @@ -115,7 +115,7 @@ protected static String getLocationHeader(Iterable headers) { } protected ManagedFhirWebAccessBuilder getManagedWebAccessBuilder() { - return new ManagedFhirWebAccessBuilder("hapi-fhir-tooling-client", null).withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); + return ManagedWebAccess.fhirBuilder().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); } public FhirRequestBuilder withResourceFormat(String resourceFormat) { 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 e849e15f2f..29d2a6d4c2 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 @@ -125,7 +125,7 @@ protected static String getLocationHeader(Iterable headers) { } protected ManagedFhirWebAccessBuilder getManagedWebAccessBuilder() { - return new ManagedFhirWebAccessBuilder("hapi-fhir-tooling-client", null).withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); + return ManagedWebAccess.fhirBuilder().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); } public FhirRequestBuilder withResourceFormat(String resourceFormat) { 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 b0b2e94bbf..47a45c691c 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 @@ -127,7 +127,7 @@ protected static String getLocationHeader(Iterable headers) { } protected ManagedFhirWebAccessBuilder getManagedWebAccessBuilder() { - return new ManagedFhirWebAccessBuilder("hapi-fhir-tooling-client", null).withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); + return ManagedWebAccess.fhirBuilder().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); } public FhirRequestBuilder withResourceFormat(String resourceFormat) { 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 e42b857935..eeb28b1be6 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 @@ -113,59 +113,10 @@ protected static boolean hasError(OperationOutcome oo) { || issue.getSeverity() == OperationOutcome.IssueSeverity.FATAL)); } - - protected ManagedFhirWebAccessBuilder getManagedWebAccessBuilder() { - return new ManagedFhirWebAccessBuilder("hapi-fhir-tooling-client", null).withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); + return ManagedWebAccess.fhirBuilder().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); } - /** - * 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 - */ - /*FIXME delete after refactor - protected OkHttpClient getHttpClient() { - - if (okHttpClient == null) { - okHttpClient = new OkHttpClient(); - } - Authenticator proxyAuthenticator = getAuthenticator(); - - OkHttpClient.Builder builder = okHttpClient.newBuilder(); - if (logger != null) builder.addInterceptor(logger); - builder.addInterceptor(new RetryInterceptor(retryCount)); - return builder.connectTimeout(timeout, timeoutUnit) - .writeTimeout(timeout, timeoutUnit) - .readTimeout(timeout, timeoutUnit) - .proxyAuthenticator(proxyAuthenticator) - .build(); - } -*/ - /*FIXME delete after refactor - @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(); - }; - } -*/ public FhirRequestBuilder withResourceFormat(String resourceFormat) { this.resourceFormat = resourceFormat; return this; @@ -197,8 +148,6 @@ public FhirRequestBuilder withTimeout(long timeout, TimeUnit unit) { return this; } - - public ResourceRequest execute() throws IOException { HTTPRequest requestWithHeaders = formatHeaders(httpRequest, resourceFormat, headers); HTTPResult response = getManagedWebAccessBuilder().httpCall(requestWithHeaders);//getHttpClient().newCall(httpRequest.build()).execute(); diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/ManagedWebAccessAuthTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/ManagedWebAccessAuthTests.java new file mode 100644 index 0000000000..8198a614c4 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/ManagedWebAccessAuthTests.java @@ -0,0 +1,11 @@ +package org.hl7.fhir.r5.utils.client; + +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/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java index b5ca2ecd4f..5b3ff01d02 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java @@ -50,7 +50,8 @@ public ManagedFhirWebAccessBuilder(String userAgent, List ser protected HTTPRequest httpRequestWithDefaultHeaders(HTTPRequest request) { List headers = new ArrayList<>(); - if (HTTPHeaderUtil.getSingleHeader(request.getHeaders(), HTTPHeaderUtil.USER_AGENT) == null) { + if (HTTPHeaderUtil.getSingleHeader(request.getHeaders(), HTTPHeaderUtil.USER_AGENT) == null + && getUserAgent() != null) { headers.add(new HTTPHeader(HTTPHeaderUtil.USER_AGENT, getUserAgent())); } request.getHeaders().forEach(headers::add); 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 b9d818f5c1..91776bf275 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 @@ -32,16 +32,15 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; import lombok.Getter; import okhttp3.Response; +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), @@ -77,6 +76,8 @@ public enum WebAccessPolicy { @Getter private static IFhirWebAccessor fhirWebAccessor; + + @Getter private static String userAgent; private static List serverAuthDetails; @@ -133,4 +134,19 @@ public static HTTPResult httpCall(HTTPRequest httpRequest) throws IOException { return fhirBuilder().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/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.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/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"); + } +} From 0c4da028b3e976ef42451ac832d18134ff6d6d47 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 7 Nov 2024 22:14:49 +1030 Subject: [PATCH 25/42] fix slicing by type and profile to allow multiple options per slice --- .../hl7/fhir/r5/fhirpath/FHIRPathEngine.java | 11 ++++- .../fhir/utilities/i18n/I18nConstants.java | 7 +++- .../src/main/resources/Messages.properties | 12 +++--- .../instance/InstanceValidator.java | 42 ++++++++++--------- 4 files changed, 43 insertions(+), 29 deletions(-) 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.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/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.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)); From ee7dc203f6b61f7445af9223e4f9710cc557ae8d Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 7 Nov 2024 22:15:54 +1030 Subject: [PATCH 26/42] List measure choices when a match by version can't be found --- .../validation/instance/type/MeasureValidator.java | 11 +++++++++++ 1 file changed, 11 insertions(+) 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; From a93ab69231c827b7e554e74f66153451c40c7808 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 7 Nov 2024 22:16:27 +1030 Subject: [PATCH 27/42] Validate fhirpath expression in slice discriminators --- RELEASE_NOTES.md | 3 ++ .../r4/utils/client/FHIRToolingClient.java | 2 + .../type/StructureDefinitionValidator.java | 46 ++++++++++++++++++- 3 files changed, 50 insertions(+), 1 deletion(-) 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.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 563a268f80..5649cf23fb 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 @@ -563,6 +563,8 @@ private Iterable generateHeaders(boolean hasBody) { headers.add(new HTTPHeader("Content-Language",contentLanguage)); } + headers.add(new HTTPHeader("Host", "cts.nlm.nih.gov")); + return headers; } 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)) { From 2c57f5954e06768296fabf9d7dafd1f5bdabbb66 Mon Sep 17 00:00:00 2001 From: dotasek Date: Thu, 7 Nov 2024 15:44:25 -0500 Subject: [PATCH 28/42] Remove conflicting authorization header setting methods --- .../fhir/convertors/misc/VSACImporter.java | 3 +- .../fhir/dstu2/utils/client/ClientUtils.java | 24 -------- .../dstu2/utils/client/FHIRToolingClient.java | 30 ---------- .../dstu3/utils/client/FHIRToolingClient.java | 60 +++---------------- .../r4/utils/client/FHIRToolingClient.java | 24 +------- .../r4b/utils/client/FHIRToolingClient.java | 26 +------- .../r5/utils/client/FHIRToolingClient.java | 24 +------- .../fhir/utilities/http/ManagedWebAccess.java | 2 - .../http/ManagedWebAccessBuilderBase.java | 18 ++++++ 9 files changed, 31 insertions(+), 180 deletions(-) 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..5ca4918aae 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 @@ -54,8 +54,7 @@ private void process(String source, String dest, String apiKey, boolean onlyNew, Map errs = new HashMap<>(); FHIRToolingClient fhirToolingClient = new FHIRToolingClient("https://cts.nlm.nih.gov/fhir", "fhir/vsac"); - fhirToolingClient.setUsername("apikey"); - fhirToolingClient.setPassword(apiKey); + fhirToolingClient.setTimeoutNormal(30000); fhirToolingClient.setTimeoutExpand(30000); 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 52059fd746..35b3c70f75 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,12 +33,10 @@ 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.*; @@ -47,7 +45,6 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS import lombok.Getter; import lombok.Setter; -import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; @@ -93,13 +90,7 @@ public class ClientUtils { @Setter private int timeout = 5000; - @Getter - @Setter - private String username; - @Getter - @Setter - private String password; @Setter @Getter @@ -213,18 +204,6 @@ public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat) { return unmarshalReference(response, resourceFormat); } - private Iterable getAuthHeaders() { - if (password != null) { - try { - byte[] b = Base64.encodeBase64((username + ":" + password).getBytes("ASCII")); - String b64 = new String(b, StandardCharsets.US_ASCII); - return Arrays.asList(new HTTPHeader[]{new HTTPHeader("Authorization", "Basic " + b64)}); - } catch (UnsupportedEncodingException e) { - } - } - return Collections.emptyList(); - } - public Bundle postBatchRequest(URI resourceUri, byte[] payload, String resourceFormat, int timeoutLoading) { if (FhirSettings.isProhibitNetworkAccess()) { throw new FHIRException("Network Access is prohibited in this context"); @@ -318,9 +297,6 @@ protected Iterable getFhirHeaders(HTTPRequest httpRequest, String fo Iterable resourceFormatHeaders = getResourceFormatHeaders(httpRequest, format); resourceFormatHeaders.forEach(configuredHeaders::add); - Iterable authHeaders = getAuthHeaders(); - authHeaders.forEach(configuredHeaders::add); - if (headers != null) { headers.forEach(configuredHeaders::add); } 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 7460d806c3..eafabce2c4 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 @@ -110,20 +110,6 @@ public FHIRToolingClient(String baseServiceUrl, String userAgent) throws URISynt initialize(baseServiceUrl); } - public FHIRToolingClient(String baseServiceUrl, String userAgent, String username, String password) - throws URISyntaxException { - preferredResourceFormat = ResourceFormat.RESOURCE_XML; - utils = getClientUtils(); - utils.setUserAgent(userAgent); - utils.setUsername(username); - utils.setPassword(password); - initialize(baseServiceUrl); - } - - - - - public void initialize(String baseServiceUrl) throws URISyntaxException { base = baseServiceUrl; resourceAddress = new ResourceAddress(baseServiceUrl); @@ -592,22 +578,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.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 90c319d715..8e4706409c 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 @@ -6,6 +6,7 @@ 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; @@ -68,13 +69,16 @@ public class FHIRToolingClient extends FHIRBaseToolingClient { private CapabilityStatement capabilities; private Client client = new Client(); private List headers = new ArrayList<>(); - private String username; - private String password; + @Setter + @Getter private String userAgent; private EnumSet allowedVersions; + @Setter @Getter private String acceptLanguage; + @Setter private String contentLanguage; + @Getter private int useCount; //Pass endpoint for client - URI @@ -534,21 +538,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(); @@ -579,15 +568,9 @@ public void setClientHeaders(Iterable headers) { headers.forEach(this.headers::add); } - //FIXME should be in ManagedWebAccess? private Iterable generateHeaders(boolean hasBody) { - List headers = new ArrayList<>(); - // Add basic auth header if it exists - if (basicAuthHeaderExists()) { - headers.add(getAuthorizationHeader()); - } // Add any other headers - headers.addAll(this.headers); + List headers = new ArrayList<>(this.headers); if (!Utilities.noString(userAgent)) { headers.add(new HTTPHeader("User-Agent",userAgent)); } @@ -603,40 +586,11 @@ private Iterable generateHeaders(boolean hasBody) { return headers; } - public boolean basicAuthHeaderExists() { - return (username != null) && (password != null); - } - - public HTTPHeader getAuthorizationHeader() { - String usernamePassword = username + ":" + password; - String base64usernamePassword = Base64.getEncoder().encodeToString(usernamePassword.getBytes()); - return new HTTPHeader("Authorization", "Basic " + base64usernamePassword); - } - - public String getUserAgent() { - return userAgent; - } - - public void setUserAgent(String userAgent) { - this.userAgent = userAgent; - } - public String getServerVersion() { checkCapabilities(); return capabilities == null ? null : capabilities.getSoftware().getVersion(); } - public void setAcceptLanguage(String lang) { - this.acceptLanguage = lang; - } - public void setContentLanguage(String lang) { - this.contentLanguage = lang; - } - - public int getUseCount() { - return useCount; - } - private void recordUse() { useCount++; } 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 5649cf23fb..89bd16f2c5 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 @@ -76,10 +76,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient { @Setter private Client client = new Client(); private List headers = new ArrayList<>(); - @Getter @Setter - private String username; - @Getter @Setter - private String password; + @Getter @Setter private String userAgent; @Setter @@ -544,13 +541,8 @@ public void setClientHeaders(Iterable headers) { //FIXME should be in ManagedWebAccess? private Iterable generateHeaders(boolean hasBody) { - List headers = new ArrayList<>(); - // Add basic auth header if it exists - if (basicAuthHeaderExists()) { - headers.add(getAuthorizationHeader()); - } // Add any other headers - headers.addAll(this.headers); + List headers = new ArrayList<>(this.headers); if (!Utilities.noString(userAgent)) { headers.add(new HTTPHeader("User-Agent",userAgent)); } @@ -563,21 +555,9 @@ private Iterable generateHeaders(boolean hasBody) { headers.add(new HTTPHeader("Content-Language",contentLanguage)); } - headers.add(new HTTPHeader("Host", "cts.nlm.nih.gov")); - return headers; } - public boolean basicAuthHeaderExists() { - return (username != null) && (password != null); - } - - public HTTPHeader getAuthorizationHeader() { - String usernamePassword = username + ":" + password; - String base64usernamePassword = Base64.getEncoder().encodeToString(usernamePassword.getBytes()); - return new HTTPHeader("Authorization", "Basic " + base64usernamePassword); - } - public String getServerVersion() { checkCapabilities(); return capabilities == null ? null : capabilities.getSoftware().getVersion(); 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 9c247bf9d9..4a9fdd5883 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 @@ -38,7 +38,6 @@ 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; @@ -46,7 +45,6 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS 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.*; @@ -96,13 +94,6 @@ public class FHIRToolingClient extends FHIRBaseToolingClient{ @Setter private Client client = new Client(); private List headers = new ArrayList<>(); - @Getter - @Setter - private String username; - - @Getter - @Setter - private String password; @Setter @Getter @@ -537,13 +528,8 @@ public void setClientHeaders(Iterable headers) { } private Iterable generateHeaders(boolean hasBody) { - List headers = new ArrayList<>(); - // Add basic auth header if it exists - if (basicAuthHeaderExists()) { - headers.add(getAuthorizationHeader()); - } // Add any other headers - headers.addAll(this.headers); + List headers = new ArrayList<>(this.headers); if (!Utilities.noString(userAgent)) { headers.add(new HTTPHeader("User-Agent",userAgent)); } @@ -559,16 +545,6 @@ private Iterable generateHeaders(boolean hasBody) { return headers; } - public boolean basicAuthHeaderExists() { - return (username != null) && (password != null); - } - - public HTTPHeader getAuthorizationHeader() { - String usernamePassword = username + ":" + password; - String base64usernamePassword = Base64.getEncoder().encodeToString(usernamePassword.getBytes()); - return new HTTPHeader("Authorization", "Basic " + base64usernamePassword); - } - public String getServerVersion() { return capabilities == null ? null : capabilities.getSoftware().getVersion(); } 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 696cd56cdf..6db89655a3 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 @@ -100,12 +100,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient { @Setter private Client client = new Client(); private List headers = new ArrayList<>(); - @Getter - @Setter - private String username; - @Getter - @Setter - private String password; + @Setter @Getter private String userAgent; @@ -602,13 +597,8 @@ public void setClientHeaders(Iterable headers) { //FIXME should be in ManagedWebAccess? private Iterable generateHeaders(boolean hasBody) { - List headers = new ArrayList<>(); - // Add basic auth header if it exists - if (basicAuthHeaderExists()) { - headers.add(getAuthorizationHeader()); - } // Add any other headers - headers.addAll(this.headers); + List headers = new ArrayList<>(this.headers); if (!Utilities.noString(userAgent)) { headers.add(new HTTPHeader("User-Agent",userAgent)); } @@ -624,16 +614,6 @@ private Iterable generateHeaders(boolean hasBody) { return headers; } - public boolean basicAuthHeaderExists() { - return (username != null) && (password != null); - } - - public HTTPHeader getAuthorizationHeader() { - String usernamePassword = username + ":" + password; - String base64usernamePassword = Base64.getEncoder().encodeToString(usernamePassword.getBytes()); - return new HTTPHeader("Authorization", "Basic " + base64usernamePassword); - } - public String getServerVersion() { if (capabilities == null) { try { 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 91776bf275..a2e0a529a4 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 @@ -76,8 +76,6 @@ public enum WebAccessPolicy { @Getter private static IFhirWebAccessor fhirWebAccessor; - - @Getter private static String userAgent; private static List serverAuthDetails; diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessBuilderBase.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessBuilderBase.java index f21f41bea5..68b89033d5 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessBuilderBase.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessBuilderBase.java @@ -51,4 +51,22 @@ public B withToken(String token) { this.token = token; 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; + } } From 7aef291a0c1165814124195208d411711fd30027 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 8 Nov 2024 08:46:23 +1030 Subject: [PATCH 29/42] fix VSAC importer for changes to ManagedWebAccess --- .../main/java/org/hl7/fhir/convertors/misc/VSACImporter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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..432a4dc1bd 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; @@ -53,9 +54,8 @@ private void process(String source, String dest, String apiKey, boolean onlyNew, 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); From 4592dc1a8219837040233e7a72aa1f33b4ae2c96 Mon Sep 17 00:00:00 2001 From: dotasek Date: Thu, 7 Nov 2024 17:42:32 -0500 Subject: [PATCH 30/42] Tests 1 --- .../client/ManagedWebAccessAuthTests.java | 11 -- .../http/ManagedWebAccessAuthTests.java | 164 ++++++++++++++++++ .../http/ManagedFhirWebAccessBuilder.java | 2 + .../http/ManagedWebAccessBuilder.java | 2 + .../http/ManagedWebAccessBuilderBase.java | 6 + 5 files changed, 174 insertions(+), 11 deletions(-) delete mode 100644 org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/ManagedWebAccessAuthTests.java create mode 100644 org.hl7.fhir.r5/src/test/java/org/hl7/fhir/utilities/http/ManagedWebAccessAuthTests.java diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/ManagedWebAccessAuthTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/ManagedWebAccessAuthTests.java deleted file mode 100644 index 8198a614c4..0000000000 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/ManagedWebAccessAuthTests.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.hl7.fhir.r5.utils.client; - -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/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/utilities/http/ManagedWebAccessAuthTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/utilities/http/ManagedWebAccessAuthTests.java new file mode 100644 index 0000000000..89eed159e7 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/utilities/http/ManagedWebAccessAuthTests.java @@ -0,0 +1,164 @@ +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) + ); + + ManagedFhirWebAccessBuilder builder = new ManagedFhirWebAccessBuilder("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 { + + + ManagedFhirWebAccessBuilder builder = new ManagedFhirWebAccessBuilder("dummyAgent", null).withBasicAuth("dummy1", "pass1"); + + testBasicServerAuth(builder); + } + + private void testBasicServerAuth(ManagedFhirWebAccessBuilder 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 { + HttpUrl serverUrl = server.url("blah/blah/blah?arg=blah"); + + server.enqueue( + new MockResponse() + .setBody("Dummy Response").setResponseCode(200) + ); + + ManagedFhirWebAccessBuilder builder = new ManagedFhirWebAccessBuilder("dummyAgent", null).withToken(DUMMY_TOKEN); + 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 static 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 { + HttpUrl serverUrl = server.url("blah/blah/blah?arg=blah"); + + server.enqueue( + new MockResponse() + .setBody("Dummy Response").setResponseCode(200) + ); + + ManagedFhirWebAccessBuilder builder = new ManagedFhirWebAccessBuilder("dummyAgent", null).withApiKey(DUMMY_API_KEY); + 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 testServerAuthFromSettings() throws IOException, InterruptedException { + ManagedFhirWebAccessBuilder builder = new ManagedFhirWebAccessBuilder( + "dummyAgent", + List.of(getBasicAuthServerPojo())); + + testBasicServerAuth(builder); + } + + private ServerDetailsPOJO getBasicAuthServerPojo() { + ServerDetailsPOJO pojo = new ServerDetailsPOJO( + server.url("").toString(), + "basic", + "dummyServerType", + DUMMY_USERNAME, + DUMMY_PASSWORD, + null, null); + return pojo; + } +} diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java index 5b3ff01d02..25a59750be 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java @@ -46,6 +46,8 @@ public ManagedFhirWebAccessBuilder withLogger(ToolingClientLogger logger) { public ManagedFhirWebAccessBuilder(String userAgent, List serverAuthDetails) { super(userAgent, serverAuthDetails); + this.timeout = 5000; + this.timeoutUnit = TimeUnit.MILLISECONDS; } protected HTTPRequest httpRequestWithDefaultHeaders(HTTPRequest request) { 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/ManagedWebAccessBuilder.java index fdb15e7fc9..d06193718f 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/ManagedWebAccessBuilder.java @@ -29,6 +29,8 @@ private Map newHeaders() { 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 (getUserAgent() != null) { diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessBuilderBase.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessBuilderBase.java index 68b89033d5..1783f7382e 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessBuilderBase.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessBuilderBase.java @@ -52,6 +52,12 @@ public B withToken(String 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(); From e9917defb08665d90a7f92f1cb695b6b45413f09 Mon Sep 17 00:00:00 2001 From: dotasek Date: Thu, 7 Nov 2024 17:53:14 -0500 Subject: [PATCH 31/42] Rename ...AccessBuilder classes to ...Accessor --- .../hl7/fhir/dstu2/utils/client/ClientUtils.java | 2 +- .../utils/client/network/FhirRequestBuilder.java | 2 +- .../r4/utils/client/network/FhirRequestBuilder.java | 4 +--- .../utils/client/network/FhirRequestBuilder.java | 5 +---- .../r5/utils/client/network/FhirRequestBuilder.java | 2 +- .../utilities/http/ManagedWebAccessAuthTests.java | 13 ++++++------- ...cessBuilder.java => ManagedFhirWebAccessor.java} | 10 +++++----- .../hl7/fhir/utilities/http/ManagedWebAccess.java | 9 ++++----- ...ebAccessBuilder.java => ManagedWebAccessor.java} | 7 ++----- ...BuilderBase.java => ManagedWebAccessorBase.java} | 4 ++-- .../org/hl7/fhir/utilities/npm/PackageClient.java | 5 ++--- ...rTests.java => ManagedFhirWebAccessorTests.java} | 6 +++--- 12 files changed, 29 insertions(+), 40 deletions(-) rename org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/{ManagedFhirWebAccessBuilder.java => ManagedFhirWebAccessor.java} (93%) rename org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/{ManagedWebAccessBuilder.java => ManagedWebAccessor.java} (94%) rename org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/{ManagedWebAccessBuilderBase.java => ManagedWebAccessorBase.java} (89%) rename org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/http/{ManagedFhirWebAccessBuilderTests.java => ManagedFhirWebAccessorTests.java} (87%) 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 35b3c70f75..31dfb85273 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 @@ -109,7 +109,7 @@ public class ClientUtils { private String contentLanguage; private final TimeUnit timeoutUnit = TimeUnit.MILLISECONDS; - protected ManagedFhirWebAccessBuilder getManagedWebAccessBuilder() { + protected ManagedFhirWebAccessor getManagedWebAccessBuilder() { return ManagedWebAccess.fhirBuilder().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); } 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 d632679490..f087c81b44 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 @@ -114,7 +114,7 @@ protected static String getLocationHeader(Iterable headers) { return HTTPHeaderUtil.getSingleHeader(headers, CONTENT_LOCATION_HEADER); } - protected ManagedFhirWebAccessBuilder getManagedWebAccessBuilder() { + protected ManagedFhirWebAccessor getManagedWebAccessBuilder() { return ManagedWebAccess.fhirBuilder().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); } 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 29d2a6d4c2..72da9e83f9 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 @@ -5,8 +5,6 @@ import java.util.List; import java.util.concurrent.TimeUnit; -import javax.annotation.Nonnull; - import lombok.Getter; import lombok.Setter; import org.apache.commons.lang3.StringUtils; @@ -124,7 +122,7 @@ protected static String getLocationHeader(Iterable headers) { return HTTPHeaderUtil.getSingleHeader(headers, CONTENT_LOCATION_HEADER); } - protected ManagedFhirWebAccessBuilder getManagedWebAccessBuilder() { + protected ManagedFhirWebAccessor getManagedWebAccessBuilder() { return ManagedWebAccess.fhirBuilder().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); } 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 47a45c691c..b969a15c29 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 @@ -3,11 +3,8 @@ 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; @@ -126,7 +123,7 @@ protected static String getLocationHeader(Iterable headers) { return HTTPHeaderUtil.getSingleHeader(headers, CONTENT_LOCATION_HEADER); } - protected ManagedFhirWebAccessBuilder getManagedWebAccessBuilder() { + protected ManagedFhirWebAccessor getManagedWebAccessBuilder() { return ManagedWebAccess.fhirBuilder().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); } 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 eeb28b1be6..d4a285a4ba 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 @@ -113,7 +113,7 @@ protected static boolean hasError(OperationOutcome oo) { || issue.getSeverity() == OperationOutcome.IssueSeverity.FATAL)); } - protected ManagedFhirWebAccessBuilder getManagedWebAccessBuilder() { + protected ManagedFhirWebAccessor getManagedWebAccessBuilder() { return ManagedWebAccess.fhirBuilder().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); } diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/utilities/http/ManagedWebAccessAuthTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/utilities/http/ManagedWebAccessAuthTests.java index 89eed159e7..98d7a89594 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/utilities/http/ManagedWebAccessAuthTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/utilities/http/ManagedWebAccessAuthTests.java @@ -17,7 +17,6 @@ public class ManagedWebAccessAuthTests { - public static final String DUMMY_AGENT = "dummyAgent"; public static final String DUMMY_USERNAME = "dummy1"; public static final String DUMMY_PASSWORD = "pass1"; @@ -44,7 +43,7 @@ public void testBaseCase() throws IOException, InterruptedException { .setBody("Dummy Response").setResponseCode(200) ); - ManagedFhirWebAccessBuilder builder = new ManagedFhirWebAccessBuilder("dummyAgent", null); + ManagedFhirWebAccessor builder = new ManagedFhirWebAccessor("dummyAgent", null); HTTPResult result = builder.httpCall(new HTTPRequest().withUrl(serverUrl.toString()).withMethod(HTTPRequest.HttpMethod.GET)); assertThat(result.getCode()).isEqualTo(200); @@ -62,12 +61,12 @@ public void testBaseCase() throws IOException, InterruptedException { public void testBasicAuthCase() throws IOException, InterruptedException { - ManagedFhirWebAccessBuilder builder = new ManagedFhirWebAccessBuilder("dummyAgent", null).withBasicAuth("dummy1", "pass1"); + ManagedFhirWebAccessor builder = new ManagedFhirWebAccessor("dummyAgent", null).withBasicAuth("dummy1", "pass1"); testBasicServerAuth(builder); } - private void testBasicServerAuth(ManagedFhirWebAccessBuilder builder) throws IOException, InterruptedException { + private void testBasicServerAuth(ManagedFhirWebAccessor builder) throws IOException, InterruptedException { HttpUrl serverUrl = server.url("blah/blah/blah?arg=blah"); server.enqueue( @@ -99,7 +98,7 @@ public void testTokenAuthCase() throws IOException, InterruptedException { .setBody("Dummy Response").setResponseCode(200) ); - ManagedFhirWebAccessBuilder builder = new ManagedFhirWebAccessBuilder("dummyAgent", null).withToken(DUMMY_TOKEN); + ManagedFhirWebAccessor builder = new ManagedFhirWebAccessor("dummyAgent", null).withToken(DUMMY_TOKEN); HTTPResult result = builder.httpCall(new HTTPRequest().withUrl(serverUrl.toString()).withMethod(HTTPRequest.HttpMethod.GET)); assertThat(result.getCode()).isEqualTo(200); @@ -128,7 +127,7 @@ public void testApiKeyAuthCase() throws IOException, InterruptedException { .setBody("Dummy Response").setResponseCode(200) ); - ManagedFhirWebAccessBuilder builder = new ManagedFhirWebAccessBuilder("dummyAgent", null).withApiKey(DUMMY_API_KEY); + ManagedFhirWebAccessor builder = new ManagedFhirWebAccessor("dummyAgent", null).withApiKey(DUMMY_API_KEY); HTTPResult result = builder.httpCall(new HTTPRequest().withUrl(serverUrl.toString()).withMethod(HTTPRequest.HttpMethod.GET)); assertThat(result.getCode()).isEqualTo(200); @@ -144,7 +143,7 @@ public void testApiKeyAuthCase() throws IOException, InterruptedException { @Test public void testServerAuthFromSettings() throws IOException, InterruptedException { - ManagedFhirWebAccessBuilder builder = new ManagedFhirWebAccessBuilder( + ManagedFhirWebAccessor builder = new ManagedFhirWebAccessor( "dummyAgent", List.of(getBasicAuthServerPojo())); diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessor.java similarity index 93% rename from org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java rename to org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessor.java index 25a59750be..df5f616e75 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilder.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessor.java @@ -13,7 +13,7 @@ import java.util.Map; import java.util.concurrent.TimeUnit; -public class ManagedFhirWebAccessBuilder extends ManagedWebAccessBuilderBase{ +public class ManagedFhirWebAccessor extends ManagedWebAccessorBase { /** * The singleton instance of the HttpClient, used for all requests. @@ -26,25 +26,25 @@ public class ManagedFhirWebAccessBuilder extends ManagedWebAccessBuilderBase serverAuthDetails) { + public ManagedFhirWebAccessor(String userAgent, List serverAuthDetails) { super(userAgent, serverAuthDetails); this.timeout = 5000; this.timeoutUnit = TimeUnit.MILLISECONDS; 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 a2e0a529a4..ff7df4e945 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 @@ -35,7 +35,6 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS import java.util.*; import lombok.Getter; -import okhttp3.Response; import org.hl7.fhir.utilities.settings.FhirSettings; import org.hl7.fhir.utilities.settings.ServerDetailsPOJO; @@ -104,12 +103,12 @@ public static void setUserAgent(String userAgent) { ManagedWebAccess.userAgent = userAgent; } - public static ManagedWebAccessBuilder builder() { - return new ManagedWebAccessBuilder(userAgent, serverAuthDetails); + public static ManagedWebAccessor builder() { + return new ManagedWebAccessor(userAgent, serverAuthDetails); } - public static ManagedFhirWebAccessBuilder fhirBuilder() { - return new ManagedFhirWebAccessBuilder(userAgent, serverAuthDetails); + public static ManagedFhirWebAccessor fhirBuilder() { + return new ManagedFhirWebAccessor(userAgent, serverAuthDetails); } public static HTTPResult get(String url) throws IOException { 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 94% 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 d06193718f..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 @@ -7,17 +7,14 @@ import java.util.List; import java.util.Map; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; import org.hl7.fhir.utilities.settings.ServerDetailsPOJO; /** * Simple HTTP client for making requests to a server. */ -public class ManagedWebAccessBuilder extends ManagedWebAccessBuilderBase { +public class ManagedWebAccessor extends ManagedWebAccessorBase { - public ManagedWebAccessBuilder(String userAgent, List serverAuthDetails) { + public ManagedWebAccessor(String userAgent, List serverAuthDetails) { super(userAgent, serverAuthDetails); } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessBuilderBase.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessorBase.java similarity index 89% rename from org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessBuilderBase.java rename to org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessorBase.java index 1783f7382e..67e4df641d 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessBuilderBase.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessorBase.java @@ -7,7 +7,7 @@ import java.util.List; import java.util.Map; -public abstract class ManagedWebAccessBuilderBase> { +public abstract class ManagedWebAccessorBase> { @Getter private final String userAgent; @Getter @@ -24,7 +24,7 @@ public abstract class ManagedWebAccessBuilderBase headers = new HashMap<>(); - public ManagedWebAccessBuilderBase(String userAgent, List serverAuthDetails) { + public ManagedWebAccessorBase(String userAgent, List serverAuthDetails) { this.userAgent = userAgent; this.serverAuthDetails = serverAuthDetails; } 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 111b6f2fd0..eb7e8518ee 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,7 +175,7 @@ public Date getNewPackages(Date lastCalled, List updates) { } private InputStream fetchUrl(String source, String accept) throws IOException { - ManagedWebAccessBuilder client = ManagedWebAccess.builder(); + ManagedWebAccessor client = ManagedWebAccess.builder(); if (server.getAuthenticationMode() == HTTPAuthenticationMode.TOKEN) { client.withToken(server.getToken()); } else if (server.getAuthenticationMode() == HTTPAuthenticationMode.BASIC) { diff --git a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilderTests.java b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessorTests.java similarity index 87% rename from org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilderTests.java rename to org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessorTests.java index 072364c936..3a23c86136 100644 --- a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessBuilderTests.java +++ b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/http/ManagedFhirWebAccessorTests.java @@ -4,7 +4,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -public class ManagedFhirWebAccessBuilderTests { +public class ManagedFhirWebAccessorTests { final String expectedUserAgent = "dummy-agent"; @Test @@ -13,7 +13,7 @@ void addDefaultAgentHeader() { // FIXME move to ManagedFhirWebAccessBuilder HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com"); - ManagedFhirWebAccessBuilder builder = new ManagedFhirWebAccessBuilder(expectedUserAgent, null); + ManagedFhirWebAccessor builder = new ManagedFhirWebAccessor(expectedUserAgent, null); HTTPRequest requestWithDefaultHeaders = builder.httpRequestWithDefaultHeaders(request); assertRequestContainsExpectedAgentHeader(requestWithDefaultHeaders); @@ -25,7 +25,7 @@ void addDefaultBasicHeader() { // FIXME move to ManagedFhirWebAccessBuilder HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com"); - ManagedFhirWebAccessBuilder builder = new ManagedFhirWebAccessBuilder(expectedUserAgent, null) + ManagedFhirWebAccessor builder = new ManagedFhirWebAccessor(expectedUserAgent, null) .withBasicAuth("dummy-user", "dummy-password"); HTTPRequest requestWithManagedHeaders = builder.requestWithManagedHeaders(request); From 822f0866b4fb0aea3146b0230272c26a02bb0a4e Mon Sep 17 00:00:00 2001 From: dotasek Date: Thu, 7 Nov 2024 18:07:23 -0500 Subject: [PATCH 32/42] Fix api-key token mixup, more tests --- .../http/ManagedWebAccessAuthTests.java | 65 ++++++++++++++++--- .../http/ManagedFhirWebAccessor.java | 2 +- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/utilities/http/ManagedWebAccessAuthTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/utilities/http/ManagedWebAccessAuthTests.java index 98d7a89594..94f70a3ccc 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/utilities/http/ManagedWebAccessAuthTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/utilities/http/ManagedWebAccessAuthTests.java @@ -60,7 +60,6 @@ public void testBaseCase() throws IOException, InterruptedException { @Test public void testBasicAuthCase() throws IOException, InterruptedException { - ManagedFhirWebAccessor builder = new ManagedFhirWebAccessor("dummyAgent", null).withBasicAuth("dummy1", "pass1"); testBasicServerAuth(builder); @@ -91,14 +90,20 @@ private void testBasicServerAuth(ManagedFhirWebAccessor builder) throws IOExcept @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) ); - - ManagedFhirWebAccessor builder = new ManagedFhirWebAccessor("dummyAgent", null).withToken(DUMMY_TOKEN); HTTPResult result = builder.httpCall(new HTTPRequest().withUrl(serverUrl.toString()).withMethod(HTTPRequest.HttpMethod.GET)); assertThat(result.getCode()).isEqualTo(200); @@ -112,7 +117,7 @@ public void testTokenAuthCase() throws IOException, InterruptedException { assertThat(packageRequest.getHeader("Authorization")).isEqualTo("Bearer " + DUMMY_TOKEN); } - private static void assertExpectedHeaders(RecordedRequest packageRequest, String expectedUrl, String expectedHttpMethod) { + 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); @@ -120,14 +125,19 @@ private static void assertExpectedHeaders(RecordedRequest packageRequest, String @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) ); - - ManagedFhirWebAccessor builder = new ManagedFhirWebAccessor("dummyAgent", null).withApiKey(DUMMY_API_KEY); HTTPResult result = builder.httpCall(new HTTPRequest().withUrl(serverUrl.toString()).withMethod(HTTPRequest.HttpMethod.GET)); assertThat(result.getCode()).isEqualTo(200); @@ -142,7 +152,7 @@ public void testApiKeyAuthCase() throws IOException, InterruptedException { } @Test - public void testServerAuthFromSettings() throws IOException, InterruptedException { + public void testBasicAuthFromSettings() throws IOException, InterruptedException { ManagedFhirWebAccessor builder = new ManagedFhirWebAccessor( "dummyAgent", List.of(getBasicAuthServerPojo())); @@ -151,13 +161,50 @@ public void testServerAuthFromSettings() throws IOException, InterruptedExceptio } private ServerDetailsPOJO getBasicAuthServerPojo() { - ServerDetailsPOJO pojo = new ServerDetailsPOJO( + return new ServerDetailsPOJO( server.url("").toString(), "basic", "dummyServerType", DUMMY_USERNAME, DUMMY_PASSWORD, null, null); - return pojo; } + +@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); + } +} \ 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 index df5f616e75..0ae20cfff0 100644 --- 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 @@ -100,7 +100,7 @@ protected HTTPRequest requestWithManagedHeaders(HTTPRequest httpRequest) { headers.add(new HTTPHeader("Authorization", tokenCredential)); break; case "apikey": - String apiKeyCredential = settings.getToken(); + String apiKeyCredential = settings.getApikey(); headers.add(new HTTPHeader("Api-Key", apiKeyCredential)); break; } From 2bbfcb8845a7a4f59b220aedc04bb68a56f85836 Mon Sep 17 00:00:00 2001 From: dotasek Date: Fri, 8 Nov 2024 11:08:06 -0500 Subject: [PATCH 33/42] Resolve all deletions enclosed in FIXME + fix headers in dstu2 postfeed --- .../fhir/dstu2/utils/client/ClientUtils.java | 36 ++++---------- .../dstu3/utils/client/network/Client.java | 47 ++----------------- .../network/FhirRequestBuilderTests.java | 2 - .../r4/utils/client/FHIRToolingClient.java | 1 - .../fhir/r4/utils/client/network/Client.java | 34 ++------------ .../network/FhirRequestBuilderTest.java | 1 - .../fhir/r4b/utils/client/network/Client.java | 31 ++---------- .../network/FhirRequestBuilderTest.java | 1 - .../client/TerminologyClientR5.java | 1 - .../r5/utils/client/FHIRToolingClient.java | 15 +++--- .../fhir/r5/utils/client/network/Client.java | 47 ++----------------- .../client/network/FhirRequestBuilder.java | 14 ++---- .../network/FhirRequestBuilderTest.java | 2 - .../http/ManagedFhirWebAccessorTests.java | 2 - 14 files changed, 38 insertions(+), 196 deletions(-) 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 31dfb85273..c880a948e5 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 @@ -119,9 +119,6 @@ public ResourceRequest issueOptionsRequest(URI optionsUr throw new FHIRException("Network Access is prohibited in this context"); } - /*FIXME delete this after refactor - HttpOptions options = new HttpOptions(optionsUri); - */ HTTPRequest httpRequest = new HTTPRequest() .withMethod(HTTPRequest.HttpMethod.OPTIONS) .withUrl(optionsUri.toString()); @@ -133,9 +130,7 @@ public ResourceRequest issueGetResourceRequest(URI resou if (FhirSettings.isProhibitNetworkAccess()) { throw new FHIRException("Network Access is prohibited in this context"); } - /*FIXME delete this after refactor - HttpGet httpget = new HttpGet(resourceUri); - */ + HTTPRequest httpRequest = new HTTPRequest() .withMethod(HTTPRequest.HttpMethod.GET) .withUrl(resourceUri.toString()); @@ -159,9 +154,7 @@ public ResourceRequest issuePutRequest(URI resourceUri, if (FhirSettings.isProhibitNetworkAccess()) { throw new FHIRException("Network Access is prohibited in this context"); } - /*FIXME delete this after refactor - HttpPut httpPut = new HttpPut(resourceUri); - */ + HTTPRequest httpRequest = new HTTPRequest() .withMethod(HTTPRequest.HttpMethod.PUT) .withUrl(resourceUri.toString()) @@ -174,9 +167,7 @@ public ResourceRequest issuePostRequest(URI resourceUri, if (FhirSettings.isProhibitNetworkAccess()) { throw new FHIRException("Network Access is prohibited in this context"); } - /*FIXME delete this after refactor - HttpPost httpPost = new HttpPost(resourceUri); - */ + HTTPRequest httpRequest = new HTTPRequest() .withMethod(HTTPRequest.HttpMethod.POST) .withUrl(resourceUri.toString()) @@ -193,9 +184,7 @@ public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat) { if (FhirSettings.isProhibitNetworkAccess()) { throw new FHIRException("Network Access is prohibited in this context"); } - /*FIXME delete this after refactor - HttpGet httpget = new HttpGet(resourceUri); - */ + HTTPRequest httpRequest = new HTTPRequest() .withMethod(HTTPRequest.HttpMethod.GET) .withUrl(resourceUri.toString()); @@ -208,9 +197,7 @@ public Bundle postBatchRequest(URI resourceUri, byte[] payload, String resourceF if (FhirSettings.isProhibitNetworkAccess()) { throw new FHIRException("Network Access is prohibited in this context"); } - /*FIXME delete this after refactor - HttpPost httpPost = new HttpPost(resourceUri); - */ + HTTPRequest httpRequest = new HTTPRequest() .withMethod(HTTPRequest.HttpMethod.POST) .withUrl(resourceUri.toString()) @@ -224,9 +211,7 @@ public boolean issueDeleteRequest(URI resourceUri) { if (FhirSettings.isProhibitNetworkAccess()) { throw new FHIRException("Network Access is prohibited in this context"); } - /*FIXME delete this after refactor - HttpDelete deleteRequest = new HttpDelete(resourceUri); - */ + HTTPRequest request = new HTTPRequest() .withMethod(HTTPRequest.HttpMethod.DELETE) .withUrl(resourceUri.toString()); @@ -303,7 +288,6 @@ protected Iterable getFhirHeaders(HTTPRequest httpRequest, String fo return configuredHeaders; } - //FIXME this shouldn't set Content-Type header for GET requests protected static List getResourceFormatHeaders(HTTPRequest httpRequest, String format) { List headers = new ArrayList<>(); headers.add(new HTTPHeader("Accept", format)); @@ -531,9 +515,6 @@ protected IParser getParser(String format) { public Bundle issuePostFeedRequest(URI resourceUri, Map parameters, String resourceName, Resource resource, String resourceFormat) throws IOException { - /*FIXME delete this after refactor - HttpPost httpRequest = new HttpPost(resourceUri); - */ HTTPRequest httpRequest = new HTTPRequest() .withMethod(HTTPRequest.HttpMethod.POST) @@ -542,8 +523,9 @@ public Bundle issuePostFeedRequest(URI resourceUri, Map paramete 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); - HTTPResult response = sendPayload(httpRequest.withBody(encodeFormSubmission(parameters, resourceName, resource, boundary))); + this.getFhirHeaders(httpRequest, null).forEach(headers::add); + + HTTPResult response = sendPayload(httpRequest.withBody(encodeFormSubmission(parameters, resourceName, resource, boundary)).withHeaders(headers)); return unmarshalFeed(response, resourceFormat); } 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 6ce3da987d..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 @@ -60,11 +60,7 @@ public ResourceRequest issueOptionsRequest(URI optionsUr String resourceFormat, String message, long timeout) throws IOException { - /*FIXME delete after refactor - Request.Builder request = new Request.Builder() - .method("OPTIONS", null) - .url(optionsUri.toURL()); - */ + HTTPRequest request = new HTTPRequest() .withUrl(optionsUri.toURL()) .withMethod(HTTPRequest.HttpMethod.OPTIONS); @@ -77,10 +73,6 @@ public ResourceRequest issueGetResourceRequest(URI resou Iterable headers, String message, long timeout) throws IOException { - /*FIXME delete after refactor - Request.Builder request = new Request.Builder() - .url(resourceUri.toURL()); - */ HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) .withMethod(HTTPRequest.HttpMethod.GET); @@ -105,12 +97,7 @@ public ResourceRequest issuePutRequest(URI resourceUri, String message, long timeout) throws IOException { if (payload == null) throw new EFhirClientException("PUT requests require a non-null payload"); - /*FIXME delete after refactor - 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) @@ -133,12 +120,7 @@ public ResourceRequest issuePostRequest(URI resourceUri, String message, long timeout) throws IOException { if (payload == null) throw new EFhirClientException("POST requests require a non-null payload"); - /*FIXME delete after refactor - 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) @@ -147,11 +129,6 @@ public ResourceRequest issuePostRequest(URI resourceUri, } public boolean issueDeleteRequest(URI resourceUri) throws IOException { - /*FIXME delete after refactor - Request.Builder request = new Request.Builder() - .url(resourceUri.toURL()) - .delete(); - */ HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) .withMethod(HTTPRequest.HttpMethod.DELETE); @@ -159,10 +136,6 @@ public boolean issueDeleteRequest(URI resourceUri) throws IOException { } public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat) throws IOException { - /*FIXME delete after refactor - Request.Builder request = new Request.Builder() - .url(resourceUri.toURL()); -*/ HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) .withMethod(HTTPRequest.HttpMethod.GET); @@ -176,12 +149,7 @@ public Bundle issuePostFeedRequest(URI resourceUri, String resourceFormat) throws IOException { String boundary = "----WebKitFormBoundarykbMUo6H8QaUnYtRy"; byte[] payload = ByteUtils.encodeFormSubmission(parameters, resourceName, resource, boundary); - /*FIXME delete after refactor - 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) @@ -196,12 +164,7 @@ public Bundle postBatchRequest(URI resourceUri, String message, int timeout) throws IOException { if (payload == null) throw new EFhirClientException("POST requests require a non-null payload"); - /*FIXME delete after refactor - 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) 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 d2f85b5202..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 @@ -16,7 +16,6 @@ public class FhirRequestBuilderTests { @Test @DisplayName("Test resource format headers are added correctly (GET).") void addResourceFormatHeadersGET() { - //FIXME tested here. Should get list of HTTPHeader. String testFormat = "yaml"; HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com").withMethod(HTTPRequest.HttpMethod.GET); @@ -33,7 +32,6 @@ void addResourceFormatHeadersGET() { @Test @DisplayName("Test resource format headers are added correctly (POST).") void addResourceFormatHeadersPOST() { - //FIXME tested here. Should get list of HTTPHeader. String testFormat = "yaml"; HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com").withMethod(HTTPRequest.HttpMethod.POST); 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 89bd16f2c5..59eec77434 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 @@ -539,7 +539,6 @@ public void setClientHeaders(Iterable headers) { headers.forEach(this.headers::add); } - //FIXME should be in ManagedWebAccess? private Iterable generateHeaders(boolean hasBody) { // Add any other headers List headers = new ArrayList<>(this.headers); 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 0835cda2a5..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 @@ -31,9 +31,7 @@ public class Client { public ResourceRequest issueOptionsRequest(URI optionsUri, String resourceFormat, String message, long timeout) throws IOException { - /*FIXME delete once refactor is done - Request.Builder request = new Request.Builder().method("OPTIONS", null).url(optionsUri.toURL()); -*/ + HTTPRequest request = new HTTPRequest() .withUrl(optionsUri.toURL()) .withMethod(HTTPRequest.HttpMethod.OPTIONS); @@ -42,10 +40,6 @@ public ResourceRequest issueOptionsRequest(URI optionsUr public ResourceRequest issueGetResourceRequest(URI resourceUri, String resourceFormat, Iterable headers, String message, long timeout) throws IOException { - - /*FIXME delete once refactor is done - Request.Builder request = new Request.Builder().url(resourceUri.toURL()); - */ HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) .withMethod(HTTPRequest.HttpMethod.GET); @@ -61,10 +55,7 @@ public ResourceRequest issuePutRequest(URI resourceUri, Iterable headers, String message, long timeout) throws IOException { if (payload == null) throw new EFhirClientException("PUT requests require a non-null payload"); - /*FIXME delete once refactor is done - 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) @@ -83,10 +74,7 @@ public ResourceRequest issuePostRequest(URI resourceUri, String resourceFormat, Iterable headers, String message, long timeout) throws IOException { if (payload == null) throw new EFhirClientException("POST requests require a non-null payload"); - /*FIXME delete once refactor is done - 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) @@ -97,9 +85,6 @@ public ResourceRequest issuePostRequest(URI resourceUri, } public boolean issueDeleteRequest(URI resourceUri, int timeout) throws IOException { - /*FIXME delete once refactor is done - Request.Builder request = new Request.Builder().url(resourceUri.toURL()).delete(); - */ HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) .withMethod(HTTPRequest.HttpMethod.DELETE); @@ -108,9 +93,6 @@ public boolean issueDeleteRequest(URI resourceUri, int timeout) throws IOExcepti } public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat, int timeout) throws IOException { - /*FIXME delete once refactor is done - Request.Builder request = new Request.Builder().url(resourceUri.toURL()); - */ HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) .withMethod(HTTPRequest.HttpMethod.GET); @@ -123,11 +105,6 @@ public Bundle issuePostFeedRequest(URI resourceUri, Map paramete String boundary = "----WebKitFormBoundarykbMUo6H8QaUnYtRy"; byte[] payload = ByteUtils.encodeFormSubmission(parameters, resourceName, resource, boundary); - /*FIXME delete once refactor is done - 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) @@ -141,10 +118,7 @@ public Bundle postBatchRequest(URI resourceUri, byte[] payload, String resourceF throws IOException { if (payload == null) throw new EFhirClientException("POST requests require a non-null payload"); - /*FIXME delete once refactor is done - 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) 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 index 77e6d473fb..5d278d574e 100644 --- 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 @@ -33,7 +33,6 @@ void addResourceFormatHeadersGET() { @Test @DisplayName("Test resource format headers are added correctly (POST).") void addResourceFormatHeadersPOST() { - //FIXME tested here. Should get list of HTTPHeader. String testFormat = "yaml"; HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com").withMethod(HTTPRequest.HttpMethod.POST); 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 1e17be18dc..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 @@ -35,9 +35,6 @@ public class Client { public ResourceRequest issueOptionsRequest(URI optionsUri, String resourceFormat, String message, long timeout) throws IOException { - /*FIXME delete once refactor is done - Request.Builder request = new Request.Builder().method("OPTIONS", null).url(optionsUri.toURL()); -*/ HTTPRequest request = new HTTPRequest() .withUrl(optionsUri.toURL()) .withMethod(HTTPRequest.HttpMethod.OPTIONS); @@ -46,9 +43,6 @@ public ResourceRequest issueOptionsRequest(URI optionsUr public ResourceRequest issueGetResourceRequest(URI resourceUri, String resourceFormat, Iterable headers, String message, long timeout) throws IOException { -/*FIXME delete once refactor is done - Request.Builder request = new Request.Builder().url(resourceUri.toURL()); -*/ HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) .withMethod(HTTPRequest.HttpMethod.GET); @@ -64,10 +58,7 @@ public ResourceRequest issuePutRequest(URI resourceUri, Iterable headers, String message, long timeout) throws IOException { if (payload == null) throw new EFhirClientException("PUT requests require a non-null payload"); -/*FIXME delete once refactor is done - 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) @@ -87,10 +78,6 @@ public ResourceRequest issuePostRequest(URI resourceUri, if (payload == null) throw new EFhirClientException("POST requests require a non-null payload"); - /*FIXME delete once refactor is done - 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) @@ -101,9 +88,6 @@ public ResourceRequest issuePostRequest(URI resourceUri, } public boolean issueDeleteRequest(URI resourceUri) throws IOException { - /*FIXME delete once refactor is done - Request.Builder request = new Request.Builder().url(resourceUri.toURL()).delete(); - */ HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) .withMethod(HTTPRequest.HttpMethod.DELETE); @@ -112,9 +96,6 @@ public boolean issueDeleteRequest(URI resourceUri) throws IOException { } public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat) throws IOException { - /*FIXME delete once refactor is done - Request.Builder request = new Request.Builder().url(resourceUri.toURL()); - */ HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) .withMethod(HTTPRequest.HttpMethod.GET); @@ -125,10 +106,7 @@ public Bundle issuePostFeedRequest(URI resourceUri, Map paramete Resource resource, String resourceFormat) throws IOException { String boundary = "----WebKitFormBoundarykbMUo6H8QaUnYtRy"; byte[] payload = ByteUtils.encodeFormSubmission(parameters, resourceName, resource, boundary); - /*FIXME delete once refactor is done - 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) @@ -141,10 +119,7 @@ public Bundle postBatchRequest(URI resourceUri, byte[] payload, String resourceF String message, int timeout) throws IOException { if (payload == null) throw new EFhirClientException("POST requests require a non-null payload"); - /*FIXME delete once refactor is done - 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) 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 a1004d6dfc..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 @@ -33,7 +33,6 @@ void addResourceFormatHeadersGET() { @Test @DisplayName("Test resource format headers are added correctly (POST).") void addResourceFormatHeadersPOST() { - //FIXME tested here. Should get list of HTTPHeader. String testFormat = "yaml"; HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com").withMethod(HTTPRequest.HttpMethod.POST); 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 4254edc815..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 @@ -80,7 +80,6 @@ public String getVersion() { public TerminologyClientR5(String id, String address, String userAgent) throws URISyntaxException { this.client = new FHIRToolingClient(address, userAgent); - //FIXME set up FHIR Tooling Client to use ManagedWebAccess setClientHeaders(new ClientHeaders()); this.id = id; } 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 6db89655a3..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 @@ -403,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) { @@ -420,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; @@ -595,7 +599,6 @@ public void setClientHeaders(Iterable headers) { headers.forEach(this.headers::add); } - //FIXME should be in ManagedWebAccess? private Iterable generateHeaders(boolean hasBody) { // Add any other headers List headers = new ArrayList<>(this.headers); 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 e4d2ab5f67..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 @@ -35,11 +35,6 @@ public ResourceRequest issueOptionsRequest(URI optionsUr String resourceFormat, String message, long timeout) throws IOException { - /*FIXME delete once refactor is done - Request.Builder request = new Request.Builder() - .method("OPTIONS", null) - .url(optionsUri.toURL()); - */ HTTPRequest request = new HTTPRequest() .withUrl(optionsUri.toURL()) .withMethod(HTTPRequest.HttpMethod.OPTIONS); @@ -51,11 +46,6 @@ public ResourceRequest issueGetResourceRequest(URI resou Iterable headers, String message, long timeout) throws IOException { - /*FIXME delete once refactor is done - Request.Builder request = new Request.Builder() - .url(resourceUri.toURL()); - - */ HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) .withMethod(HTTPRequest.HttpMethod.GET); @@ -78,12 +68,7 @@ public ResourceRequest issuePutRequest(URI resourceUri, String message, long timeout) throws IOException { if (payload == null) throw new EFhirClientException(0, "PUT requests require a non-null payload"); - /*FIXME delete once refactor is done - 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) @@ -108,12 +93,7 @@ public ResourceRequest issuePostRequest(URI resourceUri, String message, long timeout) throws IOException { if (payload == null) throw new EFhirClientException(0, "POST requests require a non-null payload"); - /*FIXME delete once refactor is done - RequestBody body = RequestBody.create(MediaType.parse(getContentTypeWithDefaultCharset(resourceFormat)), payload); - Request.Builder request = new Request.Builder() - .url(resourceUri.toURL()) - .post(body); -*/ + HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) .withMethod(HTTPRequest.HttpMethod.POST) @@ -124,11 +104,6 @@ public ResourceRequest issuePostRequest(URI resourceUri, } public boolean issueDeleteRequest(URI resourceUri) throws IOException { - /*FIXME delete once refactor is done - Request.Builder request = new Request.Builder() - .url(resourceUri.toURL()) - .delete(); - */ HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) .withMethod(HTTPRequest.HttpMethod.DELETE); @@ -136,10 +111,6 @@ public boolean issueDeleteRequest(URI resourceUri) throws IOException { } public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat) throws IOException { - /*FIXME delete once refactor is done - Request.Builder request = new Request.Builder() - .url(resourceUri.toURL()); - */ HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) .withMethod(HTTPRequest.HttpMethod.GET); @@ -153,12 +124,7 @@ public Bundle issuePostFeedRequest(URI resourceUri, String resourceFormat) throws IOException { String boundary = "----WebKitFormBoundarykbMUo6H8QaUnYtRy"; byte[] payload = ByteUtils.encodeFormSubmission(parameters, resourceName, resource, boundary); - /*FIXME delete once refactor is done - RequestBody body = RequestBody.create(MediaType.parse(getContentTypeWithDefaultCharset(resourceFormat)), payload); - Request.Builder request = new Request.Builder() - .url(resourceUri.toURL()) - .post(body); -*/ + HTTPRequest request = new HTTPRequest() .withUrl(resourceUri.toURL()) .withMethod(HTTPRequest.HttpMethod.POST) @@ -174,12 +140,7 @@ public Bundle postBatchRequest(URI resourceUri, String message, int timeout) throws IOException { if (payload == null) throw new EFhirClientException(0, "POST requests require a non-null payload"); - /*FIXME delete once refactor is done - 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) 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 d4a285a4ba..77e1ed4430 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 @@ -29,11 +29,6 @@ public class FhirRequestBuilder { protected static final String LOCATION_HEADER = "location"; protected static final String CONTENT_LOCATION_HEADER = "content-location"; - /** - * 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 Iterable headers = null; @@ -55,7 +50,6 @@ public class FhirRequestBuilder { private String source; - //TODO this should be the only constructor. There should be no okHttp exposure. public FhirRequestBuilder(HTTPRequest httpRequest, String source) { this.source = source; this.httpRequest = httpRequest; @@ -78,13 +72,11 @@ protected static HTTPRequest formatHeaders(HTTPRequest request, String format, I return request.withHeaders(allHeaders); } - /** - - /** * Adds necessary headers for the given resource format provided. * * @param httpRequest {@link HTTPRequest} to add default headers to. + * @param format Expected {@link Resource} format. */ protected static Iterable getResourceFormatHeaders(HTTPRequest httpRequest, String format) { List headers = new ArrayList<>(); @@ -279,8 +271,10 @@ protected IParser getParser(String format) { } /** - * Extracts the 'location' header from. If no value for 'location' exists, the + * 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); 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 ffcf8ab164..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 @@ -18,7 +18,6 @@ class FhirRequestBuilderTest { @Test @DisplayName("Test resource format headers are added correctly (GET).") void addResourceFormatHeadersGET() { - //FIXME tested here. Should get list of HTTPHeader. String testFormat = "yaml"; HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com").withMethod(HTTPRequest.HttpMethod.GET); @@ -35,7 +34,6 @@ void addResourceFormatHeadersGET() { @Test @DisplayName("Test resource format headers are added correctly (POST).") void addResourceFormatHeadersPOST() { - //FIXME tested here. Should get list of HTTPHeader. String testFormat = "yaml"; HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com").withMethod(HTTPRequest.HttpMethod.POST); 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 index 3a23c86136..be68a2571b 100644 --- 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 @@ -10,7 +10,6 @@ public class ManagedFhirWebAccessorTests { @Test @DisplayName("Test default headers are added correctly.") void addDefaultAgentHeader() { - // FIXME move to ManagedFhirWebAccessBuilder HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com"); ManagedFhirWebAccessor builder = new ManagedFhirWebAccessor(expectedUserAgent, null); @@ -22,7 +21,6 @@ void addDefaultAgentHeader() { @Test @DisplayName("Test default headers are added correctly.") void addDefaultBasicHeader() { - // FIXME move to ManagedFhirWebAccessBuilder HTTPRequest request = new HTTPRequest().withUrl("http://www.google.com"); ManagedFhirWebAccessor builder = new ManagedFhirWebAccessor(expectedUserAgent, null) From ebcbf4db495bb892e1a3b78e4ea3868ce1439731 Mon Sep 17 00:00:00 2001 From: dotasek Date: Fri, 8 Nov 2024 11:18:57 -0500 Subject: [PATCH 34/42] More TODO erasures. Plus found missing functionality --- .../fhir/r4b/utils/client/FHIRToolingClient.java | 14 +++++++++----- .../utils/client/network/FhirRequestBuilder.java | 1 - .../utilities/http/ManagedFhirWebAccessor.java | 7 +++---- 3 files changed, 12 insertions(+), 10 deletions(-) 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 4a9fdd5883..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 @@ -362,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) { @@ -379,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; 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 b969a15c29..74a4c3354a 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 @@ -51,7 +51,6 @@ public class FhirRequestBuilder { private ToolingClientLogger logger = null; private String source; - //TODO this should be the only constructor. There should be no okHttp exposure. public FhirRequestBuilder(HTTPRequest httpRequest, String source) { this.source = source; this.httpRequest = httpRequest; 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 index 0ae20cfff0..211c7ac982 100644 --- 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 @@ -126,10 +126,9 @@ public HTTPResult httpCall(HTTPRequest httpRequest) throws IOException { } OkHttpClient okHttpClient = getOkHttpClient(); //TODO check and throw based on httpRequest: - // if (!ManagedWebAccess.inAllowedPaths(url)) { - // throw new IOException("The pathname '"+url+"' cannot be accessed by policy"); - // } - //TODO add auth headers to 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: From cc00bb903cfb3e7a141974a326a3433e1855bf51 Mon Sep 17 00:00:00 2001 From: dotasek Date: Fri, 8 Nov 2024 11:26:43 -0500 Subject: [PATCH 35/42] Move tests to utilities. Clarify missing functionality --- .../java/org/hl7/fhir/utilities/http/ManagedWebAccess.java | 1 + .../hl7/fhir/utilities/http/ManagedWebAccessAuthTests.java | 6 ++++++ 2 files changed, 7 insertions(+) rename {org.hl7.fhir.r5 => org.hl7.fhir.utilities}/src/test/java/org/hl7/fhir/utilities/http/ManagedWebAccessAuthTests.java (97%) 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 ff7df4e945..a075325a13 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 @@ -68,6 +68,7 @@ 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; diff --git a/org.hl7.fhir.r5/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 similarity index 97% rename from org.hl7.fhir.r5/src/test/java/org/hl7/fhir/utilities/http/ManagedWebAccessAuthTests.java rename to org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/http/ManagedWebAccessAuthTests.java index 94f70a3ccc..f57d93988f 100644 --- a/org.hl7.fhir.r5/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 @@ -207,4 +207,10 @@ private ServerDetailsPOJO getApiKeyAuthServerPojo() { 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 From fb6a22c3b15fb3a205dde028f0658de5fe0bdcb5 Mon Sep 17 00:00:00 2001 From: dotasek Date: Fri, 8 Nov 2024 14:29:40 -0500 Subject: [PATCH 36/42] Clean up comments and JavaDoc --- .../fhir/dstu2/utils/client/ClientUtils.java | 40 +++++++++---------- .../dstu2/utils/client/FHIRToolingClient.java | 15 ++++--- .../dstu3/utils/client/FHIRToolingClient.java | 18 +++++---- .../client/network/FhirRequestBuilder.java | 6 +-- .../r4/utils/client/FHIRToolingClient.java | 18 +++++---- .../client/network/FhirRequestBuilder.java | 10 ++--- .../client/network/FhirRequestBuilder.java | 8 +--- .../client/network/FhirRequestBuilder.java | 2 +- 8 files changed, 61 insertions(+), 56 deletions(-) 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 c880a948e5..000173f23a 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 @@ -81,17 +81,10 @@ public class ClientUtils { private static boolean debugging = false; -/* - @Getter - @Setter - private HttpHost proxy; -*/ @Getter @Setter private int timeout = 5000; - - @Setter @Getter private ToolingClientLogger logger; @@ -233,8 +226,12 @@ protected ResourceRequest issueResourceRequest(String re return issueResourceRequest(resourceFormat, request, Collections.emptyList(), timeoutLoading); } /** - * @param resourceFormat - * @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, HTTPRequest request, @Nonnull Iterable headers, int timeoutLoading) { @@ -254,18 +251,21 @@ protected ResourceRequest issueResourceRequest(String re } /** - * Method adds required request headers. TODO handle JSON request as well. + * Get required headers for FHIR requests. * - * @param format + * @param httpRequest the request + * @param format the expected format */ 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 format + * @param httpRequest the request + * @param format the expected format + * @param headers any additional headers to add */ protected Iterable getFhirHeaders(HTTPRequest httpRequest, String format, Iterable headers) { List configuredHeaders = new ArrayList<>(); @@ -325,8 +325,8 @@ protected HTTPResult sendRequest(HTTPRequest request) { /** * 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(HTTPResult response, String format) { @@ -353,8 +353,8 @@ protected T unmarshalReference(HTTPResult response, String /** * Unmarshals Bundle from response stream. * - * @param response - * @return + * @param response The response from the server + * @return The unmarshalled Bundle */ protected Bundle unmarshalFeed(HTTPResult response, String format) { Bundle feed = null; @@ -556,10 +556,10 @@ private byte[] encodeFormSubmission(Map parameters, String resou } /** - * Method posts request payload + * Send an HTTP Post/Put Payload * - * @param request - * @return + * @param request The request to be sent + * @return The response from the server */ protected HTTPResult sendPayload(HTTPRequest request) { HTTPResult response = null; 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 eafabce2c4..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 @@ -402,10 +402,13 @@ public OperationOutcome validate(Class resourceClass, T /** - * 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) { @@ -419,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; 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 8e4706409c..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 @@ -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; 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 f087c81b44..8889e1c7c6 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 @@ -56,9 +56,9 @@ public FhirRequestBuilder(HTTPRequest httpRequest, String source) { /** * Adds necessary default headers, formatting headers, and any passed in {@link HTTPHeader}s to the passed in - * {@link okhttp3.Request.Builder} + * {@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 HTTPHeader}s to add to the request. */ @@ -228,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". 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 59eec77434..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 @@ -356,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) { @@ -370,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; 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 72da9e83f9..57722a3a1a 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 @@ -28,9 +28,7 @@ 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 final HTTPRequest httpRequest; private String resourceFormat = null; private Iterable headers = null; @@ -62,9 +60,9 @@ public FhirRequestBuilder(HTTPRequest httpRequest, String source) { /** * Adds necessary default headers, formatting headers, and any passed in - * {@link HTTPHeader}s 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 HTTPHeader}s to add to the request. */ @@ -269,7 +267,7 @@ protected Bundle unmarshalFeed(HTTPResult response, String format) { /** * 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.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 74a4c3354a..f8ce9e192d 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 @@ -102,17 +102,13 @@ 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. */ - /** - * Extracts the 'location' header from. If no value for 'location' exists, the - * value for 'content-location' is returned. If neither header exists, we return null. - */ protected static String getLocationHeader(Iterable headers) { String locationHeader = HTTPHeaderUtil.getSingleHeader(headers, LOCATION_HEADER); 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 77e1ed4430..dfd65ba572 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 @@ -250,7 +250,7 @@ protected Bundle unmarshalFeed(HTTPResult response, String format) { /** * 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". From d724a27d958a90798d0d2cef903fcd1edef6aa21 Mon Sep 17 00:00:00 2001 From: dotasek Date: Fri, 8 Nov 2024 14:35:36 -0500 Subject: [PATCH 37/42] Final rename --- .../hl7/fhir/convertors/misc/ICD11Generator.java | 3 +-- .../hl7/fhir/dstu2/utils/client/ClientUtils.java | 10 +++++----- .../utils/client/network/FhirRequestBuilder.java | 8 ++++---- .../utils/client/network/FhirRequestBuilder.java | 8 ++++---- .../r4b/terminologies/TerminologyCacheManager.java | 10 +--------- .../utils/client/network/FhirRequestBuilder.java | 8 ++++---- .../r5/terminologies/TerminologyCacheManager.java | 10 +--------- .../utils/client/network/FhirRequestBuilder.java | 8 ++++---- .../hl7/fhir/utilities/http/ManagedWebAccess.java | 14 +++++++------- .../org/hl7/fhir/utilities/npm/PackageClient.java | 8 ++++---- 10 files changed, 35 insertions(+), 52 deletions(-) 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 cb5d85951c..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().withHeader("API-Version", "v2").withHeader("Accept-Language", "en").get(source,"application/json"); + 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.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 000173f23a..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 @@ -102,8 +102,8 @@ public class ClientUtils { private String contentLanguage; private final TimeUnit timeoutUnit = TimeUnit.MILLISECONDS; - protected ManagedFhirWebAccessor getManagedWebAccessBuilder() { - return ManagedWebAccess.fhirBuilder().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); + protected ManagedFhirWebAccessor getManagedWebAccessor() { + return ManagedWebAccess.fhirAccessor().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); } public ResourceRequest issueOptionsRequest(URI optionsUri, String resourceFormat, @@ -241,7 +241,7 @@ protected ResourceRequest issueResourceRequest(String re Iterable configuredHeaders = getFhirHeaders(request, resourceFormat, headers); try { - HTTPResult response = getManagedWebAccessBuilder().httpCall(request.withHeaders(configuredHeaders)); + HTTPResult response = getManagedWebAccessor().httpCall(request.withHeaders(configuredHeaders)); T resource = unmarshalReference(response, resourceFormat); return new ResourceRequest(resource, response.getCode(), getLocationHeader(response.getHeaders())); } catch (IOException ioe) { @@ -312,7 +312,7 @@ protected HTTPResult sendRequest(HTTPRequest request) { HTTPResult response = null; try { - response = getManagedWebAccessBuilder().httpCall(request); + response = getManagedWebAccessor().httpCall(request); return response; } catch (IOException ioe) { if (ClientUtils.debugging) { @@ -565,7 +565,7 @@ protected HTTPResult sendPayload(HTTPRequest request) { HTTPResult response = null; try { - response = getManagedWebAccessBuilder().httpCall(request); + response = getManagedWebAccessor().httpCall(request); } catch (IOException ioe) { throw new EFhirClientException("Error sending HTTP Post/Put Payload: " + ioe.getMessage(), ioe); } 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 8889e1c7c6..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 @@ -114,8 +114,8 @@ protected static String getLocationHeader(Iterable headers) { return HTTPHeaderUtil.getSingleHeader(headers, CONTENT_LOCATION_HEADER); } - protected ManagedFhirWebAccessor getManagedWebAccessBuilder() { - return ManagedWebAccess.fhirBuilder().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); + protected ManagedFhirWebAccessor getManagedWebAccessor() { + return ManagedWebAccess.fhirAccessor().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); } public FhirRequestBuilder withResourceFormat(String resourceFormat) { @@ -151,14 +151,14 @@ public FhirRequestBuilder withTimeout(long timeout, TimeUnit unit) { public ResourceRequest execute() throws IOException { HTTPRequest requestWithHeaders = formatHeaders(httpRequest, resourceFormat, headers); - HTTPResult response = getManagedWebAccessBuilder().httpCall(requestWithHeaders); + HTTPResult response = getManagedWebAccessor().httpCall(requestWithHeaders); T resource = unmarshalReference(response, resourceFormat); return new ResourceRequest(resource, response.getCode(), getLocationHeader(response.getHeaders())); } public Bundle executeAsBatch() throws IOException { HTTPRequest requestWithHeaders = formatHeaders(httpRequest, resourceFormat, null); - HTTPResult response = getManagedWebAccessBuilder().httpCall(requestWithHeaders); + HTTPResult response = getManagedWebAccessor().httpCall(requestWithHeaders); return unmarshalFeed(response, resourceFormat); } 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 57722a3a1a..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 @@ -120,8 +120,8 @@ protected static String getLocationHeader(Iterable headers) { return HTTPHeaderUtil.getSingleHeader(headers, CONTENT_LOCATION_HEADER); } - protected ManagedFhirWebAccessor getManagedWebAccessBuilder() { - return ManagedWebAccess.fhirBuilder().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); + protected ManagedFhirWebAccessor getManagedWebAccessor() { + return ManagedWebAccess.fhirAccessor().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); } public FhirRequestBuilder withResourceFormat(String resourceFormat) { @@ -158,7 +158,7 @@ public FhirRequestBuilder withTimeout(long timeout, TimeUnit unit) { public ResourceRequest execute() throws IOException { HTTPRequest requestWithHeaders = formatHeaders(httpRequest, resourceFormat, headers); - HTTPResult response = getManagedWebAccessBuilder().httpCall(requestWithHeaders); + HTTPResult response = getManagedWebAccessor().httpCall(requestWithHeaders); T resource = unmarshalReference(response, resourceFormat, null); return new ResourceRequest(resource, response.getCode(), getLocationHeader(response.getHeaders())); @@ -166,7 +166,7 @@ public ResourceRequest execute() throws IOException { public Bundle executeAsBatch() throws IOException { HTTPRequest requestWithHeaders = formatHeaders(httpRequest, resourceFormat, null); - HTTPResult response = getManagedWebAccessBuilder().httpCall(requestWithHeaders); + HTTPResult response = getManagedWebAccessor().httpCall(requestWithHeaders); return unmarshalFeed(response, resourceFormat); } 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 c717f43479..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,7 +148,7 @@ 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)) .put(url, bs.toByteArray(), null, "application/zip"); if (res.getCode() >= 300) { 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 f8ce9e192d..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 @@ -118,8 +118,8 @@ protected static String getLocationHeader(Iterable headers) { return HTTPHeaderUtil.getSingleHeader(headers, CONTENT_LOCATION_HEADER); } - protected ManagedFhirWebAccessor getManagedWebAccessBuilder() { - return ManagedWebAccess.fhirBuilder().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); + protected ManagedFhirWebAccessor getManagedWebAccessor() { + return ManagedWebAccess.fhirAccessor().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); } public FhirRequestBuilder withResourceFormat(String resourceFormat) { @@ -155,14 +155,14 @@ public FhirRequestBuilder withTimeout(long timeout, TimeUnit unit) { public ResourceRequest execute() throws IOException { HTTPRequest requestWithHeaders = formatHeaders(httpRequest, resourceFormat, headers); - HTTPResult response = getManagedWebAccessBuilder().httpCall(requestWithHeaders); + HTTPResult response = getManagedWebAccessor().httpCall(requestWithHeaders); T resource = unmarshalReference(response, resourceFormat, null); return new ResourceRequest(resource, response.getCode(), getLocationHeader(response.getHeaders())); } public Bundle executeAsBatch() throws IOException { HTTPRequest requestWithHeaders = formatHeaders(httpRequest, resourceFormat, null); - HTTPResult response = getManagedWebAccessBuilder().httpCall(requestWithHeaders);return unmarshalFeed(response, resourceFormat); + HTTPResult response = getManagedWebAccessor().httpCall(requestWithHeaders);return unmarshalFeed(response, resourceFormat); } /** 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 b58751dbe7..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,7 +148,7 @@ 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)) .put(url, bs.toByteArray(), null, "application/zip"); 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 dfd65ba572..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 @@ -105,8 +105,8 @@ protected static boolean hasError(OperationOutcome oo) { || issue.getSeverity() == OperationOutcome.IssueSeverity.FATAL)); } - protected ManagedFhirWebAccessor getManagedWebAccessBuilder() { - return ManagedWebAccess.fhirBuilder().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); + protected ManagedFhirWebAccessor getManagedWebAccessor() { + return ManagedWebAccess.fhirAccessor().withRetries(retryCount).withTimeout(timeout, timeoutUnit).withLogger(logger); } public FhirRequestBuilder withResourceFormat(String resourceFormat) { @@ -142,14 +142,14 @@ public FhirRequestBuilder withTimeout(long timeout, TimeUnit unit) { public ResourceRequest execute() throws IOException { HTTPRequest requestWithHeaders = formatHeaders(httpRequest, resourceFormat, headers); - HTTPResult response = getManagedWebAccessBuilder().httpCall(requestWithHeaders);//getHttpClient().newCall(httpRequest.build()).execute(); + HTTPResult response = getManagedWebAccessor().httpCall(requestWithHeaders);//getHttpClient().newCall(httpRequest.build()).execute(); T resource = unmarshalReference(response, resourceFormat, null); return new ResourceRequest(resource, response.getCode(), getLocationHeader(response.getHeaders())); } public Bundle executeAsBatch() throws IOException { HTTPRequest requestWithHeaders = formatHeaders(httpRequest, resourceFormat, null); - HTTPResult response = getManagedWebAccessBuilder().httpCall(requestWithHeaders); + HTTPResult response = getManagedWebAccessor().httpCall(requestWithHeaders); return unmarshalFeed(response, resourceFormat); } 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 a075325a13..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 @@ -104,32 +104,32 @@ public static void setUserAgent(String userAgent) { ManagedWebAccess.userAgent = userAgent; } - public static ManagedWebAccessor builder() { + public static ManagedWebAccessor accessor() { return new ManagedWebAccessor(userAgent, serverAuthDetails); } - public static ManagedFhirWebAccessor fhirBuilder() { + 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().get(url, accept); + return accessor().get(url, accept); } public static HTTPResult post(String url, byte[] content, String contentType, String accept) throws IOException { - return builder().post(url, content, contentType, accept); + return accessor().post(url, content, contentType, accept); } public static HTTPResult put(String url, byte[] content, String contentType, String accept) throws IOException { - return builder().put(url, content, contentType, accept); + return accessor().put(url, content, contentType, accept); } public static HTTPResult httpCall(HTTPRequest httpRequest) throws IOException { - return fhirBuilder().httpCall(httpRequest); + return fhirAccessor().httpCall(httpRequest); } public static void loadFromFHIRSettings() { 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 eb7e8518ee..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 @@ -175,13 +175,13 @@ public Date getNewPackages(Date lastCalled, List updates) { } private InputStream fetchUrl(String source, String accept) throws IOException { - ManagedWebAccessor client = ManagedWebAccess.builder(); + 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, accept); + HTTPResult res = webAccessor.get(source, accept); res.checkThrowException(); return new ByteArrayInputStream(res.getContent()); } From 8b43a95aa80e214e0a90d0ac67a52558083c385e Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Sat, 9 Nov 2024 06:33:07 +1030 Subject: [PATCH 38/42] update vsac access code --- .../main/java/org/hl7/fhir/convertors/misc/VSACImporter.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 432a4dc1bd..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 @@ -46,10 +46,10 @@ 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<>(); @@ -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)); } From 765ba8baed54460b3eb98216600f23d1fd1435b1 Mon Sep 17 00:00:00 2001 From: dotasek Date: Fri, 8 Nov 2024 15:22:50 -0500 Subject: [PATCH 39/42] Fix get bytes for -1 or chunked content --- .../org/hl7/fhir/utilities/http/ManagedFhirWebAccessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 211c7ac982..b12c841757 100644 --- 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 @@ -143,7 +143,7 @@ public HTTPResult httpCall(HTTPRequest httpRequest) throws IOException { } 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)); + 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) { From 60acdd0b8841b8aa026dc36209164fdadba904e9 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Sat, 9 Nov 2024 18:39:12 +1030 Subject: [PATCH 40/42] Add mode to web access --- .../convertors/analytics/PackageVisitor.java | 4 +-- .../fhir/convertors/misc/ICD11Generator.java | 2 +- .../fhir/r4/conformance/ProfileComparer.java | 2 +- .../TerminologyCacheManager.java | 4 +-- .../hl7/fhir/r5/elementmodel/SHLParser.java | 2 +- .../TerminologyCacheManager.java | 4 +-- .../http/ManagedFhirWebAccessor.java | 4 +-- .../fhir/utilities/http/ManagedWebAccess.java | 26 +++++++++---------- .../utilities/http/ManagedWebAccessUtils.java | 8 ++++-- .../utilities/http/ManagedWebAccessor.java | 12 ++++----- .../http/ManagedWebAccessorBase.java | 6 ++++- .../npm/FilesystemPackageCacheManager.java | 4 +-- .../hl7/fhir/utilities/npm/NpmPackage.java | 2 +- .../hl7/fhir/utilities/npm/PackageClient.java | 2 +- .../utilities/settings/ServerDetailsPOJO.java | 3 +++ .../http/ManagedWebAccessAuthTests.java | 3 +++ .../org/hl7/fhir/validation/IgLoader.java | 2 +- .../java/org/hl7/fhir/validation/Scanner.java | 2 +- .../hl7/fhir/validation/ValidationEngine.java | 4 +-- .../validation/cli/utils/ProfileLoader.java | 2 +- .../validation/tests/ValidationTests.java | 2 +- 21 files changed, 57 insertions(+), 43 deletions(-) diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/analytics/PackageVisitor.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/analytics/PackageVisitor.java index 442bdc68c1..c8263ed960 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/analytics/PackageVisitor.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/analytics/PackageVisitor.java @@ -241,7 +241,7 @@ private void processCurrentPackage(String url, String pid, Set cpidSet, File co = ManagedFileAccess.file(Utilities.path(cache, pid+"."+manifest.asString("date")+".tgz")); if (!co.exists()) { - HTTPResult res = ManagedWebAccess.get(repo+"/package.tgz?nocache=" + System.currentTimeMillis()); + HTTPResult res = ManagedWebAccess.get("web", repo+"/package.tgz?nocache=" + System.currentTimeMillis()); res.checkThrowException(); TextFile.bytesToFile(res.getContent(), co); } @@ -338,7 +338,7 @@ private void processFeed(Set list, String str) throws IOException, Parse System.out.println("Feed "+str); try { - HTTPResult res = ManagedWebAccess.get(str+"?nocache=" + System.currentTimeMillis()); + HTTPResult res = ManagedWebAccess.get("web", str+"?nocache=" + System.currentTimeMillis()); res.checkThrowException(); Document xml = XMLUtil.parseToDom(res.getContent()); for (Element channel : XMLUtil.getNamedChildren(xml.getDocumentElement(), "channel")) { 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 0e7a391d2e..564f882b72 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 @@ -394,7 +394,7 @@ private CodeSystem makeEntityCodeSystem() { private JsonObject fetchJson(String source) throws IOException { - HTTPResult res = ManagedWebAccess.accessor().withHeader("API-Version", "v2").withHeader("Accept-Language", "en").get(source,"application/json"); + HTTPResult res = ManagedWebAccess.accessor("web").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.r4/src/main/java/org/hl7/fhir/r4/conformance/ProfileComparer.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/conformance/ProfileComparer.java index f9d34478de..32b490c2e0 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/conformance/ProfileComparer.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/conformance/ProfileComparer.java @@ -1384,7 +1384,7 @@ private String cachedFetch(String id, String source) throws IOException { if (f.exists()) return TextFile.fileToString(f); - HTTPResult res = ManagedWebAccess.get(source); + HTTPResult res = ManagedWebAccess.get("web", source); res.checkThrowException(); String result = TextFile.bytesToString(res.getContent()); TextFile.stringToFile(result, f); 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 819d36fc2c..f06a762bff 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 @@ -79,7 +79,7 @@ private void fillCache(String source) throws IOException { try { System.out.println("Initialise terminology cache from " + source); - HTTPResult res = ManagedWebAccess.get(source + "?nocache=" + System.currentTimeMillis()); + HTTPResult res = ManagedWebAccess.get("web", source + "?nocache=" + System.currentTimeMillis()); res.checkThrowException(); unzip(new ByteArrayInputStream(res.getContent()), cacheFolder); } catch (Exception e) { @@ -148,7 +148,7 @@ 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.accessor() + HTTPResult res = ManagedWebAccess.accessor("web") .withBasicAuth(token.substring(0, token.indexOf(':')), token.substring(token.indexOf(':') + 1)) .put(url, bs.toByteArray(), null, "application/zip"); if (res.getCode() >= 300) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHLParser.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHLParser.java index bbbf0e8514..71297b9fe6 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHLParser.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHLParser.java @@ -299,7 +299,7 @@ private HTTPResult fetchManifest() throws IOException { JsonObject j = new JsonObject(); j.add("recipient", "FHIR Validator"); - HTTPResult res = ManagedWebAccess.post(url, org.hl7.fhir.utilities.json.parser.JsonParser.composeBytes(j), "application/json", "application/json"); + HTTPResult res = ManagedWebAccess.post("web", url, org.hl7.fhir.utilities.json.parser.JsonParser.composeBytes(j), "application/json", "application/json"); res.checkThrowException(); return res; } 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 daa378a5b6..3cc569e68f 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 @@ -78,7 +78,7 @@ private void fillCache(String source) throws IOException { try { System.out.println("Initialise terminology cache from "+source); - HTTPResult res = ManagedWebAccess.get(source+"?nocache=" + System.currentTimeMillis()); + HTTPResult res = ManagedWebAccess.get("web", source+"?nocache=" + System.currentTimeMillis()); res.checkThrowException(); unzip(new ByteArrayInputStream(res.getContent()), cacheFolder); } catch (Exception e) { @@ -148,7 +148,7 @@ 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.accessor() + HTTPResult res = ManagedWebAccess.accessor("web") .withBasicAuth(token.substring(0, token.indexOf(':')), token.substring(token.indexOf(':') + 1)) .put(url, bs.toByteArray(), null, "application/zip"); 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 index b12c841757..501ad5fa5a 100644 --- 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 @@ -45,7 +45,7 @@ public ManagedFhirWebAccessor withLogger(ToolingClientLogger logger) { public ManagedFhirWebAccessor(String userAgent, List serverAuthDetails) { - super(userAgent, serverAuthDetails); + super("fhir", userAgent, serverAuthDetails); this.timeout = 5000; this.timeoutUnit = TimeUnit.MILLISECONDS; } @@ -88,7 +88,7 @@ protected HTTPRequest requestWithManagedHeaders(HTTPRequest httpRequest) { } } } else { - ServerDetailsPOJO settings = ManagedWebAccessUtils.getServer(httpRequest.getUrl().toString(), getServerAuthDetails()); + ServerDetailsPOJO settings = ManagedWebAccessUtils.getServer(getMode(), httpRequest.getUrl().toString(), getServerAuthDetails()); if (settings != null) { switch (settings.getAuthenticationType()) { case "basic": 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 3ebb85c20d..df80a2dc3d 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 @@ -52,9 +52,9 @@ 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; + HTTPResult get(String mode, String url, String accept, Map headers) throws IOException; + HTTPResult post(String mode, String url, byte[] bytes, String contentType, String accept, Map headers) throws IOException; + HTTPResult put(String mode, String url, byte[] bytes, String contentType, String accept, Map headers) throws IOException; } public interface IFhirWebAccessor { @@ -104,28 +104,28 @@ public static void setUserAgent(String userAgent) { ManagedWebAccess.userAgent = userAgent; } - public static ManagedWebAccessor accessor() { - return new ManagedWebAccessor(userAgent, serverAuthDetails); + public static ManagedWebAccessor accessor(String mode) { + return new ManagedWebAccessor(mode, userAgent, serverAuthDetails); } public static ManagedFhirWebAccessor fhirAccessor() { return new ManagedFhirWebAccessor(userAgent, serverAuthDetails); } - public static HTTPResult get(String url) throws IOException { - return accessor().get(url); + public static HTTPResult get(String mode, String url) throws IOException { + return accessor(mode).get(url); } - public static HTTPResult get(String url, String accept) throws IOException { - return accessor().get(url, accept); + public static HTTPResult get(String mode, String url, String accept) throws IOException { + return accessor(mode).get(url, accept); } - public static HTTPResult post(String url, byte[] content, String contentType, String accept) throws IOException { - return accessor().post(url, content, contentType, accept); + public static HTTPResult post(String mode, String url, byte[] content, String contentType, String accept) throws IOException { + return accessor(mode).post(url, content, contentType, accept); } - public static HTTPResult put(String url, byte[] content, String contentType, String accept) throws IOException { - return accessor().put(url, content, contentType, accept); + public static HTTPResult put(String mode, String url, byte[] content, String contentType, String accept) throws IOException { + return accessor(mode).put(url, content, contentType, accept); } public static HTTPResult httpCall(HTTPRequest httpRequest) throws IOException { 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 index a973536335..560e374ae9 100644 --- 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 @@ -4,14 +4,18 @@ public class ManagedWebAccessUtils { - public static ServerDetailsPOJO getServer(String url, Iterable serverAuthDetails) { + public static ServerDetailsPOJO getServer(String mode, String url, Iterable serverAuthDetails) { if (serverAuthDetails != null) { for (ServerDetailsPOJO t : serverAuthDetails) { - if (url.startsWith(t.getUrl())) { + if (url.startsWith(t.getUrl()) && modesMatch(mode, t.getMode())) { return t; } } } return null; } + + private static boolean modesMatch(String criteria, String value) { + return criteria == null || value == null || criteria.equals(value); + } } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessor.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessor.java index 4c364a2f59..e2b6d64391 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessor.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/http/ManagedWebAccessor.java @@ -14,8 +14,8 @@ */ public class ManagedWebAccessor extends ManagedWebAccessorBase { - public ManagedWebAccessor(String userAgent, List serverAuthDetails) { - super(userAgent, serverAuthDetails); + public ManagedWebAccessor(String mode, String userAgent, List serverAuthDetails) { + super(mode, userAgent, serverAuthDetails); } private Map newHeaders() { @@ -66,7 +66,7 @@ private SimpleHTTPClient setupClient(String url) throws IOException { } } } else { - ServerDetailsPOJO settings = ManagedWebAccessUtils.getServer(url, getServerAuthDetails()); + ServerDetailsPOJO settings = ManagedWebAccessUtils.getServer(getMode(), url, getServerAuthDetails()); if (settings != null) { switch (settings.getAuthenticationType()) { case "basic" : @@ -101,7 +101,7 @@ public HTTPResult get(String url, String accept) throws IOException { SimpleHTTPClient client = setupClient(url); return client.get(url, accept); case MANAGED: - return ManagedWebAccess.getAccessor().get(url, accept, newHeaders()); + return ManagedWebAccess.getAccessor().get(getMode(), url, accept, newHeaders()); case PROHIBITED: throw new IOException("Access to the internet is not allowed by local security policy"); default: @@ -119,7 +119,7 @@ public HTTPResult post(String url, byte[] content, String contentType, String ac SimpleHTTPClient client = setupClient(url); return client.post(url, contentType, content, accept); case MANAGED: - return ManagedWebAccess.getAccessor().post(url, content, contentType, accept, newHeaders()); + return ManagedWebAccess.getAccessor().post(getMode(), url, content, contentType, accept, newHeaders()); case PROHIBITED: throw new IOException("Access to the internet is not allowed by local security policy"); default: @@ -137,7 +137,7 @@ public HTTPResult put(String url, byte[] content, String contentType, String acc SimpleHTTPClient client = setupClient(url); return client.put(url, contentType, content, accept); case MANAGED: - return ManagedWebAccess.getAccessor().put(url, content, contentType, accept, newHeaders()); + return ManagedWebAccess.getAccessor().put(getMode(), url, content, contentType, accept, newHeaders()); case PROHIBITED: throw new IOException("Access to the internet is not allowed by local security policy"); default: 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 index 67e4df641d..f7ac120e1d 100644 --- 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 @@ -8,6 +8,9 @@ import java.util.Map; public abstract class ManagedWebAccessorBase> { + @Getter + private final String mode; + @Getter private final String userAgent; @Getter @@ -24,7 +27,8 @@ public abstract class ManagedWebAccessorBase @Getter private final Map headers = new HashMap<>(); - public ManagedWebAccessorBase(String userAgent, List serverAuthDetails) { + public ManagedWebAccessorBase(String mode, String userAgent, List serverAuthDetails) { + this.mode = mode; this.userAgent = userAgent; this.serverAuthDetails = serverAuthDetails; } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/FilesystemPackageCacheManager.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/FilesystemPackageCacheManager.java index e8eca8f804..fb5df4aa7c 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/FilesystemPackageCacheManager.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/FilesystemPackageCacheManager.java @@ -729,7 +729,7 @@ private InputStreamWithSrc fetchSourceFromUrlSpecific(String url) { private InputStream fetchFromUrlSpecific(String source, boolean optional) throws FHIRException { try { - HTTPResult res = ManagedWebAccess.get(source); + HTTPResult res = ManagedWebAccess.get("web", source); res.checkThrowException(); return new ByteArrayInputStream(res.getContent()); } catch (Exception e) { @@ -863,7 +863,7 @@ private void checkBuildLoaded() { private void loadFromBuildServer() throws IOException { - HTTPResult res = ManagedWebAccess.get("https://build.fhir.org/ig/qas.json?nocache=" + System.currentTimeMillis()); + HTTPResult res = ManagedWebAccess.get("web", "https://build.fhir.org/ig/qas.json?nocache=" + System.currentTimeMillis()); res.checkThrowException(); buildInfo = (JsonArray) JsonParser.parse(TextFile.bytesToString(res.getContent())); diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/NpmPackage.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/NpmPackage.java index 54c8ee9172..6dad79d3bf 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/NpmPackage.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/NpmPackage.java @@ -1480,7 +1480,7 @@ public Date dateAsDate() { } public static NpmPackage fromUrl(String source) throws IOException { - HTTPResult res = ManagedWebAccess.get(source+"?nocache=" + System.currentTimeMillis()); + HTTPResult res = ManagedWebAccess.get("npm", source+"?nocache=" + System.currentTimeMillis()); res.checkThrowException(); return fromPackage(new ByteArrayInputStream(res.getContent())); } 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 6f6feeb849..9c44e81b74 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 @@ -175,7 +175,7 @@ public Date getNewPackages(Date lastCalled, List updates) { } private InputStream fetchUrl(String source, String accept) throws IOException { - ManagedWebAccessor webAccessor = ManagedWebAccess.accessor(); + ManagedWebAccessor webAccessor = ManagedWebAccess.accessor("web"); if (server.getAuthenticationMode() == HTTPAuthenticationMode.TOKEN) { webAccessor.withToken(server.getToken()); } else if (server.getAuthenticationMode() == HTTPAuthenticationMode.BASIC) { diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/settings/ServerDetailsPOJO.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/settings/ServerDetailsPOJO.java index 5d716bcfe4..e2c5784dc6 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/settings/ServerDetailsPOJO.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/settings/ServerDetailsPOJO.java @@ -11,6 +11,9 @@ @AllArgsConstructor public class ServerDetailsPOJO { + // what kind of HTTP server this is - a FHIR tx server, an NPM server, or a general web server + String mode; + String url; // possible values: none, basic, token, apikey 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 index f57d93988f..e57f4118a8 100644 --- 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 @@ -162,6 +162,7 @@ public void testBasicAuthFromSettings() throws IOException, InterruptedException private ServerDetailsPOJO getBasicAuthServerPojo() { return new ServerDetailsPOJO( + null, server.url("").toString(), "basic", "dummyServerType", @@ -181,6 +182,7 @@ public void testTokenAuthFromSettings() throws IOException, InterruptedException private ServerDetailsPOJO getTokenAuthServerPojo() { return new ServerDetailsPOJO( + null, server.url("").toString(), "token", "dummyServerType", @@ -200,6 +202,7 @@ public void testApiKeyAuthFromSettings() throws IOException, InterruptedExceptio private ServerDetailsPOJO getApiKeyAuthServerPojo() { return new ServerDetailsPOJO( + null, server.url("").toString(), "apikey", "dummyServerType", diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java index 22c05b678d..1462a83680 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java @@ -430,7 +430,7 @@ private String loadPackageForVersion(InputStream stream) throws FHIRException, I private InputStream fetchFromUrlSpecific(String source, boolean optional) throws FHIRException, IOException { try { - HTTPResult res = ManagedWebAccess.get(source + "?nocache=" + System.currentTimeMillis()); + HTTPResult res = ManagedWebAccess.get("web", source + "?nocache=" + System.currentTimeMillis()); res.checkThrowException(); return new ByteArrayInputStream(res.getContent()); } catch (IOException e) { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/Scanner.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/Scanner.java index d62bd106ce..856f566687 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/Scanner.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/Scanner.java @@ -319,7 +319,7 @@ protected OperationOutcome exceptionToOutcome(Exception ex) throws IOException, } protected void download(String address, String filename) throws IOException { - HTTPResult res = ManagedWebAccess.get(address); + HTTPResult res = ManagedWebAccess.get("web", address); res.checkThrowException(); TextFile.bytesToFile(res.getContent(), filename); } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java index 863e02715e..46738614a9 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java @@ -942,7 +942,7 @@ public void handleOutput(Resource r, String output, String version) throws FHIRE if (output.startsWith("http://")) { ByteArrayOutputStream bs = new ByteArrayOutputStream(); handleOutputToStream(r, output, bs, version); - HTTPResult res = ManagedWebAccess.post(output, bs.toByteArray(), "application/fhir+xml", "application/fhir+xml"); + HTTPResult res = ManagedWebAccess.post("web", output, bs.toByteArray(), "application/fhir+xml", "application/fhir+xml"); res.checkThrowException(); } else { FileOutputStream s = ManagedFileAccess.outStream(output); @@ -1099,7 +1099,7 @@ public FilesystemPackageCacheManager getPcm() throws IOException { @Override public byte[] fetchRaw(IResourceValidator validator, String source) throws IOException { - HTTPResult res = ManagedWebAccess.get(source); + HTTPResult res = ManagedWebAccess.get("web", source); res.checkThrowException(); return res.getContent(); } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/ProfileLoader.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/ProfileLoader.java index b73684eb6f..2ace9d6d44 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/ProfileLoader.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/ProfileLoader.java @@ -25,7 +25,7 @@ public static byte[] loadProfileSource(String src) throws FHIRException, IOExcep private static byte[] loadProfileFromUrl(String src) throws FHIRException { try { - HTTPResult res = ManagedWebAccess.get(src + "?nocache=" + System.currentTimeMillis()); + HTTPResult res = ManagedWebAccess.get("web", src + "?nocache=" + System.currentTimeMillis()); res.checkThrowException(); return res.getContent(); } catch (Exception e) { diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java index e4b712a708..09c87d7f09 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java @@ -858,7 +858,7 @@ public static void saveWhenDone() throws IOException { @Override public byte[] fetchRaw(IResourceValidator validator, String source) throws MalformedURLException, IOException { - HTTPResult res = ManagedWebAccess.get(source); + HTTPResult res = ManagedWebAccess.get("web", source); res.checkThrowException(); return res.getContent(); } From cac080b5bdfe249d661511adc3f72506d96aef90 Mon Sep 17 00:00:00 2001 From: dotasek Date: Mon, 11 Nov 2024 09:23:59 -0500 Subject: [PATCH 41/42] Fix merge issue. --- .../java/org/hl7/fhir/utilities/http/ManagedWebAccess.java | 4 ---- 1 file changed, 4 deletions(-) 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 5e93d3f046..df80a2dc3d 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 @@ -61,10 +61,6 @@ public interface IFhirWebAccessor { HTTPResult httpCall(HTTPRequest httpRequest); } - 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 From 4ee4a89a7604345620826b115486d9c820dc437b Mon Sep 17 00:00:00 2001 From: dotasek Date: Mon, 11 Nov 2024 11:06:09 -0500 Subject: [PATCH 42/42] Fix refactor misses --- .../hl7/fhir/convertors/misc/CKMImporter.java | 2 +- .../org/hl7/fhir/r4/test/VSACClientTest.java | 18 ++++++ .../hl7/fhir/r5/elementmodel/SHCParser.java | 17 ++++++ .../hl7/fhir/r5/elementmodel/SHLParser.java | 2 +- .../utilities/json/JsonTrackingParser.java | 60 +++++++++---------- .../utilities/json/parser/JsonParser.java | 2 +- .../org/hl7/fhir/validation/IgLoader.java | 4 +- .../hl7/fhir/validation/ipa/IPAValidator.java | 2 +- 8 files changed, 71 insertions(+), 36 deletions(-) create mode 100644 org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/test/VSACClientTest.java diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/CKMImporter.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/CKMImporter.java index e4d04ada77..e6581f81e4 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/CKMImporter.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/CKMImporter.java @@ -126,7 +126,7 @@ private void processArchetype(String id) throws Exception { private Document loadXml(String address) throws Exception { - HTTPResult res = ManagedWebAccess.get(address, "application/xml"); + HTTPResult res = ManagedWebAccess.get("web", address, "application/xml"); res.checkThrowException(); InputStream xml = new ByteArrayInputStream(res.getContent()); diff --git a/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/test/VSACClientTest.java b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/test/VSACClientTest.java new file mode 100644 index 0000000000..97937fa23a --- /dev/null +++ b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/test/VSACClientTest.java @@ -0,0 +1,18 @@ +package org.hl7.fhir.r4.test; + +import org.hl7.fhir.r4.model.CapabilityStatement; +import org.hl7.fhir.r4.utils.client.FHIRToolingClient; +import org.junit.jupiter.api.Test; + +import java.net.URISyntaxException; + +public class VSACClientTest { + @Test + public void testVSAC() throws URISyntaxException { + FHIRToolingClient fhirToolingClient = new FHIRToolingClient("https://cts.nlm.nih.gov/fhir", "fhir/vsac"); + fhirToolingClient.setTimeoutNormal(30000); + fhirToolingClient.setTimeoutExpand(30000); + CapabilityStatement cs = fhirToolingClient.getCapabilitiesStatement(); + System.out.println(cs); + } +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHCParser.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHCParser.java index 1376bdcb6d..04d936f989 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHCParser.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHCParser.java @@ -29,6 +29,9 @@ import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.VersionUtilities; +import org.hl7.fhir.utilities.http.HTTPRequest; +import org.hl7.fhir.utilities.http.HTTPResult; +import org.hl7.fhir.utilities.http.ManagedWebAccess; import org.hl7.fhir.utilities.json.JsonException; import org.hl7.fhir.utilities.json.model.JsonArray; import org.hl7.fhir.utilities.json.model.JsonElement; @@ -428,6 +431,20 @@ private static String decompress(byte[] compressed) throws Exception { private String getVCIIssuer(List errors, String issuer) { try { JsonObject vci = org.hl7.fhir.utilities.json.parser.JsonParser.parseObjectFromUrl("https://raw.githubusercontent.com/the-commons-project/vci-directory/main/vci-issuers.json"); + + /* HTTPResult httpResult = ManagedWebAccess.httpCall( + new HTTPRequest().withMethod(HTTPVerb.GET).withUrl(new URL("https://raw.githubusercontent.com/the-commons-project/vci-directory/main/vci-issuers.json")) + new URL("https://raw.githubusercontent.com/the-commons-project/vci-directory/main/vci-issuers.json") + HTTPRequest.HttpMethod.GET, + null, + null, + null + + ) + ) + */ + + //JsonObject vci = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(); for (JsonObject j : vci.getJsonObjects("participating_issuers")) { if (issuer.equals(j.asString("iss"))) { return j.asString("name"); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHLParser.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHLParser.java index 71297b9fe6..ffdd2560ac 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHLParser.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHLParser.java @@ -287,7 +287,7 @@ private ValidatedFragment addNamedElement(List res, String na private HTTPResult fetchFile(String url, String ct) throws IOException { - HTTPResult res = ManagedWebAccess.get(url, ct); + HTTPResult res = ManagedWebAccess.get("web", url, ct); res.checkThrowException(); return res; } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JsonTrackingParser.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JsonTrackingParser.java index 893aba96c3..f787357e89 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JsonTrackingParser.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JsonTrackingParser.java @@ -2,34 +2,34 @@ import java.io.File; -/* - Copyright (c) 2011+, HL7, Inc. - All rights reserved. - - Redistribution and use in source and binary forms, with or without modification, - are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of HL7 nor the names of its contributors may be used to - endorse or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - - */ +/* + Copyright (c) 2011+, HL7, Inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of HL7 nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + */ @@ -724,13 +724,13 @@ public static byte[] writeBytes(JsonObject json, boolean pretty) { } public static JsonObject fetchJson(String source) throws IOException { - HTTPResult res = ManagedWebAccess.get(source+"?nocache=" + System.currentTimeMillis(), "application/json, application/fhir+json"); + HTTPResult res = ManagedWebAccess.get("web", source+"?nocache=" + System.currentTimeMillis(), "application/json, application/fhir+json"); res.checkThrowException(); return parseJson(res.getContent()); } public static JsonArray fetchJsonArray(String source) throws IOException { - HTTPResult res = ManagedWebAccess.get(source+"?nocache=" + System.currentTimeMillis(), "application/json, application/fhir+json"); + HTTPResult res = ManagedWebAccess.get("web",source+"?nocache=" + System.currentTimeMillis(), "application/json, application/fhir+json"); res.checkThrowException(); return parseJsonArray(res.getContent()); } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/parser/JsonParser.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/parser/JsonParser.java index df6efedc35..c1e8e61044 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/parser/JsonParser.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/parser/JsonParser.java @@ -692,7 +692,7 @@ private void write(StringBuilder b, JsonElement e, boolean pretty, int indent) { private static byte[] fetch(String source) throws IOException { String murl = source.contains("?") ? source+"&nocache=" + System.currentTimeMillis() : source+"?nocache=" + System.currentTimeMillis(); - HTTPResult res = ManagedWebAccess.get(murl, "application/json, application/fhir+json"); + HTTPResult res = ManagedWebAccess.get("web", murl, "application/json, application/fhir+json"); res.checkThrowException(); return res.getContent(); } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java index 1462a83680..c6d4ef8e63 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java @@ -584,11 +584,11 @@ private byte[] fetchFromUrlSpecific(String source, String contentType, boolean o try { try { // try with cache-busting option and then try withhout in case the server doesn't support that - HTTPResult res = ManagedWebAccess.get(source + "?nocache=" + System.currentTimeMillis(), contentType); + HTTPResult res = ManagedWebAccess.get("web",source + "?nocache=" + System.currentTimeMillis(), contentType); res.checkThrowException(); return res.getContent(); } catch (Exception e) { - HTTPResult res = ManagedWebAccess.get(source, contentType); + HTTPResult res = ManagedWebAccess.get("web", source, contentType); res.checkThrowException(); return res.getContent(); } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ipa/IPAValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ipa/IPAValidator.java index 327567cc02..b0793fafa1 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ipa/IPAValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ipa/IPAValidator.java @@ -160,7 +160,7 @@ private void checkSelfLink(ValidationNode vn, Element bundle, Map= 300) { vn.getIssues().add(new ValidationMessage(Source.IPAValidator, IssueType.EXCEPTION, "http.request", "HTTP Return code is "+result.getCode()+" "+result.getMessage(),