diff --git a/docs/src/pages/guides/FHIRServerUsersGuide.md b/docs/src/pages/guides/FHIRServerUsersGuide.md index 8cf91817a74..756edd26503 100644 --- a/docs/src/pages/guides/FHIRServerUsersGuide.md +++ b/docs/src/pages/guides/FHIRServerUsersGuide.md @@ -1972,6 +1972,9 @@ This section contains reference information about each of the configuration prop |`fhirServer/term/remoteTermServiceProviders/hostnameVerificationEnabled`|boolean|Indicates whether hostname verification should be performed when using SSL transport| |`fhirServer/term/remoteTermServiceProviders/basicAuth/username`|string|The basic authentication username for this remote term service provider| |`fhirServer/term/remoteTermServiceProviders/basicAuth/password`|string|The basic authentication password for this remote term service provider| +|`fhirServer/term/remoteTermServiceProviders/headers`|array of objects|The `headers` element is an array of objects| +|`fhirServer/term/remoteTermServiceProviders/headers/name`|string|The HTTP header name that will be added to requests by this remote term service provider| +|`fhirServer/term/remoteTermServiceProviders/headers/value`|string|The HTTP header value that will be added to requests by this remote term service provider| |`fhirServer/term/remoteTermServiceProviders/httpTimeout`|integer|The HTTP read timeout for this remote term service provider (in milliseconds)| |`fhirServer/term/remoteTermServiceProviders/supports`|array of objects|The `supports` element is an array of objects| |`fhirServer/term/remoteTermServiceProviders/supports/system`|string|The system URI supported by this remote term service provider| @@ -2246,6 +2249,7 @@ must restart the server for that change to take effect. |`fhirServer/term/remoteTermServiceProviders/trustStore`|N|N| |`fhirServer/term/remoteTermServiceProviders/hostnameVerificationEnabled`|N|N| |`fhirServer/term/remoteTermServiceProviders/basicAuth`|N|N| +|`fhirServer/term/remoteTermServiceProviders/headers`|N|N| |`fhirServer/term/remoteTermServiceProviders/httpTimeout`|N|N| |`fhirServer/term/remoteTermServiceProviders/supports`|N|N| |`fhirServer/resources/open`|Y|Y| diff --git a/fhir-server-test/src/test/java/com/ibm/fhir/server/test/RemoteTermServiceProviderTest.java b/fhir-server-test/src/test/java/com/ibm/fhir/server/test/RemoteTermServiceProviderTest.java index cff1998611d..a01c299a8f3 100644 --- a/fhir-server-test/src/test/java/com/ibm/fhir/server/test/RemoteTermServiceProviderTest.java +++ b/fhir-server-test/src/test/java/com/ibm/fhir/server/test/RemoteTermServiceProviderTest.java @@ -32,6 +32,7 @@ import com.ibm.fhir.term.remote.provider.RemoteTermServiceProvider; import com.ibm.fhir.term.remote.provider.RemoteTermServiceProvider.Configuration; import com.ibm.fhir.term.remote.provider.RemoteTermServiceProvider.Configuration.BasicAuth; +import com.ibm.fhir.term.remote.provider.RemoteTermServiceProvider.Configuration.Header; import com.ibm.fhir.term.remote.provider.RemoteTermServiceProvider.Configuration.Supports; import com.ibm.fhir.term.remote.provider.RemoteTermServiceProvider.Configuration.TrustStore; import com.ibm.fhir.term.util.CodeSystemSupport; @@ -47,11 +48,19 @@ public void testCreateCodeSystem() throws Exception { CodeSystem codeSystem = TestUtil.readLocalResource("CodeSystem-test.json"); Entity entity = Entity.entity(codeSystem, FHIRMediaType.APPLICATION_FHIR_JSON); - Response response = target.path("CodeSystem").path("test").request().put(entity); + Response response = target.path("CodeSystem").path("test") + .request() + .header("X-FHIR-TENANT-ID", "tenant1") + .header("X-FHIR-DSID", "profile") + .put(entity); int status = response.getStatus(); assertTrue(status == Response.Status.CREATED.getStatusCode() || status == Response.Status.OK.getStatusCode()); - response = target.path("CodeSystem").path("test").request(FHIRMediaType.APPLICATION_FHIR_JSON).get(); + response = target.path("CodeSystem").path("test") + .request(FHIRMediaType.APPLICATION_FHIR_JSON) + .header("X-FHIR-TENANT-ID", "tenant1") + .header("X-FHIR-DSID", "profile") + .get(); assertResponse(response, Response.Status.OK.getStatusCode()); CodeSystem responseCodeSystem = response.readEntity(CodeSystem.class); @@ -60,7 +69,7 @@ public void testCreateCodeSystem() throws Exception { this.codeSystem = responseCodeSystem; } - @Test + @Test(dependsOnMethods = { "testCreateCodeSystem" }) public void testCreateRemoteTermServiceProvider() { Configuration configuration = Configuration.builder() .base(getRestBaseURL()) @@ -72,6 +81,14 @@ public void testCreateRemoteTermServiceProvider() { .username(getFhirUser()) .password(getFhirPassword()) .build()) + .headers(Header.builder() + .name("X-FHIR-TENANT-ID") + .value("tenant1") + .build(), + Header.builder() + .name("X-FHIR-DSID") + .value("profile") + .build()) .supports(Supports.builder() .system(codeSystem.getUrl().getValue()) .version(codeSystem.getVersion().getValue()) diff --git a/fhir-server/src/main/java/com/ibm/fhir/server/listener/FHIRServletContextListener.java b/fhir-server/src/main/java/com/ibm/fhir/server/listener/FHIRServletContextListener.java index 9dc06b39d85..546efe8ff3b 100644 --- a/fhir-server/src/main/java/com/ibm/fhir/server/listener/FHIRServletContextListener.java +++ b/fhir-server/src/main/java/com/ibm/fhir/server/listener/FHIRServletContextListener.java @@ -64,6 +64,7 @@ import com.ibm.fhir.term.remote.provider.RemoteTermServiceProvider; import com.ibm.fhir.term.remote.provider.RemoteTermServiceProvider.Configuration; import com.ibm.fhir.term.remote.provider.RemoteTermServiceProvider.Configuration.BasicAuth; +import com.ibm.fhir.term.remote.provider.RemoteTermServiceProvider.Configuration.Header; import com.ibm.fhir.term.remote.provider.RemoteTermServiceProvider.Configuration.Supports; import com.ibm.fhir.term.remote.provider.RemoteTermServiceProvider.Configuration.TrustStore; import com.ibm.fhir.term.service.FHIRTermService; @@ -330,6 +331,17 @@ private void configureTermServiceCapabilities(PropertyGroup fhirConfig) throws E .build()); } + Object[] headersArray = remoteTermServiceProviderPropertyGroup.getArrayProperty("headers"); + if (headersArray != null) { + for (Object headerObject : headersArray) { + PropertyGroup headerPropertyGroup = (PropertyGroup) headerObject; + builder.headers(Header.builder() + .name(headerPropertyGroup.getStringProperty("name")) + .value(headerPropertyGroup.getStringProperty("value")) + .build()); + } + } + builder.httpTimeout(remoteTermServiceProviderPropertyGroup.getIntProperty("httpTimeout", Configuration.DEFAULT_HTTP_TIMEOUT)); Object[] supportsArray = remoteTermServiceProviderPropertyGroup.getArrayProperty("supports"); diff --git a/fhir-term-remote/src/main/java/com/ibm/fhir/term/remote/provider/RemoteTermServiceProvider.java b/fhir-term-remote/src/main/java/com/ibm/fhir/term/remote/provider/RemoteTermServiceProvider.java index c106030c0d9..e189db63933 100644 --- a/fhir-term-remote/src/main/java/com/ibm/fhir/term/remote/provider/RemoteTermServiceProvider.java +++ b/fhir-term-remote/src/main/java/com/ibm/fhir/term/remote/provider/RemoteTermServiceProvider.java @@ -48,6 +48,8 @@ import javax.ws.rs.client.ClientRequestFilter; import javax.ws.rs.client.Entity; import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; @@ -96,11 +98,13 @@ public class RemoteTermServiceProvider extends AbstractTermServiceProvider { private final Configuration configuration; private final String base; + private final MultivaluedMap headersMap; private Client client; public RemoteTermServiceProvider(Configuration configuration) { this.configuration = Objects.requireNonNull(configuration, "configuration"); this.base = configuration.getBase(); + headersMap = buildHeadersMap(configuration.getHeaders()); try { log.info("Creating client..."); @@ -185,11 +189,13 @@ public Concept getConcept(CodeSystem codeSystem, Code code) { .queryParam("version", codeSystem.getVersion().getValue()) .queryParam("code", code.getValue()) .request(FHIRMediaType.APPLICATION_FHIR_JSON) + .headers(headersMap) .get() : target.path(CODE_SYSTEM_LOOKUP) .queryParam("system", codeSystem.getUrl().getValue()) .queryParam("code", code.getValue()) .request(FHIRMediaType.APPLICATION_FHIR_JSON) + .headers(headersMap) .get(); log(GET, uri(CODE_SYSTEM_LOOKUP), response.getStatus(), elapsed(start)); @@ -240,6 +246,7 @@ public Set getConcepts(CodeSystem codeSystem, List filters, Funct response = target.path(VALUE_SET_EXPAND) .request(FHIRMediaType.APPLICATION_FHIR_JSON) + .headers(headersMap) .post(Entity.entity(parameters, FHIRMediaType.APPLICATION_FHIR_JSON)); log(POST, uri(VALUE_SET_EXPAND), response.getStatus(), elapsed(start)); @@ -294,11 +301,13 @@ public boolean hasConcept(CodeSystem codeSystem, Code code) { .queryParam("version", codeSystem.getVersion().getValue()) .queryParam("code", code.getValue()) .request(FHIRMediaType.APPLICATION_FHIR_JSON) + .headers(headersMap) .get() : target.path(CODE_SYSTEM_VALIDATE_CODE) .queryParam("url", codeSystem.getUrl().getValue()) .queryParam("code", code.getValue()) .request(FHIRMediaType.APPLICATION_FHIR_JSON) + .headers(headersMap) .get(); log(GET, uri(CODE_SYSTEM_VALIDATE_CODE), response.getStatus(), elapsed(start)); @@ -359,12 +368,14 @@ public boolean subsumes(CodeSystem codeSystem, Code codeA, Code codeB) { .queryParam("codeA", codeA.getValue()) .queryParam("codeB", codeB.getValue()) .request(FHIRMediaType.APPLICATION_FHIR_JSON) + .headers(headersMap) .get() : target.path(CODE_SYSTEM_SUBSUMES) .queryParam("system", codeSystem.getUrl().getValue()) .queryParam("codeA", codeA.getValue()) .queryParam("codeB", codeB.getValue()) .request(FHIRMediaType.APPLICATION_FHIR_JSON) + .headers(headersMap) .get(); log(GET, uri(CODE_SYSTEM_SUBSUMES), response.getStatus(), elapsed(start)); @@ -386,6 +397,14 @@ public boolean subsumes(CodeSystem codeSystem, Code codeA, Code codeB) { } } + private MultivaluedMap buildHeadersMap(List headers) { + MultivaluedMap headersMap = new MultivaluedHashMap<>(); + for (Configuration.Header header : headers) { + headersMap.putSingle(header.getName(), header.getValue()); + } + return headersMap; + } + private double elapsed(long start) { return (System.currentTimeMillis() - start) / 1000.0; } @@ -594,6 +613,7 @@ public static class Configuration { private final TrustStore trustStore; private final boolean hostnameVerificationEnabled; private final BasicAuth basicAuth; + private final List
headers; private final int httpTimeout; private final List supports; @@ -602,6 +622,7 @@ private Configuration(Builder builder) { trustStore = builder.trustStore; hostnameVerificationEnabled = builder.hostnameVerificationEnabled; basicAuth = builder.basicAuth; + headers = Collections.unmodifiableList(builder.headers); httpTimeout = builder.httpTimeout; supports = Collections.unmodifiableList(builder.supports); } @@ -622,6 +643,10 @@ public BasicAuth getBasicAuth() { return basicAuth; } + public List
getHeaders() { + return headers; + } + public int getHttpTimeout() { return httpTimeout; } @@ -646,13 +671,14 @@ public boolean equals(Object obj) { Objects.equals(trustStore, other.trustStore) && Objects.equals(hostnameVerificationEnabled, other.hostnameVerificationEnabled) && Objects.equals(basicAuth, other.basicAuth) && + Objects.equals(headers, other.headers) && Objects.equals(httpTimeout, other.httpTimeout) && Objects.equals(supports, other.supports); } @Override public int hashCode() { - return Objects.hash(base, trustStore, hostnameVerificationEnabled, basicAuth, httpTimeout, supports); + return Objects.hash(base, trustStore, hostnameVerificationEnabled, basicAuth, headers, httpTimeout, supports); } public Builder toBuilder() { @@ -668,6 +694,7 @@ public static class Builder { private TrustStore trustStore; private boolean hostnameVerificationEnabled = DEFAULT_HOSTNAME_VERIFICATION_ENABLED; private BasicAuth basicAuth; + private List
headers = new ArrayList<>(); private int httpTimeout = DEFAULT_HTTP_TIMEOUT; private List supports = new ArrayList<>(); @@ -693,6 +720,18 @@ public Builder basicAuth(BasicAuth basicAuth) { return this; } + public Builder headers(Header... headers) { + for (Header value : headers) { + this.headers.add(value); + } + return this; + } + + public Builder headers(Collection
headers) { + this.headers = new ArrayList<>(headers); + return this; + } + public Builder httpTimeout(int httpTimeout) { this.httpTimeout = httpTimeout; return this; @@ -719,6 +758,7 @@ protected Builder from(Configuration configuration) { trustStore = configuration.trustStore; hostnameVerificationEnabled = configuration.hostnameVerificationEnabled; basicAuth = configuration.basicAuth; + headers = configuration.headers; httpTimeout = configuration.httpTimeout; supports.addAll(configuration.supports); return this; @@ -895,6 +935,83 @@ protected Builder from(BasicAuth basicAuth) { } } + /** + * A class that represents the HTTP header(s) supported by a remote term service provider + */ + public static class Header { + private final String name; + private final Object value; + + public Header(Builder builder) { + name = Objects.requireNonNull(builder.name, "name"); + value = Objects.requireNonNull(builder.value, "value"); + } + + public String getName() { + return name; + } + + public Object getValue() { + return value; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Header other = (Header) obj; + return Objects.equals(name, other.name) && + Objects.equals(value, other.value); + } + + @Override + public int hashCode() { + return Objects.hash(name, value); + } + + public Builder toBuilder() { + return new Builder().from(this); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String name; + private Object value; + + private Builder() { } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder value(Object value) { + this.value = value; + return this; + } + + public Header build() { + return new Header(this); + } + + protected Builder from(Header header) { + name = header.name; + value = header.value; + return this; + } + } + } + /** * A class that represents the code system(s) supported by a remote term service provider */