Terminology client headers (#599)
* wip * adding tests for headers in client * fix * r4 updated httpclient * updating http client code for r4 and dstu3 * dunno why this didn't get added before
This commit is contained in:
parent
7a989a835c
commit
e5a05f5562
|
@ -21,7 +21,6 @@ public class VSACImporter extends OIDBasedValueSetImporter {
|
|||
}
|
||||
|
||||
public static void main(String[] args) throws FHIRException, IOException, ParseException, URISyntaxException {
|
||||
// new PhinVadsImporter().importValueSet(TextFile.fileToBytes("C:\\work\\org.hl7.fhir\\packages\\us.cdc.phinvads-source\\source\\PHVS_BirthDefectsLateralityatDiagnosis_HL7_V1.txt"));
|
||||
VSACImporter self = new VSACImporter();
|
||||
self.process(args[0], args[1], args[2], args[3]);
|
||||
}
|
||||
|
@ -29,12 +28,16 @@ public class VSACImporter extends OIDBasedValueSetImporter {
|
|||
private void process(String source, String dest, String username, String password) throws FHIRException, IOException, URISyntaxException {
|
||||
CSVReader csv = new CSVReader(new FileInputStream(source));
|
||||
csv.readHeaders();
|
||||
FHIRToolingClient client = new FHIRToolingClient("https://cts.nlm.nih.gov/fhir", username, password);
|
||||
|
||||
FHIRToolingClient fhirToolingClient = new FHIRToolingClient("https://cts.nlm.nih.gov/fhir");
|
||||
fhirToolingClient.setUsername(username);
|
||||
fhirToolingClient.setPassword(password);
|
||||
|
||||
int i = 0;
|
||||
while (csv.line()) {
|
||||
String oid = csv.cell("OID");
|
||||
try {
|
||||
ValueSet vs = client.read(ValueSet.class, oid);
|
||||
ValueSet vs = fhirToolingClient.read(ValueSet.class, oid);
|
||||
new JsonParser().compose(new FileOutputStream(Utilities.path(dest, "ValueSet-" + oid + ".json")), vs);
|
||||
i++;
|
||||
if (i % 100 == 0) {
|
||||
|
@ -45,68 +48,5 @@ public class VSACImporter extends OIDBasedValueSetImporter {
|
|||
}
|
||||
}
|
||||
System.out.println("Done. " + i + " ValueSets");
|
||||
// for (File f : new File(source).listFiles()) {
|
||||
// try {
|
||||
// System.out.println("Process " + f.getName());
|
||||
// List<ValueSet> vsl = importValueSet(TextFile.fileToBytes(f));
|
||||
// for (ValueSet vs : vsl) {
|
||||
// if (vs.getId() != null) {
|
||||
// new JsonParser().compose(new FileOutputStream(Utilities.path(dest, "ValueSet-" + vs.getId() + ".json")), vs);
|
||||
// }
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
// private List<ValueSet> importValueSet(byte[] source) throws Exception {
|
||||
// List<ValueSet> res = new ArrayList<ValueSet>();
|
||||
// Element x = loadXml(new ByteArrayInputStream(source)).getDocumentElement();
|
||||
// List<Element> vl = XMLUtil.getNamedChildren(x, "DescribedValueSet");
|
||||
// for (Element v : vl) {
|
||||
// ValueSet vs = new ValueSet();
|
||||
// vs.setId(v.getAttribute("ID"));
|
||||
// vs.setUrl("http://cts.nlm.nih.gov/fhir/ValueSet/" + vs.getId());
|
||||
// vs.getMeta().setSource("https://vsac.nlm.nih.gov/valueset/" + vs.getId() + "/expansion");
|
||||
// vs.setVersion(v.getAttribute("version"));
|
||||
// vs.setTitle(v.getAttribute("displayName"));
|
||||
// vs.setName(Utilities.titleize(vs.getTitle()).replace(" ", ""));
|
||||
// Element d = XMLUtil.getNamedChild(v, "Purpose");
|
||||
// if (d != null) {
|
||||
// vs.setDescription(d.getTextContent());
|
||||
// }
|
||||
// Element s = XMLUtil.getNamedChild(v, "Status");
|
||||
// if (s != null && "Active".equals(s.getTextContent())) {
|
||||
// vs.setStatus(PublicationStatus.ACTIVE);
|
||||
// } else {
|
||||
// vs.setStatus(PublicationStatus.DRAFT);
|
||||
// }
|
||||
// Element dt = XMLUtil.getNamedChild(v, "RevisionDate");
|
||||
// if (dt != null) {
|
||||
// vs.getDateElement().setValueAsString(dt.getTextContent());
|
||||
// }
|
||||
//
|
||||
// Element cl = XMLUtil.getNamedChild(v, "ConceptList");
|
||||
// Element cc = XMLUtil.getFirstChild(cl);
|
||||
//
|
||||
// while (cc != null) {
|
||||
// String code = cc.getAttribute("code");
|
||||
// String display = cc.getAttribute("displayName");
|
||||
// String csoid = cc.getAttribute("codeSystem");
|
||||
// String csver = cc.getAttribute("codeSystemVersion");
|
||||
// String url = context.oid2Uri(csoid);
|
||||
// if (url == null) {
|
||||
// url = "urn:oid:" + csoid;
|
||||
// }
|
||||
// csver = fixVersionforSystem(url, csver);
|
||||
// ConceptSetComponent inc = getInclude(vs, url, csver);
|
||||
// inc.addConcept().setCode(code).setDisplay(display);
|
||||
// cc = XMLUtil.getNextSibling(cc);
|
||||
// }
|
||||
//
|
||||
// res.add(vs);
|
||||
// }
|
||||
// return res;
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ import org.hl7.fhir.dstu2.utils.client.FHIRToolingClient;
|
|||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.model.*;
|
||||
import org.hl7.fhir.r5.terminologies.TerminologyClient;
|
||||
import org.hl7.fhir.r5.utils.client.network.ClientHeaders;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
|
@ -143,5 +144,13 @@ public class TerminologyClientR2 implements TerminologyClient {
|
|||
return (CanonicalResource) r5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientHeaders getClientHeaders() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminologyClient setClientHeaders(ClientHeaders clientHeaders) {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -37,6 +37,7 @@ import org.hl7.fhir.dstu3.utils.client.FHIRToolingClient;
|
|||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.model.*;
|
||||
import org.hl7.fhir.r5.terminologies.TerminologyClient;
|
||||
import org.hl7.fhir.r5.utils.client.network.ClientHeaders;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
|
@ -46,9 +47,16 @@ import java.util.Map;
|
|||
public class TerminologyClientR3 implements TerminologyClient {
|
||||
|
||||
private final FHIRToolingClient client; // todo: use the R2 client
|
||||
private ClientHeaders clientHeaders;
|
||||
|
||||
public TerminologyClientR3(String address) throws URISyntaxException {
|
||||
client = new FHIRToolingClient(address);
|
||||
setClientHeaders(new ClientHeaders());
|
||||
}
|
||||
|
||||
public TerminologyClientR3(String address, ClientHeaders clientHeaders) throws URISyntaxException {
|
||||
client = new FHIRToolingClient(address);
|
||||
setClientHeaders(clientHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -143,5 +151,15 @@ public class TerminologyClientR3 implements TerminologyClient {
|
|||
return (CanonicalResource) r5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientHeaders getClientHeaders() {
|
||||
return clientHeaders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminologyClient setClientHeaders(ClientHeaders clientHeaders) {
|
||||
this.clientHeaders = clientHeaders;
|
||||
this.client.setClientHeaders(this.clientHeaders.headers());
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -37,6 +37,7 @@ import org.hl7.fhir.r4.model.Resource;
|
|||
import org.hl7.fhir.r4.utils.client.FHIRToolingClient;
|
||||
import org.hl7.fhir.r5.model.*;
|
||||
import org.hl7.fhir.r5.terminologies.TerminologyClient;
|
||||
import org.hl7.fhir.r5.utils.client.network.ClientHeaders;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
|
@ -46,9 +47,16 @@ import java.util.Map;
|
|||
public class TerminologyClientR4 implements TerminologyClient {
|
||||
|
||||
private final FHIRToolingClient client; // todo: use the R2 client
|
||||
private ClientHeaders clientHeaders;
|
||||
|
||||
public TerminologyClientR4(String address) throws URISyntaxException {
|
||||
client = new FHIRToolingClient(address);
|
||||
this.client = new FHIRToolingClient(address);
|
||||
setClientHeaders(new ClientHeaders());
|
||||
}
|
||||
|
||||
public TerminologyClientR4(String address, ClientHeaders clientHeaders) throws URISyntaxException {
|
||||
this.client = new FHIRToolingClient(address);
|
||||
setClientHeaders(clientHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -143,4 +151,15 @@ public class TerminologyClientR4 implements TerminologyClient {
|
|||
return (CanonicalResource) r5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientHeaders getClientHeaders() {
|
||||
return clientHeaders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminologyClient setClientHeaders(ClientHeaders clientHeaders) {
|
||||
this.clientHeaders = clientHeaders;
|
||||
this.client.setClientHeaders(this.clientHeaders.headers());
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -34,6 +34,7 @@ import org.hl7.fhir.exceptions.FHIRException;
|
|||
import org.hl7.fhir.r5.model.*;
|
||||
import org.hl7.fhir.r5.terminologies.TerminologyClient;
|
||||
import org.hl7.fhir.r5.utils.client.FHIRToolingClient;
|
||||
import org.hl7.fhir.r5.utils.client.network.ClientHeaders;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
|
@ -43,9 +44,16 @@ import java.util.Map;
|
|||
public class TerminologyClientR5 implements TerminologyClient {
|
||||
|
||||
private final FHIRToolingClient client;
|
||||
private ClientHeaders clientHeaders;
|
||||
|
||||
public TerminologyClientR5(String address) throws URISyntaxException {
|
||||
client = new FHIRToolingClient(address);
|
||||
this.client = new FHIRToolingClient(address);
|
||||
setClientHeaders(new ClientHeaders());
|
||||
}
|
||||
|
||||
public TerminologyClientR5(String address, ClientHeaders clientHeaders) throws URISyntaxException {
|
||||
this.client = new FHIRToolingClient(address);
|
||||
setClientHeaders(clientHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -129,4 +137,15 @@ public class TerminologyClientR5 implements TerminologyClient {
|
|||
return (CanonicalResource) r5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientHeaders getClientHeaders() {
|
||||
return clientHeaders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminologyClient setClientHeaders(ClientHeaders clientHeaders) {
|
||||
this.clientHeaders = clientHeaders;
|
||||
this.client.setClientHeaders(this.clientHeaders.headers());
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -51,6 +51,19 @@
|
|||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- HTTP Client -->
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>4.9.0</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache POI -->
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
package org.hl7.fhir.dstu3.context;
|
||||
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,683 +0,0 @@
|
|||
package org.hl7.fhir.dstu3.utils.client;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import 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.dstu3.formats.IParser;
|
||||
import org.hl7.fhir.dstu3.formats.IParser.OutputStyle;
|
||||
import org.hl7.fhir.dstu3.formats.JsonParser;
|
||||
import org.hl7.fhir.dstu3.formats.XmlParser;
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
import org.hl7.fhir.dstu3.model.OperationOutcome;
|
||||
import org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity;
|
||||
import org.hl7.fhir.dstu3.model.OperationOutcome.OperationOutcomeIssueComponent;
|
||||
import org.hl7.fhir.dstu3.model.Resource;
|
||||
import org.hl7.fhir.dstu3.model.ResourceType;
|
||||
import org.hl7.fhir.dstu3.utils.ResourceUtilities;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
/**
|
||||
* Helper class handling lower level HTTP transport concerns.
|
||||
* TODO Document methods.
|
||||
* @author Claude Nanjo
|
||||
*/
|
||||
public class ClientUtils {
|
||||
|
||||
public static final String DEFAULT_CHARSET = "UTF-8";
|
||||
public static final String HEADER_LOCATION = "location";
|
||||
private static boolean debugging = false;
|
||||
public static final int TIMEOUT_SOCKET = 5000;
|
||||
public static final int TIMEOUT_CONNECT = 1000;
|
||||
|
||||
private HttpHost proxy;
|
||||
private int timeout = TIMEOUT_SOCKET;
|
||||
private String username;
|
||||
private String password;
|
||||
private ToolingClientLogger logger;
|
||||
private int retryCount;
|
||||
private HttpClient httpclient;
|
||||
|
||||
public HttpHost getProxy() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
public void setProxy(HttpHost proxy) {
|
||||
this.proxy = proxy;
|
||||
}
|
||||
|
||||
public int getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public void setTimeout(int timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issueOptionsRequest(URI optionsUri, String resourceFormat, String message, int timeout) {
|
||||
HttpOptions options = new HttpOptions(optionsUri);
|
||||
return issueResourceRequest(resourceFormat, options, message, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issueGetResourceRequest(URI resourceUri, String resourceFormat, String message, int timeout) {
|
||||
HttpGet httpget = new HttpGet(resourceUri);
|
||||
return issueResourceRequest(resourceFormat, httpget, message, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri, byte[] payload, String resourceFormat, List<Header> headers, String message, int timeout) {
|
||||
HttpPut httpPut = new HttpPut(resourceUri);
|
||||
return issueResourceRequest(resourceFormat, httpPut, payload, headers, message, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri, byte[] payload, String resourceFormat, String message, int timeout) {
|
||||
HttpPut httpPut = new HttpPut(resourceUri);
|
||||
return issueResourceRequest(resourceFormat, httpPut, payload, null, message, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri, byte[] payload, String resourceFormat, List<Header> headers, String message, int timeout) {
|
||||
HttpPost httpPost = new HttpPost(resourceUri);
|
||||
return issueResourceRequest(resourceFormat, httpPost, payload, headers, message, timeout);
|
||||
}
|
||||
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri, byte[] payload, String resourceFormat, String message, int timeout) {
|
||||
return issuePostRequest(resourceUri, payload, resourceFormat, null, message, timeout);
|
||||
}
|
||||
|
||||
public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat) {
|
||||
HttpGet httpget = new HttpGet(resourceUri);
|
||||
configureFhirRequest(httpget, resourceFormat);
|
||||
HttpResponse response = sendRequest(httpget);
|
||||
return unmarshalReference(response, resourceFormat);
|
||||
}
|
||||
|
||||
private void setAuth(HttpRequest httpget) {
|
||||
if (password != null) {
|
||||
try {
|
||||
byte[] b = Base64.encodeBase64((username+":"+password).getBytes("ASCII"));
|
||||
String b64 = new String(b, StandardCharsets.US_ASCII);
|
||||
httpget.setHeader("Authorization", "Basic " + b64);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Bundle postBatchRequest(URI resourceUri, byte[] payload, String resourceFormat, String message, int timeout) {
|
||||
HttpPost httpPost = new HttpPost(resourceUri);
|
||||
configureFhirRequest(httpPost, resourceFormat);
|
||||
HttpResponse response = sendPayload(httpPost, payload, proxy, message, timeout);
|
||||
return unmarshalFeed(response, resourceFormat);
|
||||
}
|
||||
|
||||
public boolean issueDeleteRequest(URI resourceUri) {
|
||||
HttpDelete deleteRequest = new HttpDelete(resourceUri);
|
||||
HttpResponse response = sendRequest(deleteRequest);
|
||||
int responseStatusCode = response.getStatusLine().getStatusCode();
|
||||
boolean deletionSuccessful = false;
|
||||
if(responseStatusCode == 204) {
|
||||
deletionSuccessful = true;
|
||||
}
|
||||
return deletionSuccessful;
|
||||
}
|
||||
|
||||
/***********************************************************
|
||||
* Request/Response Helper methods
|
||||
***********************************************************/
|
||||
|
||||
protected <T extends Resource> ResourceRequest<T> issueResourceRequest(String resourceFormat, HttpUriRequest request, String message, int timeout) {
|
||||
return issueResourceRequest(resourceFormat, request, null, message, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resourceFormat
|
||||
* @param options
|
||||
* @return
|
||||
*/
|
||||
protected <T extends Resource> ResourceRequest<T> issueResourceRequest(String resourceFormat, HttpUriRequest request, byte[] payload, String message, int timeout) {
|
||||
return issueResourceRequest(resourceFormat, request, payload, null, message, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resourceFormat
|
||||
* @param options
|
||||
* @return
|
||||
*/
|
||||
protected <T extends Resource> ResourceRequest<T> issueResourceRequest(String resourceFormat, HttpUriRequest request, byte[] payload, List<Header> headers, String message, int timeout) {
|
||||
configureFhirRequest(request, resourceFormat, headers);
|
||||
HttpResponse response = null;
|
||||
if(request instanceof HttpEntityEnclosingRequest && payload != null) {
|
||||
response = sendPayload((HttpEntityEnclosingRequestBase)request, payload, proxy, message, timeout);
|
||||
} else if (request instanceof HttpEntityEnclosingRequest && payload == null){
|
||||
throw new EFhirClientException("PUT and POST requests require a non-null payload");
|
||||
} else {
|
||||
response = sendRequest(request);
|
||||
}
|
||||
T resource = unmarshalReference(response, resourceFormat);
|
||||
return new ResourceRequest<T>(resource, response.getStatusLine().getStatusCode(), getLocationHeader(response));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Method adds required request headers.
|
||||
* TODO handle JSON request as well.
|
||||
*
|
||||
* @param request
|
||||
*/
|
||||
protected void configureFhirRequest(HttpRequest request, String format) {
|
||||
configureFhirRequest(request, format, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method adds required request headers.
|
||||
* TODO handle JSON request as well.
|
||||
*
|
||||
* @param request
|
||||
*/
|
||||
protected void configureFhirRequest(HttpRequest request, String format, List<Header> headers) {
|
||||
request.addHeader("User-Agent", "Java FHIR Client for FHIR");
|
||||
|
||||
if (format != null) {
|
||||
request.addHeader("Accept",format);
|
||||
request.addHeader("Content-Type", format + ";charset=" + DEFAULT_CHARSET);
|
||||
}
|
||||
request.addHeader("Accept-Charset", DEFAULT_CHARSET);
|
||||
if(headers != null) {
|
||||
for(Header header : headers) {
|
||||
request.addHeader(header);
|
||||
}
|
||||
}
|
||||
setAuth(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method posts request payload
|
||||
*
|
||||
* @param request
|
||||
* @param payload
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings({ "resource", "deprecation" })
|
||||
protected HttpResponse sendPayload(HttpEntityEnclosingRequestBase request, byte[] payload, HttpHost proxy, String message, int timeout) {
|
||||
HttpResponse response = null;
|
||||
boolean ok = false;
|
||||
long t = System.currentTimeMillis();
|
||||
int tryCount = 0;
|
||||
while (!ok) {
|
||||
try {
|
||||
tryCount++;
|
||||
if (httpclient == null) {
|
||||
makeClient(proxy);
|
||||
}
|
||||
HttpParams params = httpclient.getParams();
|
||||
HttpConnectionParams.setSoTimeout(params, timeout < 1 ? this.timeout : timeout * 1000);
|
||||
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)+" for "+message+")");
|
||||
if (tryCount <= retryCount || (tryCount < 3 && ioe instanceof org.apache.http.conn.ConnectTimeoutException)) {
|
||||
ok = false;
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
} else {
|
||||
if (tryCount > 1) {
|
||||
System.out.println("Giving up: "+ioe.getMessage()+" (R3 / "+(System.currentTimeMillis()-t)+"ms / "+Utilities.describeSize(payload.length)+" for "+message+")");
|
||||
}
|
||||
throw new EFhirClientException("Error sending HTTP Post/Put Payload: "+ioe.getMessage(), ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public void makeClient(HttpHost proxy) {
|
||||
httpclient = new DefaultHttpClient();
|
||||
HttpParams params = httpclient.getParams();
|
||||
HttpConnectionParams.setConnectionTimeout(params, TIMEOUT_CONNECT);
|
||||
HttpConnectionParams.setSoTimeout(params, timeout);
|
||||
HttpConnectionParams.setSoKeepalive(params, true);
|
||||
if(proxy != null) {
|
||||
httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param request
|
||||
* @param payload
|
||||
* @return
|
||||
*/
|
||||
protected HttpResponse sendRequest(HttpUriRequest request) {
|
||||
HttpResponse response = null;
|
||||
try {
|
||||
if (httpclient == null) {
|
||||
makeClient(proxy);
|
||||
}
|
||||
response = httpclient.execute(request);
|
||||
} catch(IOException ioe) {
|
||||
if (ClientUtils.debugging ) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
throw new EFhirClientException("Error sending Http Request: "+ioe.getMessage(), ioe);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmarshals a resource from the response stream.
|
||||
*
|
||||
* @param response
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T extends Resource> T unmarshalReference(HttpResponse response, String format) {
|
||||
T resource = null;
|
||||
OperationOutcome error = null;
|
||||
byte[] cnt = log(response);
|
||||
if (cnt != null) {
|
||||
try {
|
||||
resource = (T)getParser(format).parse(cnt);
|
||||
if (resource instanceof OperationOutcome && hasError((OperationOutcome)resource)) {
|
||||
error = (OperationOutcome) resource;
|
||||
}
|
||||
} catch(IOException ioe) {
|
||||
throw new EFhirClientException("Error reading Http Response: "+ioe.getMessage(), ioe);
|
||||
} catch(Exception e) {
|
||||
throw new EFhirClientException("Error parsing response message: "+e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
if(error != null) {
|
||||
throw new EFhirClientException("Error from server: "+ResourceUtilities.getErrorDescription(error), error);
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmarshals Bundle from response stream.
|
||||
*
|
||||
* @param response
|
||||
* @return
|
||||
*/
|
||||
protected Bundle unmarshalFeed(HttpResponse response, String format) {
|
||||
Bundle feed = null;
|
||||
byte[] cnt = log(response);
|
||||
String contentType = response.getHeaders("Content-Type")[0].getValue();
|
||||
OperationOutcome error = null;
|
||||
try {
|
||||
if (cnt != null) {
|
||||
if(contentType.contains(ResourceFormat.RESOURCE_XML.getHeader()) || contentType.contains("text/xml+fhir")) {
|
||||
Resource rf = getParser(format).parse(cnt);
|
||||
if (rf instanceof Bundle)
|
||||
feed = (Bundle) rf;
|
||||
else if (rf instanceof OperationOutcome && hasError((OperationOutcome) rf)) {
|
||||
error = (OperationOutcome) rf;
|
||||
} else {
|
||||
throw new EFhirClientException("Error reading server response: a resource was returned instead");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(IOException ioe) {
|
||||
throw new EFhirClientException("Error reading Http Response", ioe);
|
||||
} catch(Exception e) {
|
||||
throw new EFhirClientException("Error parsing response message", e);
|
||||
}
|
||||
if(error != null) {
|
||||
throw new EFhirClientException("Error from server: "+ResourceUtilities.getErrorDescription(error), error);
|
||||
}
|
||||
return feed;
|
||||
}
|
||||
|
||||
private boolean hasError(OperationOutcome oo) {
|
||||
for (OperationOutcomeIssueComponent t : oo.getIssue())
|
||||
if (t.getSeverity() == IssueSeverity.ERROR || t.getSeverity() == IssueSeverity.FATAL)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected String getLocationHeader(HttpResponse response) {
|
||||
String location = null;
|
||||
if(response.getHeaders("location").length > 0) {//TODO Distinguish between both cases if necessary
|
||||
location = response.getHeaders("location")[0].getValue();
|
||||
} else if(response.getHeaders("content-location").length > 0) {
|
||||
location = response.getHeaders("content-location")[0].getValue();
|
||||
}
|
||||
return location;
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************************
|
||||
* Client connection methods
|
||||
* ***************************************************************/
|
||||
|
||||
public HttpURLConnection buildConnection(URI baseServiceUri, String tail) {
|
||||
try {
|
||||
HttpURLConnection client = (HttpURLConnection) baseServiceUri.resolve(tail).toURL().openConnection();
|
||||
return client;
|
||||
} catch(MalformedURLException mue) {
|
||||
throw new EFhirClientException("Invalid Service URL", mue);
|
||||
} catch(IOException ioe) {
|
||||
throw new EFhirClientException("Unable to establish connection to server: " + baseServiceUri.toString() + tail, ioe);
|
||||
}
|
||||
}
|
||||
|
||||
public HttpURLConnection buildConnection(URI baseServiceUri, ResourceType resourceType, String id) {
|
||||
return buildConnection(baseServiceUri, ResourceAddress.buildRelativePathFromResourceType(resourceType, id));
|
||||
}
|
||||
|
||||
/******************************************************************
|
||||
* Other general helper methods
|
||||
* ****************************************************************/
|
||||
|
||||
|
||||
public <T extends Resource> byte[] getResourceAsByteArray(T resource, boolean pretty, boolean isJson) {
|
||||
ByteArrayOutputStream baos = null;
|
||||
byte[] byteArray = null;
|
||||
try {
|
||||
baos = new ByteArrayOutputStream();
|
||||
IParser parser = null;
|
||||
if(isJson) {
|
||||
parser = new JsonParser();
|
||||
} else {
|
||||
parser = new XmlParser();
|
||||
}
|
||||
parser.setOutputStyle(pretty ? OutputStyle.PRETTY : OutputStyle.NORMAL);
|
||||
parser.compose(baos, resource);
|
||||
baos.close();
|
||||
byteArray = baos.toByteArray();
|
||||
baos.close();
|
||||
} catch (Exception e) {
|
||||
try{
|
||||
baos.close();
|
||||
}catch(Exception ex) {
|
||||
throw new EFhirClientException("Error closing output stream", ex);
|
||||
}
|
||||
throw new EFhirClientException("Error converting output stream to byte array", e);
|
||||
}
|
||||
return byteArray;
|
||||
}
|
||||
|
||||
public byte[] getFeedAsByteArray(Bundle feed, boolean pretty, boolean isJson) {
|
||||
ByteArrayOutputStream baos = null;
|
||||
byte[] byteArray = null;
|
||||
try {
|
||||
baos = new ByteArrayOutputStream();
|
||||
IParser parser = null;
|
||||
if(isJson) {
|
||||
parser = new JsonParser();
|
||||
} else {
|
||||
parser = new XmlParser();
|
||||
}
|
||||
parser.setOutputStyle(pretty ? OutputStyle.PRETTY : OutputStyle.NORMAL);
|
||||
parser.compose(baos, feed);
|
||||
baos.close();
|
||||
byteArray = baos.toByteArray();
|
||||
baos.close();
|
||||
} catch (Exception e) {
|
||||
try{
|
||||
baos.close();
|
||||
}catch(Exception ex) {
|
||||
throw new EFhirClientException("Error closing output stream", ex);
|
||||
}
|
||||
throw new EFhirClientException("Error converting output stream to byte array", e);
|
||||
}
|
||||
return byteArray;
|
||||
}
|
||||
|
||||
public Calendar getLastModifiedResponseHeaderAsCalendarObject(URLConnection serverConnection) {
|
||||
String dateTime = null;
|
||||
try {
|
||||
dateTime = serverConnection.getHeaderField("Last-Modified");
|
||||
SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", new Locale("en", "US"));
|
||||
Date lastModifiedTimestamp = format.parse(dateTime);
|
||||
Calendar calendar=Calendar.getInstance();
|
||||
calendar.setTime(lastModifiedTimestamp);
|
||||
return calendar;
|
||||
} catch(ParseException pe) {
|
||||
throw new EFhirClientException("Error parsing Last-Modified response header " + dateTime, pe);
|
||||
}
|
||||
}
|
||||
|
||||
protected IParser getParser(String format) {
|
||||
if(StringUtils.isBlank(format)) {
|
||||
format = ResourceFormat.RESOURCE_XML.getHeader();
|
||||
}
|
||||
if(format.equalsIgnoreCase("json") || format.equalsIgnoreCase(ResourceFormat.RESOURCE_JSON.getHeader()) || format.equalsIgnoreCase(ResourceFormat.RESOURCE_JSON.getHeader())) {
|
||||
return new JsonParser();
|
||||
} else if(format.equalsIgnoreCase("xml") || format.equalsIgnoreCase(ResourceFormat.RESOURCE_XML.getHeader()) || format.equalsIgnoreCase(ResourceFormat.RESOURCE_XML.getHeader())) {
|
||||
return new XmlParser();
|
||||
} else {
|
||||
throw new EFhirClientException("Invalid format: " + format);
|
||||
}
|
||||
}
|
||||
|
||||
public Bundle issuePostFeedRequest(URI resourceUri, Map<String, String> parameters, String resourceName, Resource resource, String resourceFormat) throws IOException {
|
||||
HttpPost httppost = new HttpPost(resourceUri);
|
||||
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));
|
||||
return unmarshalFeed(response, resourceFormat);
|
||||
}
|
||||
|
||||
private byte[] encodeFormSubmission(Map<String, String> parameters, String resourceName, Resource resource, String boundary) throws IOException {
|
||||
ByteArrayOutputStream b = new ByteArrayOutputStream();
|
||||
OutputStreamWriter w = new OutputStreamWriter(b, "UTF-8");
|
||||
for (String name : parameters.keySet()) {
|
||||
w.write("--");
|
||||
w.write(boundary);
|
||||
w.write("\r\nContent-Disposition: form-data; name=\""+name+"\"\r\n\r\n");
|
||||
w.write(parameters.get(name)+"\r\n");
|
||||
}
|
||||
w.write("--");
|
||||
w.write(boundary);
|
||||
w.write("\r\nContent-Disposition: form-data; name=\""+resourceName+"\"\r\n\r\n");
|
||||
w.close();
|
||||
JsonParser json = new JsonParser();
|
||||
json.setOutputStyle(OutputStyle.NORMAL);
|
||||
json.compose(b, resource);
|
||||
b.close();
|
||||
w = new OutputStreamWriter(b, "UTF-8");
|
||||
w.write("\r\n--");
|
||||
w.write(boundary);
|
||||
w.write("--");
|
||||
w.close();
|
||||
return b.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method posts request payload
|
||||
*
|
||||
* @param request
|
||||
* @param payload
|
||||
* @return
|
||||
*/
|
||||
protected HttpResponse sendPayload(HttpEntityEnclosingRequestBase request, byte[] payload) {
|
||||
HttpResponse response = null;
|
||||
try {
|
||||
log(request);
|
||||
if (httpclient == null) {
|
||||
makeClient(proxy);
|
||||
}
|
||||
request.setEntity(new ByteArrayEntity(payload));
|
||||
response = httpclient.execute(request);
|
||||
log(response);
|
||||
} catch(IOException ioe) {
|
||||
throw new EFhirClientException("Error sending HTTP Post/Put Payload: "+ioe.getMessage(), ioe);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
private void log(HttpUriRequest request) {
|
||||
if (logger != null) {
|
||||
List<String> headers = new ArrayList<>();
|
||||
for (Header h : request.getAllHeaders()) {
|
||||
headers.add(h.toString());
|
||||
}
|
||||
logger.logRequest(request.getMethod(), request.getURI().toString(), headers, null);
|
||||
}
|
||||
}
|
||||
private void log(HttpEntityEnclosingRequestBase request) {
|
||||
if (logger != null) {
|
||||
List<String> headers = new ArrayList<>();
|
||||
for (Header h : request.getAllHeaders()) {
|
||||
headers.add(h.toString());
|
||||
}
|
||||
byte[] cnt = null;
|
||||
InputStream s;
|
||||
try {
|
||||
s = request.getEntity().getContent();
|
||||
cnt = IOUtils.toByteArray(s);
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
logger.logRequest(request.getMethod(), request.getURI().toString(), headers, cnt);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] log(HttpResponse response) {
|
||||
byte[] cnt = null;
|
||||
try {
|
||||
InputStream s = response.getEntity().getContent();
|
||||
cnt = IOUtils.toByteArray(s);
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
if (logger != null) {
|
||||
List<String> headers = new ArrayList<>();
|
||||
for (Header h : response.getAllHeaders()) {
|
||||
headers.add(h.toString());
|
||||
}
|
||||
logger.logResponse(response.getStatusLine().toString(), headers, cnt);
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
|
||||
public ToolingClientLogger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
public void setLogger(ToolingClientLogger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used for debugging
|
||||
*
|
||||
* @param instream
|
||||
* @return
|
||||
*/
|
||||
protected String writeInputStreamAsString(InputStream instream) {
|
||||
String value = null;
|
||||
try {
|
||||
value = IOUtils.toString(instream, "UTF-8");
|
||||
System.out.println(value);
|
||||
|
||||
} catch(IOException ioe) {
|
||||
//Do nothing
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public int getRetryCount() {
|
||||
return retryCount;
|
||||
}
|
||||
|
||||
public void setRetryCount(int retryCount) {
|
||||
this.retryCount = retryCount;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -33,11 +33,11 @@ package org.hl7.fhir.dstu3.utils.client;
|
|||
|
||||
*/
|
||||
|
||||
import org.hl7.fhir.dstu3.model.OperationOutcome;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.dstu3.model.OperationOutcome;
|
||||
|
||||
/**
|
||||
* FHIR client exception.
|
||||
*
|
||||
|
@ -99,7 +99,6 @@ public class EFhirClientException extends RuntimeException {
|
|||
* A default message of "One or more server side errors have occurred during this operation. Refer to e.getServerErrors() for additional details."
|
||||
* will be returned to users.
|
||||
*
|
||||
* @param message
|
||||
* @param serverError
|
||||
*/
|
||||
public EFhirClientException(OperationOutcome serverError) {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -34,26 +34,21 @@ package org.hl7.fhir.dstu3.utils.client;
|
|||
*/
|
||||
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.client.utils.URIBuilder;
|
||||
import org.hl7.fhir.dstu3.model.Resource;
|
||||
import org.hl7.fhir.dstu3.model.ResourceType;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
//Make resources address subclass of URI
|
||||
|
||||
/**
|
||||
* Helper class to manage FHIR Resource URIs
|
||||
*
|
||||
|
@ -217,23 +212,19 @@ public class ResourceAddress {
|
|||
return res;
|
||||
}
|
||||
|
||||
public URI resolveMetadataUri(boolean quick) {
|
||||
return baseServiceUri.resolve(quick ? "metadata?_summary=true" : "metadata");
|
||||
}
|
||||
|
||||
public URI resolveMetadataUri(boolean quick) {
|
||||
return baseServiceUri.resolve(quick ? "metadata?_summary=true" : "metadata");
|
||||
}
|
||||
|
||||
public URI resolveMetadataTxCaps() {
|
||||
return baseServiceUri.resolve("metadata?mode=terminology");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static ResourceAddress.ResourceVersionedIdentifier parseCreateLocation(String locationResponseHeader) {
|
||||
public static ResourceVersionedIdentifier parseCreateLocation(String locationResponseHeader) {
|
||||
Pattern pattern = Pattern.compile(REGEX_ID_WITH_HISTORY);
|
||||
Matcher matcher = pattern.matcher(locationResponseHeader);
|
||||
ResourceVersionedIdentifier parsedHeader = null;
|
||||
|
@ -430,5 +421,4 @@ public class ResourceAddress {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
package org.hl7.fhir.dstu3.utils.client;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.dstu3.model.Resource;
|
||||
|
||||
public class ResourceRequest<T extends Resource> {
|
||||
private T payload;
|
||||
private int httpStatus = -1;
|
||||
private String location;
|
||||
private List<Integer> successfulStatuses = new ArrayList<Integer>();
|
||||
private List<Integer> errorStatuses = new ArrayList<Integer>();
|
||||
|
||||
public ResourceRequest(T payload, int httpStatus, List<Integer> successfulStatuses, List<Integer> errorStatuses, String location) {
|
||||
this.payload = payload;
|
||||
this.httpStatus = httpStatus;
|
||||
if(successfulStatuses != null) {
|
||||
this.successfulStatuses.addAll(successfulStatuses);
|
||||
}
|
||||
if(errorStatuses != null) {
|
||||
this.errorStatuses.addAll(errorStatuses);
|
||||
}
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public ResourceRequest(T payload, int httpStatus, String location) {
|
||||
this.payload = payload;
|
||||
this.httpStatus = httpStatus;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public ResourceRequest(T payload, int httpStatus, int successfulStatus, String location) {
|
||||
this.payload = payload;
|
||||
this.httpStatus = httpStatus;
|
||||
this.successfulStatuses.add(successfulStatus);
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public int getHttpStatus() {
|
||||
return httpStatus;
|
||||
}
|
||||
|
||||
public T getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
public T getReference() {
|
||||
T payloadResource = null;
|
||||
if(payload != null) {
|
||||
payloadResource = payload;
|
||||
}
|
||||
return payloadResource;
|
||||
}
|
||||
|
||||
public boolean isSuccessfulRequest() {
|
||||
return successfulStatuses.contains(httpStatus) && !errorStatuses.contains(httpStatus) && httpStatus > 0;
|
||||
}
|
||||
|
||||
public boolean isUnsuccessfulRequest() {
|
||||
return !isSuccessfulRequest();
|
||||
}
|
||||
|
||||
public void addSuccessStatus(int status) {
|
||||
this.successfulStatuses.add(status);
|
||||
}
|
||||
|
||||
public void addErrorStatus(int status) {
|
||||
this.errorStatuses.add(status);
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
return location;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package org.hl7.fhir.dstu3.utils.client.network;
|
||||
|
||||
import org.hl7.fhir.dstu3.formats.IParser;
|
||||
import org.hl7.fhir.dstu3.formats.JsonParser;
|
||||
import org.hl7.fhir.dstu3.formats.XmlParser;
|
||||
import org.hl7.fhir.dstu3.model.Resource;
|
||||
import org.hl7.fhir.dstu3.utils.client.EFhirClientException;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
public class ByteUtils {
|
||||
|
||||
public static <T extends Resource> byte[] resourceToByteArray(T resource, boolean pretty, boolean isJson) {
|
||||
ByteArrayOutputStream baos = null;
|
||||
byte[] byteArray = null;
|
||||
try {
|
||||
baos = new ByteArrayOutputStream();
|
||||
IParser parser = null;
|
||||
if (isJson) {
|
||||
parser = new JsonParser();
|
||||
} else {
|
||||
parser = new XmlParser();
|
||||
}
|
||||
parser.setOutputStyle(pretty ? IParser.OutputStyle.PRETTY : IParser.OutputStyle.NORMAL);
|
||||
parser.compose(baos, resource);
|
||||
baos.close();
|
||||
byteArray = baos.toByteArray();
|
||||
baos.close();
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
baos.close();
|
||||
} catch (Exception ex) {
|
||||
throw new EFhirClientException("Error closing output stream", ex);
|
||||
}
|
||||
throw new EFhirClientException("Error converting output stream to byte array", e);
|
||||
}
|
||||
return byteArray;
|
||||
}
|
||||
|
||||
public static byte[] encodeFormSubmission(Map<String, String> parameters, String resourceName, Resource resource, String boundary) throws IOException {
|
||||
ByteArrayOutputStream b = new ByteArrayOutputStream();
|
||||
OutputStreamWriter w = new OutputStreamWriter(b, StandardCharsets.UTF_8);
|
||||
for (String name : parameters.keySet()) {
|
||||
w.write("--");
|
||||
w.write(boundary);
|
||||
w.write("\r\nContent-Disposition: form-data; name=\"" + name + "\"\r\n\r\n");
|
||||
w.write(parameters.get(name) + "\r\n");
|
||||
}
|
||||
w.write("--");
|
||||
w.write(boundary);
|
||||
w.write("\r\nContent-Disposition: form-data; name=\"" + resourceName + "\"\r\n\r\n");
|
||||
w.close();
|
||||
JsonParser json = new JsonParser();
|
||||
json.setOutputStyle(IParser.OutputStyle.NORMAL);
|
||||
json.compose(b, resource);
|
||||
b.close();
|
||||
w = new OutputStreamWriter(b, StandardCharsets.UTF_8);
|
||||
w.write("\r\n--");
|
||||
w.write(boundary);
|
||||
w.write("--");
|
||||
w.close();
|
||||
return b.toByteArray();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
package org.hl7.fhir.dstu3.utils.client.network;
|
||||
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
import org.hl7.fhir.dstu3.model.Resource;
|
||||
import org.hl7.fhir.dstu3.utils.client.EFhirClientException;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class Client {
|
||||
|
||||
public static final String DEFAULT_CHARSET = "UTF-8";
|
||||
private static final long DEFAULT_TIMEOUT = 5000;
|
||||
private ToolingClientLogger logger;
|
||||
private int retryCount;
|
||||
private long timeout = DEFAULT_TIMEOUT;
|
||||
|
||||
public ToolingClientLogger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
public void setLogger(ToolingClientLogger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public int getRetryCount() {
|
||||
return retryCount;
|
||||
}
|
||||
|
||||
public void setRetryCount(int retryCount) {
|
||||
this.retryCount = retryCount;
|
||||
}
|
||||
|
||||
public long getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public void setTimeout(long timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issueOptionsRequest(URI optionsUri,
|
||||
String resourceFormat,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
Request.Builder request = new Request.Builder()
|
||||
.method("OPTIONS", null)
|
||||
.url(optionsUri.toURL());
|
||||
|
||||
return executeFhirRequest(request, resourceFormat, new Headers.Builder().build(), message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issueGetResourceRequest(URI resourceUri,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL());
|
||||
|
||||
return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public int tester(int trytry) {
|
||||
return 5;
|
||||
}
|
||||
public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri,
|
||||
byte[] payload,
|
||||
String resourceFormat,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
return issuePutRequest(resourceUri, payload, resourceFormat, new Headers.Builder().build(), message, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri,
|
||||
byte[] payload,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
if (payload == null) throw new EFhirClientException("PUT requests require a non-null payload");
|
||||
RequestBody body = RequestBody.create(payload);
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL())
|
||||
.put(body);
|
||||
|
||||
return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri,
|
||||
byte[] payload,
|
||||
String resourceFormat,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
return issuePostRequest(resourceUri, payload, resourceFormat, new Headers.Builder().build(), message, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri,
|
||||
byte[] payload,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
if (payload == null) throw new EFhirClientException("POST requests require a non-null payload");
|
||||
RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL())
|
||||
.post(body);
|
||||
|
||||
return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public boolean issueDeleteRequest(URI resourceUri) throws IOException {
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL())
|
||||
.delete();
|
||||
return executeFhirRequest(request, null, new Headers.Builder().build(), null, retryCount, timeout).isSuccessfulRequest();
|
||||
}
|
||||
|
||||
public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat) throws IOException {
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL());
|
||||
|
||||
return executeBundleRequest(request, resourceFormat, new Headers.Builder().build(), null, retryCount, timeout);
|
||||
}
|
||||
|
||||
public Bundle issuePostFeedRequest(URI resourceUri,
|
||||
Map<String, String> parameters,
|
||||
String resourceName,
|
||||
Resource resource,
|
||||
String resourceFormat) throws IOException {
|
||||
String boundary = "----WebKitFormBoundarykbMUo6H8QaUnYtRy";
|
||||
byte[] payload = ByteUtils.encodeFormSubmission(parameters, resourceName, resource, boundary);
|
||||
RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL())
|
||||
.post(body);
|
||||
|
||||
return executeBundleRequest(request, resourceFormat, new Headers.Builder().build(), null, retryCount, timeout);
|
||||
}
|
||||
|
||||
public Bundle postBatchRequest(URI resourceUri,
|
||||
byte[] payload,
|
||||
String resourceFormat,
|
||||
String message,
|
||||
int timeout) throws IOException {
|
||||
if (payload == null) throw new EFhirClientException("POST requests require a non-null payload");
|
||||
RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL())
|
||||
.post(body);
|
||||
|
||||
return executeBundleRequest(request, resourceFormat, new Headers.Builder().build(), message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> Bundle executeBundleRequest(Request.Builder request,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
String message,
|
||||
int retryCount,
|
||||
long timeout) throws IOException {
|
||||
return new FhirRequestBuilder(request)
|
||||
.withLogger(logger)
|
||||
.withResourceFormat(resourceFormat)
|
||||
.withRetryCount(retryCount)
|
||||
.withMessage(message)
|
||||
.withHeaders(headers == null ? new Headers.Builder().build() : headers)
|
||||
.withTimeout(timeout, TimeUnit.MILLISECONDS)
|
||||
.executeAsBatch();
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> executeFhirRequest(Request.Builder request,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
String message,
|
||||
int retryCount,
|
||||
long timeout) throws IOException {
|
||||
return new FhirRequestBuilder(request)
|
||||
.withLogger(logger)
|
||||
.withResourceFormat(resourceFormat)
|
||||
.withRetryCount(retryCount)
|
||||
.withMessage(message)
|
||||
.withHeaders(headers == null ? new Headers.Builder().build() : headers)
|
||||
.withTimeout(timeout, TimeUnit.MILLISECONDS)
|
||||
.execute();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
package org.hl7.fhir.dstu3.utils.client.network;
|
||||
|
||||
import okhttp3.internal.http2.Header;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 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<Header> headers;
|
||||
|
||||
public ClientHeaders() {
|
||||
this.headers = new ArrayList<>();
|
||||
}
|
||||
|
||||
public ClientHeaders(ArrayList<Header> headers) {
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public ArrayList<Header> headers() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a header to the list of stored headers for network operations.
|
||||
*
|
||||
* @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 {
|
||||
if (headers.contains(header)) {
|
||||
throw new FHIRException("Attempting to add duplicate header, <" + header.name + ", "
|
||||
+ header.value + ">.");
|
||||
}
|
||||
headers.add(header);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a header to the list of stored headers for network operations.
|
||||
*
|
||||
* @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<Header> 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.
|
||||
* @throws FHIRException if the header passed in does not exist within the stored list.
|
||||
*/
|
||||
public ClientHeaders removeHeader(Header 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.");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the passed in headers from the list of stored headers.
|
||||
* @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<Header> headerList) throws FHIRException {
|
||||
headerList.forEach(this::removeHeader);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all stored {@link Header}.
|
||||
*/
|
||||
public ClientHeaders clearHeaders() {
|
||||
headers.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.headers.stream()
|
||||
.map(header -> "\t" + header.name + ":" + header.value)
|
||||
.collect(Collectors.joining(",\n", "{\n", "\n}"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,328 @@
|
|||
package org.hl7.fhir.dstu3.utils.client.network;
|
||||
|
||||
import okhttp3.*;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.dstu3.formats.IParser;
|
||||
import org.hl7.fhir.dstu3.formats.JsonParser;
|
||||
import org.hl7.fhir.dstu3.formats.XmlParser;
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
import org.hl7.fhir.dstu3.model.OperationOutcome;
|
||||
import org.hl7.fhir.dstu3.model.Resource;
|
||||
import org.hl7.fhir.dstu3.utils.ResourceUtilities;
|
||||
import org.hl7.fhir.dstu3.utils.client.EFhirClientException;
|
||||
import org.hl7.fhir.dstu3.utils.client.ResourceFormat;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
|
||||
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;
|
||||
|
||||
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 String resourceFormat = null;
|
||||
private Headers headers = null;
|
||||
private String message = null;
|
||||
private int retryCount = 1;
|
||||
/**
|
||||
* The timeout quantity. Used in combination with {@link FhirRequestBuilder#timeoutUnit}.
|
||||
*/
|
||||
private long timeout = 5000;
|
||||
/**
|
||||
* Time unit for {@link FhirRequestBuilder#timeout}.
|
||||
*/
|
||||
private TimeUnit timeoutUnit = TimeUnit.MILLISECONDS;
|
||||
/**
|
||||
* {@link ToolingClientLogger} for log output.
|
||||
*/
|
||||
private ToolingClientLogger logger = null;
|
||||
|
||||
public FhirRequestBuilder(Request.Builder httpRequest) {
|
||||
this.httpRequest = httpRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds necessary default headers, formatting headers, and any passed in {@link Headers} 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);
|
||||
if (format != null) addResourceFormatHeaders(request, format);
|
||||
if (headers != null) addHeaders(request, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds necessary headers for all REST requests.
|
||||
* <li>User-Agent : hapi-fhir-tooling-client</li>
|
||||
* <li>Accept-Charset : {@link FhirRequestBuilder#DEFAULT_CHARSET}</li>
|
||||
*
|
||||
* @param request {@link Request.Builder} to add default headers to.
|
||||
*/
|
||||
protected static void addDefaultHeaders(Request.Builder request) {
|
||||
request.addHeader("User-Agent", "hapi-fhir-tooling-client");
|
||||
request.addHeader("Accept-Charset", DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds necessary headers for the given resource format provided.
|
||||
*
|
||||
* @param request {@link Request.Builder} to add default headers to.
|
||||
*/
|
||||
protected static void addResourceFormatHeaders(Request.Builder request, String format) {
|
||||
request.addHeader("Accept", format);
|
||||
request.addHeader("Content-Type", format + ";charset=" + DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through the passed in {@link Headers} and adds them to the provided {@link Request.Builder}.
|
||||
*
|
||||
* @param request {@link Request.Builder} to add headers to.
|
||||
* @param headers {@link Headers} to add to request.
|
||||
*/
|
||||
protected static void addHeaders(Request.Builder request, Headers headers) {
|
||||
headers.forEach(header -> request.addHeader(header.getFirst(), header.getSecond()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if any of the {@link org.hl7.fhir.dstu3.model.OperationOutcome.OperationOutcomeIssueComponent} within the
|
||||
* provided {@link OperationOutcome} have an {@link org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity} of
|
||||
* {@link org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity#ERROR} or
|
||||
* {@link org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity#FATAL}
|
||||
*
|
||||
* @param oo {@link OperationOutcome} to evaluate.
|
||||
* @return {@link Boolean#TRUE} if an error exists.
|
||||
*/
|
||||
protected static boolean hasError(OperationOutcome oo) {
|
||||
return (oo.getIssue().stream()
|
||||
.anyMatch(issue -> issue.getSeverity() == OperationOutcome.IssueSeverity.ERROR
|
||||
|| issue.getSeverity() == OperationOutcome.IssueSeverity.FATAL));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the 'location' header from the passes in {@link Headers}. If no value for 'location' exists, the
|
||||
* value for 'content-location' is returned. If neither header exists, we return null.
|
||||
*
|
||||
* @param headers {@link Headers} to evaluate
|
||||
* @return {@link String} header value, or null if no location headers are set.
|
||||
*/
|
||||
protected static String getLocationHeader(Headers headers) {
|
||||
Map<String, List<String>> headerMap = headers.toMultimap();
|
||||
if (headerMap.containsKey(LOCATION_HEADER)) {
|
||||
return headerMap.get(LOCATION_HEADER).get(0);
|
||||
} else if (headerMap.containsKey(CONTENT_LOCATION_HEADER)) {
|
||||
return headerMap.get(CONTENT_LOCATION_HEADER).get(0);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We only ever want to have one copy of the HttpClient kicking around at any given time. If we need to make changes
|
||||
* to any configuration, such as proxy settings, timeout, caches, etc, we can do a per-call configuration through
|
||||
* the {@link OkHttpClient#newBuilder()} method. That will return a builder that shares the same connection pool,
|
||||
* dispatcher, and configuration with the original client.
|
||||
* </p>
|
||||
* The {@link OkHttpClient} uses the proxy auth properties set in the current system properties. The reason we don't
|
||||
* set the proxy address and authentication explicitly, is due to the fact that this class is often used in conjunction
|
||||
* with other http client tools which rely on the system.properties settings to determine proxy settings. It's easier
|
||||
* to keep the method consistent across the board. ...for now.
|
||||
*
|
||||
* @return {@link OkHttpClient} instance
|
||||
*/
|
||||
protected OkHttpClient getHttpClient() {
|
||||
if (okHttpClient == null) {
|
||||
okHttpClient = new OkHttpClient();
|
||||
}
|
||||
|
||||
Authenticator proxyAuthenticator = (route, response) -> {
|
||||
String credential = Credentials.basic(System.getProperty(HTTP_PROXY_USER), System.getProperty(HTTP_PROXY_PASS));
|
||||
return response.request().newBuilder()
|
||||
.header(HEADER_PROXY_AUTH, credential)
|
||||
.build();
|
||||
};
|
||||
|
||||
return okHttpClient.newBuilder()
|
||||
.addInterceptor(new RetryInterceptor(retryCount))
|
||||
.connectTimeout(timeout, timeoutUnit)
|
||||
.writeTimeout(timeout, timeoutUnit)
|
||||
.readTimeout(timeout, timeoutUnit)
|
||||
.proxyAuthenticator(proxyAuthenticator)
|
||||
.build();
|
||||
}
|
||||
|
||||
public FhirRequestBuilder withResourceFormat(String resourceFormat) {
|
||||
this.resourceFormat = resourceFormat;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FhirRequestBuilder withHeaders(Headers headers) {
|
||||
this.headers = headers;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FhirRequestBuilder withMessage(String message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FhirRequestBuilder withRetryCount(int retryCount) {
|
||||
this.retryCount = retryCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FhirRequestBuilder withLogger(ToolingClientLogger logger) {
|
||||
this.logger = logger;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FhirRequestBuilder withTimeout(long timeout, TimeUnit unit) {
|
||||
this.timeout = timeout;
|
||||
this.timeoutUnit = unit;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected Request buildRequest() {
|
||||
return httpRequest.build();
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> execute() throws IOException {
|
||||
formatHeaders(httpRequest, resourceFormat, null);
|
||||
Response response = getHttpClient().newCall(httpRequest.build()).execute();
|
||||
T resource = unmarshalReference(response, resourceFormat);
|
||||
return new ResourceRequest<T>(resource, response.code(), getLocationHeader(response.headers()));
|
||||
}
|
||||
|
||||
public Bundle executeAsBatch() throws IOException {
|
||||
formatHeaders(httpRequest, resourceFormat, null);
|
||||
Response response = getHttpClient().newCall(httpRequest.build()).execute();
|
||||
return unmarshalFeed(response, resourceFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmarshalls a resource from the response stream.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T extends Resource> T unmarshalReference(Response response, String format) {
|
||||
T resource = null;
|
||||
OperationOutcome error = null;
|
||||
|
||||
if (response.body() != null) {
|
||||
try {
|
||||
byte[] body = response.body().bytes();
|
||||
log(response.code(), response.headers(), body);
|
||||
resource = (T) getParser(format).parse(body);
|
||||
if (resource instanceof OperationOutcome && hasError((OperationOutcome) resource)) {
|
||||
error = (OperationOutcome) resource;
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new EFhirClientException("Error reading Http Response: " + ioe.getMessage(), ioe);
|
||||
} catch (Exception e) {
|
||||
throw new EFhirClientException("Error parsing response message: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (error != null) {
|
||||
throw new EFhirClientException("Error from server: " + ResourceUtilities.getErrorDescription(error), error);
|
||||
}
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmarshalls Bundle from response stream.
|
||||
*/
|
||||
protected Bundle unmarshalFeed(Response 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");
|
||||
if (body != null) {
|
||||
if (contentType.contains(ResourceFormat.RESOURCE_XML.getHeader()) || contentType.contains("text/xml+fhir")) {
|
||||
Resource rf = getParser(format).parse(body);
|
||||
if (rf instanceof Bundle)
|
||||
feed = (Bundle) rf;
|
||||
else if (rf instanceof OperationOutcome && hasError((OperationOutcome) rf)) {
|
||||
error = (OperationOutcome) rf;
|
||||
} else {
|
||||
throw new EFhirClientException("Error reading server response: a resource was returned instead");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new EFhirClientException("Error reading Http Response", ioe);
|
||||
} catch (Exception e) {
|
||||
throw new EFhirClientException("Error parsing response message", e);
|
||||
}
|
||||
if (error != null) {
|
||||
throw new EFhirClientException("Error from server: " + ResourceUtilities.getErrorDescription(error), error);
|
||||
}
|
||||
return feed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the appropriate parser based on the format type passed in. Defaults to XML parser if a blank format is
|
||||
* provided...because reasons.
|
||||
* <p>
|
||||
* Currently supports only "json" and "xml" formats.
|
||||
*
|
||||
* @param format One of "json" or "xml".
|
||||
* @return {@link JsonParser} or {@link XmlParser}
|
||||
*/
|
||||
protected IParser getParser(String format) {
|
||||
if (StringUtils.isBlank(format)) {
|
||||
format = ResourceFormat.RESOURCE_XML.getHeader();
|
||||
}
|
||||
if (format.equalsIgnoreCase("json") || format.equalsIgnoreCase(ResourceFormat.RESOURCE_JSON.getHeader())) {
|
||||
return new JsonParser();
|
||||
} else if (format.equalsIgnoreCase("xml") || format.equalsIgnoreCase(ResourceFormat.RESOURCE_XML.getHeader())) {
|
||||
return new XmlParser();
|
||||
} else {
|
||||
throw new EFhirClientException("Invalid format: " + format);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the given {@link Response}, using the current {@link ToolingClientLogger}. If the current
|
||||
* {@link FhirRequestBuilder#logger} is null, no action is taken.
|
||||
*
|
||||
* @param responseCode HTTP response code
|
||||
* @param responseHeaders {@link Headers} from response
|
||||
* @param responseBody Byte array response
|
||||
*/
|
||||
protected void log(int responseCode, Headers responseHeaders, byte[] responseBody) {
|
||||
if (logger != null) {
|
||||
List<String> headerList = new ArrayList<>(Collections.emptyList());
|
||||
Map<String, List<String>> headerMap = responseHeaders.toMultimap();
|
||||
headerMap.keySet().forEach(key -> headerMap.get(key).forEach(value -> headerList.add(key + ":" + value)));
|
||||
|
||||
try {
|
||||
logger.logResponse(Integer.toString(responseCode), headerList, responseBody);
|
||||
} catch (Exception e) {
|
||||
System.out.println("Error parsing response body passed in to logger ->\n" + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
// else { // TODO fix logs
|
||||
// System.out.println("Call to log HTTP response with null ToolingClientLogger set... are you forgetting to " +
|
||||
// "initialize your logger?");
|
||||
// }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package org.hl7.fhir.dstu3.utils.client.network;
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
import org.hl7.fhir.dstu3.model.Resource;
|
||||
|
||||
public class ResourceRequest<T extends Resource> {
|
||||
private T payload;
|
||||
private int httpStatus = -1;
|
||||
private String location;
|
||||
|
||||
public ResourceRequest(T payload, int httpStatus, String location) {
|
||||
this.payload = payload;
|
||||
this.httpStatus = httpStatus;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public int getHttpStatus() {
|
||||
return httpStatus;
|
||||
}
|
||||
|
||||
public T getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
public T getReference() {
|
||||
T payloadResource = null;
|
||||
if (payload != null) {
|
||||
payloadResource = payload;
|
||||
}
|
||||
return payloadResource;
|
||||
}
|
||||
|
||||
public boolean isSuccessfulRequest() {
|
||||
return this.httpStatus / 100 == 2;
|
||||
}
|
||||
|
||||
public boolean isUnsuccessfulRequest() {
|
||||
return !isSuccessfulRequest();
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
return location;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package org.hl7.fhir.dstu3.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);
|
||||
}
|
||||
|
||||
}
|
|
@ -58,6 +58,19 @@
|
|||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- HTTP Client -->
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>4.9.0</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache POI -->
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
|
|
|
@ -1,684 +0,0 @@
|
|||
package org.hl7.fhir.r4.utils.client;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import 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.r4.formats.IParser;
|
||||
import org.hl7.fhir.r4.formats.IParser.OutputStyle;
|
||||
import org.hl7.fhir.r4.formats.JsonParser;
|
||||
import org.hl7.fhir.r4.formats.XmlParser;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.hl7.fhir.r4.model.OperationOutcome;
|
||||
import org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity;
|
||||
import org.hl7.fhir.r4.model.OperationOutcome.OperationOutcomeIssueComponent;
|
||||
import org.hl7.fhir.r4.model.Resource;
|
||||
import org.hl7.fhir.r4.model.ResourceType;
|
||||
import org.hl7.fhir.r4.utils.ResourceUtilities;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
/**
|
||||
* Helper class handling lower level HTTP transport concerns.
|
||||
* TODO Document methods.
|
||||
* @author Claude Nanjo
|
||||
*/
|
||||
public class ClientUtils {
|
||||
|
||||
public static final String DEFAULT_CHARSET = "UTF-8";
|
||||
public static final String HEADER_LOCATION = "location";
|
||||
private static boolean debugging = false;
|
||||
public static final int TIMEOUT_SOCKET = 5000;
|
||||
public static final int TIMEOUT_CONNECT = 1000;
|
||||
|
||||
private HttpHost proxy;
|
||||
private int timeout = TIMEOUT_SOCKET;
|
||||
private String username;
|
||||
private String password;
|
||||
private ToolingClientLogger logger;
|
||||
private int retryCount;
|
||||
private HttpClient httpclient;
|
||||
|
||||
public HttpHost getProxy() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
public void setProxy(HttpHost proxy) {
|
||||
this.proxy = proxy;
|
||||
}
|
||||
|
||||
public int getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public void setTimeout(int timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issueOptionsRequest(URI optionsUri, String resourceFormat, String message, int timeout) {
|
||||
HttpOptions options = new HttpOptions(optionsUri);
|
||||
return issueResourceRequest(resourceFormat, options, message, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issueGetResourceRequest(URI resourceUri, String resourceFormat, String message, int timeout) {
|
||||
HttpGet httpget = new HttpGet(resourceUri);
|
||||
return issueResourceRequest(resourceFormat, httpget, message, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri, byte[] payload, String resourceFormat, List<Header> headers, String message, int timeout) {
|
||||
HttpPut httpPut = new HttpPut(resourceUri);
|
||||
return issueResourceRequest(resourceFormat, httpPut, payload, headers, message, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri, byte[] payload, String resourceFormat, String message, int timeout) {
|
||||
HttpPut httpPut = new HttpPut(resourceUri);
|
||||
return issueResourceRequest(resourceFormat, httpPut, payload, null, message, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri, byte[] payload, String resourceFormat, List<Header> headers, String message, int timeout) {
|
||||
HttpPost httpPost = new HttpPost(resourceUri);
|
||||
return issueResourceRequest(resourceFormat, httpPost, payload, headers, message, timeout);
|
||||
}
|
||||
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri, byte[] payload, String resourceFormat, String message, int timeout) {
|
||||
return issuePostRequest(resourceUri, payload, resourceFormat, null, message, timeout);
|
||||
}
|
||||
|
||||
public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat) {
|
||||
HttpGet httpget = new HttpGet(resourceUri);
|
||||
configureFhirRequest(httpget, resourceFormat);
|
||||
HttpResponse response = sendRequest(httpget);
|
||||
return unmarshalReference(response, resourceFormat);
|
||||
}
|
||||
|
||||
private void setAuth(HttpRequest httpget) {
|
||||
if (password != null) {
|
||||
try {
|
||||
byte[] b = Base64.encodeBase64((username+":"+password).getBytes("ASCII"));
|
||||
String b64 = new String(b, StandardCharsets.US_ASCII);
|
||||
httpget.setHeader("Authorization", "Basic " + b64);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Bundle postBatchRequest(URI resourceUri, byte[] payload, String resourceFormat, String message, int timeout) {
|
||||
HttpPost httpPost = new HttpPost(resourceUri);
|
||||
configureFhirRequest(httpPost, resourceFormat);
|
||||
HttpResponse response = sendPayload(httpPost, payload, proxy, message, timeout);
|
||||
return unmarshalFeed(response, resourceFormat);
|
||||
}
|
||||
|
||||
public boolean issueDeleteRequest(URI resourceUri) {
|
||||
HttpDelete deleteRequest = new HttpDelete(resourceUri);
|
||||
HttpResponse response = sendRequest(deleteRequest);
|
||||
int responseStatusCode = response.getStatusLine().getStatusCode();
|
||||
boolean deletionSuccessful = false;
|
||||
if(responseStatusCode == 204) {
|
||||
deletionSuccessful = true;
|
||||
}
|
||||
return deletionSuccessful;
|
||||
}
|
||||
|
||||
/***********************************************************
|
||||
* Request/Response Helper methods
|
||||
***********************************************************/
|
||||
|
||||
protected <T extends Resource> ResourceRequest<T> issueResourceRequest(String resourceFormat, HttpUriRequest request, String message, int timeout) {
|
||||
return issueResourceRequest(resourceFormat, request, null, message, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resourceFormat
|
||||
* @param options
|
||||
* @return
|
||||
*/
|
||||
protected <T extends Resource> ResourceRequest<T> issueResourceRequest(String resourceFormat, HttpUriRequest request, byte[] payload, String message, int timeout) {
|
||||
return issueResourceRequest(resourceFormat, request, payload, null, message, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resourceFormat
|
||||
* @param options
|
||||
* @return
|
||||
*/
|
||||
protected <T extends Resource> ResourceRequest<T> issueResourceRequest(String resourceFormat, HttpUriRequest request, byte[] payload, List<Header> headers, String message, int timeout) {
|
||||
configureFhirRequest(request, resourceFormat, headers);
|
||||
HttpResponse response = null;
|
||||
if(request instanceof HttpEntityEnclosingRequest && payload != null) {
|
||||
response = sendPayload((HttpEntityEnclosingRequestBase)request, payload, proxy, message, timeout);
|
||||
} else if (request instanceof HttpEntityEnclosingRequest && payload == null){
|
||||
throw new EFhirClientException("PUT and POST requests require a non-null payload");
|
||||
} else {
|
||||
response = sendRequest(request);
|
||||
}
|
||||
T resource = unmarshalReference(response, resourceFormat);
|
||||
return new ResourceRequest<T>(resource, response.getStatusLine().getStatusCode(), getLocationHeader(response));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Method adds required request headers.
|
||||
* TODO handle JSON request as well.
|
||||
*
|
||||
* @param request
|
||||
*/
|
||||
protected void configureFhirRequest(HttpRequest request, String format) {
|
||||
configureFhirRequest(request, format, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method adds required request headers.
|
||||
* TODO handle JSON request as well.
|
||||
*
|
||||
* @param request
|
||||
*/
|
||||
protected void configureFhirRequest(HttpRequest request, String format, List<Header> headers) {
|
||||
request.addHeader("User-Agent", "Java FHIR Client for FHIR");
|
||||
|
||||
if (format != null) {
|
||||
request.addHeader("Accept",format);
|
||||
request.addHeader("Content-Type", format + ";charset=" + DEFAULT_CHARSET);
|
||||
}
|
||||
request.addHeader("Accept-Charset", DEFAULT_CHARSET);
|
||||
if(headers != null) {
|
||||
for(Header header : headers) {
|
||||
request.addHeader(header);
|
||||
}
|
||||
}
|
||||
setAuth(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method posts request payload
|
||||
*
|
||||
* @param request
|
||||
* @param payload
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings({ "resource", "deprecation" })
|
||||
protected HttpResponse sendPayload(HttpEntityEnclosingRequestBase request, byte[] payload, HttpHost proxy, String message, int timeout) {
|
||||
HttpResponse response = null;
|
||||
boolean ok = false;
|
||||
long t = System.currentTimeMillis();
|
||||
int tryCount = 0;
|
||||
while (!ok) {
|
||||
try {
|
||||
tryCount++;
|
||||
if (httpclient == null) {
|
||||
makeClient(proxy);
|
||||
}
|
||||
HttpParams params = httpclient.getParams();
|
||||
HttpConnectionParams.setSoTimeout(params, timeout < 1 ? this.timeout : timeout * 1000);
|
||||
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)+" for "+message+")");
|
||||
if (tryCount <= retryCount || (tryCount < 3 && ioe instanceof org.apache.http.conn.ConnectTimeoutException)) {
|
||||
ok = false;
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
} else {
|
||||
if (tryCount > 1) {
|
||||
System.out.println("Giving up: "+ioe.getMessage()+" (R4 / "+(System.currentTimeMillis()-t)+"ms / "+Utilities.describeSize(payload.length)+" for "+message+")");
|
||||
}
|
||||
throw new EFhirClientException("Error sending HTTP Post/Put Payload: "+ioe.getMessage(), ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public void makeClient(HttpHost proxy) {
|
||||
httpclient = new DefaultHttpClient();
|
||||
HttpParams params = httpclient.getParams();
|
||||
HttpConnectionParams.setConnectionTimeout(params, TIMEOUT_CONNECT);
|
||||
HttpConnectionParams.setSoTimeout(params, timeout);
|
||||
HttpConnectionParams.setSoKeepalive(params, true);
|
||||
if(proxy != null) {
|
||||
httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param request
|
||||
* @param payload
|
||||
* @return
|
||||
*/
|
||||
protected HttpResponse sendRequest(HttpUriRequest request) {
|
||||
HttpResponse response = null;
|
||||
try {
|
||||
if (httpclient == null) {
|
||||
makeClient(proxy);
|
||||
}
|
||||
response = httpclient.execute(request);
|
||||
} catch(IOException ioe) {
|
||||
if (ClientUtils.debugging ) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
throw new EFhirClientException("Error sending Http Request: "+ioe.getMessage(), ioe);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unmarshals a resource from the response stream.
|
||||
*
|
||||
* @param response
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T extends Resource> T unmarshalReference(HttpResponse response, String format) {
|
||||
T resource = null;
|
||||
OperationOutcome error = null;
|
||||
byte[] cnt = log(response);
|
||||
if (cnt != null) {
|
||||
try {
|
||||
resource = (T)getParser(format).parse(cnt);
|
||||
if (resource instanceof OperationOutcome && hasError((OperationOutcome)resource)) {
|
||||
error = (OperationOutcome) resource;
|
||||
}
|
||||
} catch(IOException ioe) {
|
||||
throw new EFhirClientException("Error reading Http Response: "+ioe.getMessage(), ioe);
|
||||
} catch(Exception e) {
|
||||
throw new EFhirClientException("Error parsing response message: "+e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
if(error != null) {
|
||||
throw new EFhirClientException("Error from server: "+ResourceUtilities.getErrorDescription(error), error);
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmarshals Bundle from response stream.
|
||||
*
|
||||
* @param response
|
||||
* @return
|
||||
*/
|
||||
protected Bundle unmarshalFeed(HttpResponse response, String format) {
|
||||
Bundle feed = null;
|
||||
byte[] cnt = log(response);
|
||||
String contentType = response.getHeaders("Content-Type")[0].getValue();
|
||||
OperationOutcome error = null;
|
||||
try {
|
||||
if (cnt != null) {
|
||||
if(contentType.contains(ResourceFormat.RESOURCE_XML.getHeader()) || contentType.contains("text/xml+fhir")) {
|
||||
Resource rf = getParser(format).parse(cnt);
|
||||
if (rf instanceof Bundle)
|
||||
feed = (Bundle) rf;
|
||||
else if (rf instanceof OperationOutcome && hasError((OperationOutcome) rf)) {
|
||||
error = (OperationOutcome) rf;
|
||||
} else {
|
||||
throw new EFhirClientException("Error reading server response: a resource was returned instead");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(IOException ioe) {
|
||||
throw new EFhirClientException("Error reading Http Response", ioe);
|
||||
} catch(Exception e) {
|
||||
throw new EFhirClientException("Error parsing response message", e);
|
||||
}
|
||||
if(error != null) {
|
||||
throw new EFhirClientException("Error from server: "+ResourceUtilities.getErrorDescription(error), error);
|
||||
}
|
||||
return feed;
|
||||
}
|
||||
|
||||
private boolean hasError(OperationOutcome oo) {
|
||||
for (OperationOutcomeIssueComponent t : oo.getIssue())
|
||||
if (t.getSeverity() == IssueSeverity.ERROR || t.getSeverity() == IssueSeverity.FATAL)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected String getLocationHeader(HttpResponse response) {
|
||||
String location = null;
|
||||
if(response.getHeaders("location").length > 0) {//TODO Distinguish between both cases if necessary
|
||||
location = response.getHeaders("location")[0].getValue();
|
||||
} else if(response.getHeaders("content-location").length > 0) {
|
||||
location = response.getHeaders("content-location")[0].getValue();
|
||||
}
|
||||
return location;
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************************
|
||||
* Client connection methods
|
||||
* ***************************************************************/
|
||||
|
||||
public HttpURLConnection buildConnection(URI baseServiceUri, String tail) {
|
||||
try {
|
||||
HttpURLConnection client = (HttpURLConnection) baseServiceUri.resolve(tail).toURL().openConnection();
|
||||
return client;
|
||||
} catch(MalformedURLException mue) {
|
||||
throw new EFhirClientException("Invalid Service URL", mue);
|
||||
} catch(IOException ioe) {
|
||||
throw new EFhirClientException("Unable to establish connection to server: " + baseServiceUri.toString() + tail, ioe);
|
||||
}
|
||||
}
|
||||
|
||||
public HttpURLConnection buildConnection(URI baseServiceUri, ResourceType resourceType, String id) {
|
||||
return buildConnection(baseServiceUri, ResourceAddress.buildRelativePathFromResourceType(resourceType, id));
|
||||
}
|
||||
|
||||
/******************************************************************
|
||||
* Other general helper methods
|
||||
* ****************************************************************/
|
||||
|
||||
|
||||
public <T extends Resource> byte[] getResourceAsByteArray(T resource, boolean pretty, boolean isJson) {
|
||||
ByteArrayOutputStream baos = null;
|
||||
byte[] byteArray = null;
|
||||
try {
|
||||
baos = new ByteArrayOutputStream();
|
||||
IParser parser = null;
|
||||
if(isJson) {
|
||||
parser = new JsonParser();
|
||||
} else {
|
||||
parser = new XmlParser();
|
||||
}
|
||||
parser.setOutputStyle(pretty ? OutputStyle.PRETTY : OutputStyle.NORMAL);
|
||||
parser.compose(baos, resource);
|
||||
baos.close();
|
||||
byteArray = baos.toByteArray();
|
||||
baos.close();
|
||||
} catch (Exception e) {
|
||||
try{
|
||||
baos.close();
|
||||
}catch(Exception ex) {
|
||||
throw new EFhirClientException("Error closing output stream", ex);
|
||||
}
|
||||
throw new EFhirClientException("Error converting output stream to byte array", e);
|
||||
}
|
||||
return byteArray;
|
||||
}
|
||||
|
||||
public byte[] getFeedAsByteArray(Bundle feed, boolean pretty, boolean isJson) {
|
||||
ByteArrayOutputStream baos = null;
|
||||
byte[] byteArray = null;
|
||||
try {
|
||||
baos = new ByteArrayOutputStream();
|
||||
IParser parser = null;
|
||||
if(isJson) {
|
||||
parser = new JsonParser();
|
||||
} else {
|
||||
parser = new XmlParser();
|
||||
}
|
||||
parser.setOutputStyle(pretty ? OutputStyle.PRETTY : OutputStyle.NORMAL);
|
||||
parser.compose(baos, feed);
|
||||
baos.close();
|
||||
byteArray = baos.toByteArray();
|
||||
baos.close();
|
||||
} catch (Exception e) {
|
||||
try{
|
||||
baos.close();
|
||||
}catch(Exception ex) {
|
||||
throw new EFhirClientException("Error closing output stream", ex);
|
||||
}
|
||||
throw new EFhirClientException("Error converting output stream to byte array", e);
|
||||
}
|
||||
return byteArray;
|
||||
}
|
||||
|
||||
public Calendar getLastModifiedResponseHeaderAsCalendarObject(URLConnection serverConnection) {
|
||||
String dateTime = null;
|
||||
try {
|
||||
dateTime = serverConnection.getHeaderField("Last-Modified");
|
||||
SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", new Locale("en", "US"));
|
||||
Date lastModifiedTimestamp = format.parse(dateTime);
|
||||
Calendar calendar=Calendar.getInstance();
|
||||
calendar.setTime(lastModifiedTimestamp);
|
||||
return calendar;
|
||||
} catch(ParseException pe) {
|
||||
throw new EFhirClientException("Error parsing Last-Modified response header " + dateTime, pe);
|
||||
}
|
||||
}
|
||||
|
||||
protected IParser getParser(String format) {
|
||||
if(StringUtils.isBlank(format)) {
|
||||
format = ResourceFormat.RESOURCE_XML.getHeader();
|
||||
}
|
||||
if(format.equalsIgnoreCase("json") || format.equalsIgnoreCase(ResourceFormat.RESOURCE_JSON.getHeader()) || format.equalsIgnoreCase(ResourceFormat.RESOURCE_JSON.getHeader())) {
|
||||
return new JsonParser();
|
||||
} else if(format.equalsIgnoreCase("xml") || format.equalsIgnoreCase(ResourceFormat.RESOURCE_XML.getHeader()) || format.equalsIgnoreCase(ResourceFormat.RESOURCE_XML.getHeader())) {
|
||||
return new XmlParser();
|
||||
} else {
|
||||
throw new EFhirClientException("Invalid format: " + format);
|
||||
}
|
||||
}
|
||||
|
||||
public Bundle issuePostFeedRequest(URI resourceUri, Map<String, String> parameters, String resourceName, Resource resource, String resourceFormat) throws IOException {
|
||||
HttpPost httppost = new HttpPost(resourceUri);
|
||||
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));
|
||||
return unmarshalFeed(response, resourceFormat);
|
||||
}
|
||||
|
||||
private byte[] encodeFormSubmission(Map<String, String> parameters, String resourceName, Resource resource, String boundary) throws IOException {
|
||||
ByteArrayOutputStream b = new ByteArrayOutputStream();
|
||||
OutputStreamWriter w = new OutputStreamWriter(b, "UTF-8");
|
||||
for (String name : parameters.keySet()) {
|
||||
w.write("--");
|
||||
w.write(boundary);
|
||||
w.write("\r\nContent-Disposition: form-data; name=\""+name+"\"\r\n\r\n");
|
||||
w.write(parameters.get(name)+"\r\n");
|
||||
}
|
||||
w.write("--");
|
||||
w.write(boundary);
|
||||
w.write("\r\nContent-Disposition: form-data; name=\""+resourceName+"\"\r\n\r\n");
|
||||
w.close();
|
||||
JsonParser json = new JsonParser();
|
||||
json.setOutputStyle(OutputStyle.NORMAL);
|
||||
json.compose(b, resource);
|
||||
b.close();
|
||||
w = new OutputStreamWriter(b, "UTF-8");
|
||||
w.write("\r\n--");
|
||||
w.write(boundary);
|
||||
w.write("--");
|
||||
w.close();
|
||||
return b.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method posts request payload
|
||||
*
|
||||
* @param request
|
||||
* @param payload
|
||||
* @return
|
||||
*/
|
||||
protected HttpResponse sendPayload(HttpEntityEnclosingRequestBase request, byte[] payload) {
|
||||
HttpResponse response = null;
|
||||
try {
|
||||
log(request);
|
||||
if (httpclient == null) {
|
||||
makeClient(proxy);
|
||||
}
|
||||
request.setEntity(new ByteArrayEntity(payload));
|
||||
response = httpclient.execute(request);
|
||||
log(response);
|
||||
} catch(IOException ioe) {
|
||||
throw new EFhirClientException("Error sending HTTP Post/Put Payload: "+ioe.getMessage(), ioe);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
private void log(HttpUriRequest request) {
|
||||
if (logger != null) {
|
||||
List<String> headers = new ArrayList<>();
|
||||
for (Header h : request.getAllHeaders()) {
|
||||
headers.add(h.toString());
|
||||
}
|
||||
logger.logRequest(request.getMethod(), request.getURI().toString(), headers, null);
|
||||
}
|
||||
}
|
||||
private void log(HttpEntityEnclosingRequestBase request) {
|
||||
if (logger != null) {
|
||||
List<String> headers = new ArrayList<>();
|
||||
for (Header h : request.getAllHeaders()) {
|
||||
headers.add(h.toString());
|
||||
}
|
||||
byte[] cnt = null;
|
||||
InputStream s;
|
||||
try {
|
||||
s = request.getEntity().getContent();
|
||||
cnt = IOUtils.toByteArray(s);
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
logger.logRequest(request.getMethod(), request.getURI().toString(), headers, cnt);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] log(HttpResponse response) {
|
||||
byte[] cnt = null;
|
||||
try {
|
||||
InputStream s = response.getEntity().getContent();
|
||||
cnt = IOUtils.toByteArray(s);
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
if (logger != null) {
|
||||
List<String> headers = new ArrayList<>();
|
||||
for (Header h : response.getAllHeaders()) {
|
||||
headers.add(h.toString());
|
||||
}
|
||||
logger.logResponse(response.getStatusLine().toString(), headers, cnt);
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
|
||||
public ToolingClientLogger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
public void setLogger(ToolingClientLogger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used for debugging
|
||||
*
|
||||
* @param instream
|
||||
* @return
|
||||
*/
|
||||
protected String writeInputStreamAsString(InputStream instream) {
|
||||
String value = null;
|
||||
try {
|
||||
value = IOUtils.toString(instream, "UTF-8");
|
||||
System.out.println(value);
|
||||
|
||||
} catch(IOException ioe) {
|
||||
//Do nothing
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public int getRetryCount() {
|
||||
return retryCount;
|
||||
}
|
||||
|
||||
public void setRetryCount(int retryCount) {
|
||||
this.retryCount = retryCount;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -33,11 +33,11 @@ package org.hl7.fhir.r4.utils.client;
|
|||
|
||||
*/
|
||||
|
||||
import org.hl7.fhir.r4.model.OperationOutcome;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.r4.model.OperationOutcome;
|
||||
|
||||
/**
|
||||
* FHIR client exception.
|
||||
*
|
||||
|
@ -99,7 +99,6 @@ public class EFhirClientException extends RuntimeException {
|
|||
* A default message of "One or more server side errors have occurred during this operation. Refer to e.getServerErrors() for additional details."
|
||||
* will be returned to users.
|
||||
*
|
||||
* @param message
|
||||
* @param serverError
|
||||
*/
|
||||
public EFhirClientException(OperationOutcome serverError) {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -34,26 +34,21 @@ package org.hl7.fhir.r4.utils.client;
|
|||
*/
|
||||
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.client.utils.URIBuilder;
|
||||
import org.hl7.fhir.r4.model.Resource;
|
||||
import org.hl7.fhir.r4.model.ResourceType;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
//Make resources address subclass of URI
|
||||
|
||||
/**
|
||||
* Helper class to manage FHIR Resource URIs
|
||||
*
|
||||
|
@ -228,11 +223,8 @@ public class ResourceAddress {
|
|||
/**
|
||||
* For now, assume this type of location header structure.
|
||||
* Generalize later: http://hl7connect.healthintersections.com.au/svc/fhir/318/_history/1
|
||||
*
|
||||
* @param serviceBase
|
||||
* @param locationHeader
|
||||
*/
|
||||
public static ResourceAddress.ResourceVersionedIdentifier parseCreateLocation(String locationResponseHeader) {
|
||||
public static ResourceVersionedIdentifier parseCreateLocation(String locationResponseHeader) {
|
||||
Pattern pattern = Pattern.compile(REGEX_ID_WITH_HISTORY);
|
||||
Matcher matcher = pattern.matcher(locationResponseHeader);
|
||||
ResourceVersionedIdentifier parsedHeader = null;
|
||||
|
|
|
@ -1,109 +0,0 @@
|
|||
package org.hl7.fhir.r4.utils.client;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.r4.model.Resource;
|
||||
|
||||
public class ResourceRequest<T extends Resource> {
|
||||
private T payload;
|
||||
private int httpStatus = -1;
|
||||
private String location;
|
||||
private List<Integer> successfulStatuses = new ArrayList<Integer>();
|
||||
private List<Integer> errorStatuses = new ArrayList<Integer>();
|
||||
|
||||
public ResourceRequest(T payload, int httpStatus, List<Integer> successfulStatuses, List<Integer> errorStatuses, String location) {
|
||||
this.payload = payload;
|
||||
this.httpStatus = httpStatus;
|
||||
if(successfulStatuses != null) {
|
||||
this.successfulStatuses.addAll(successfulStatuses);
|
||||
}
|
||||
if(errorStatuses != null) {
|
||||
this.errorStatuses.addAll(errorStatuses);
|
||||
}
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public ResourceRequest(T payload, int httpStatus, String location) {
|
||||
this.payload = payload;
|
||||
this.httpStatus = httpStatus;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public ResourceRequest(T payload, int httpStatus, int successfulStatus, String location) {
|
||||
this.payload = payload;
|
||||
this.httpStatus = httpStatus;
|
||||
this.successfulStatuses.add(successfulStatus);
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public int getHttpStatus() {
|
||||
return httpStatus;
|
||||
}
|
||||
|
||||
public T getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
public T getReference() {
|
||||
T payloadResource = null;
|
||||
if(payload != null) {
|
||||
payloadResource = payload;
|
||||
}
|
||||
return payloadResource;
|
||||
}
|
||||
|
||||
public boolean isSuccessfulRequest() {
|
||||
return successfulStatuses.contains(httpStatus) && !errorStatuses.contains(httpStatus) && httpStatus > 0;
|
||||
}
|
||||
|
||||
public boolean isUnsuccessfulRequest() {
|
||||
return !isSuccessfulRequest();
|
||||
}
|
||||
|
||||
public void addSuccessStatus(int status) {
|
||||
this.successfulStatuses.add(status);
|
||||
}
|
||||
|
||||
public void addErrorStatus(int status) {
|
||||
this.errorStatuses.add(status);
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
return location;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package org.hl7.fhir.r4.utils.client.network;
|
||||
|
||||
import org.hl7.fhir.r4.formats.IParser;
|
||||
import org.hl7.fhir.r4.formats.JsonParser;
|
||||
import org.hl7.fhir.r4.formats.XmlParser;
|
||||
import org.hl7.fhir.r4.model.Resource;
|
||||
import org.hl7.fhir.r4.utils.client.EFhirClientException;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
public class ByteUtils {
|
||||
|
||||
public static <T extends Resource> byte[] resourceToByteArray(T resource, boolean pretty, boolean isJson) {
|
||||
ByteArrayOutputStream baos = null;
|
||||
byte[] byteArray = null;
|
||||
try {
|
||||
baos = new ByteArrayOutputStream();
|
||||
IParser parser = null;
|
||||
if (isJson) {
|
||||
parser = new JsonParser();
|
||||
} else {
|
||||
parser = new XmlParser();
|
||||
}
|
||||
parser.setOutputStyle(pretty ? IParser.OutputStyle.PRETTY : IParser.OutputStyle.NORMAL);
|
||||
parser.compose(baos, resource);
|
||||
baos.close();
|
||||
byteArray = baos.toByteArray();
|
||||
baos.close();
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
baos.close();
|
||||
} catch (Exception ex) {
|
||||
throw new EFhirClientException("Error closing output stream", ex);
|
||||
}
|
||||
throw new EFhirClientException("Error converting output stream to byte array", e);
|
||||
}
|
||||
return byteArray;
|
||||
}
|
||||
|
||||
public static byte[] encodeFormSubmission(Map<String, String> parameters, String resourceName, Resource resource, String boundary) throws IOException {
|
||||
ByteArrayOutputStream b = new ByteArrayOutputStream();
|
||||
OutputStreamWriter w = new OutputStreamWriter(b, StandardCharsets.UTF_8);
|
||||
for (String name : parameters.keySet()) {
|
||||
w.write("--");
|
||||
w.write(boundary);
|
||||
w.write("\r\nContent-Disposition: form-data; name=\"" + name + "\"\r\n\r\n");
|
||||
w.write(parameters.get(name) + "\r\n");
|
||||
}
|
||||
w.write("--");
|
||||
w.write(boundary);
|
||||
w.write("\r\nContent-Disposition: form-data; name=\"" + resourceName + "\"\r\n\r\n");
|
||||
w.close();
|
||||
JsonParser json = new JsonParser();
|
||||
json.setOutputStyle(IParser.OutputStyle.NORMAL);
|
||||
json.compose(b, resource);
|
||||
b.close();
|
||||
w = new OutputStreamWriter(b, StandardCharsets.UTF_8);
|
||||
w.write("\r\n--");
|
||||
w.write(boundary);
|
||||
w.write("--");
|
||||
w.close();
|
||||
return b.toByteArray();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
package org.hl7.fhir.r4.utils.client.network;
|
||||
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
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 java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class Client {
|
||||
|
||||
public static final String DEFAULT_CHARSET = "UTF-8";
|
||||
private static final long DEFAULT_TIMEOUT = 5000;
|
||||
private ToolingClientLogger logger;
|
||||
private int retryCount;
|
||||
private long timeout = DEFAULT_TIMEOUT;
|
||||
|
||||
public ToolingClientLogger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
public void setLogger(ToolingClientLogger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public int getRetryCount() {
|
||||
return retryCount;
|
||||
}
|
||||
|
||||
public void setRetryCount(int retryCount) {
|
||||
this.retryCount = retryCount;
|
||||
}
|
||||
|
||||
public long getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public void setTimeout(long timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issueOptionsRequest(URI optionsUri,
|
||||
String resourceFormat,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
Request.Builder request = new Request.Builder()
|
||||
.method("OPTIONS", null)
|
||||
.url(optionsUri.toURL());
|
||||
|
||||
return executeFhirRequest(request, resourceFormat, new Headers.Builder().build(), message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issueGetResourceRequest(URI resourceUri,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL());
|
||||
|
||||
return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public int tester(int trytry) {
|
||||
return 5;
|
||||
}
|
||||
public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri,
|
||||
byte[] payload,
|
||||
String resourceFormat,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
return issuePutRequest(resourceUri, payload, resourceFormat, new Headers.Builder().build(), message, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri,
|
||||
byte[] payload,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
if (payload == null) throw new EFhirClientException("PUT requests require a non-null payload");
|
||||
RequestBody body = RequestBody.create(payload);
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL())
|
||||
.put(body);
|
||||
|
||||
return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri,
|
||||
byte[] payload,
|
||||
String resourceFormat,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
return issuePostRequest(resourceUri, payload, resourceFormat, new Headers.Builder().build(), message, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri,
|
||||
byte[] payload,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
if (payload == null) throw new EFhirClientException("POST requests require a non-null payload");
|
||||
RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL())
|
||||
.post(body);
|
||||
|
||||
return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public boolean issueDeleteRequest(URI resourceUri) throws IOException {
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL())
|
||||
.delete();
|
||||
return executeFhirRequest(request, null, new Headers.Builder().build(), null, retryCount, timeout).isSuccessfulRequest();
|
||||
}
|
||||
|
||||
public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat) throws IOException {
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL());
|
||||
|
||||
return executeBundleRequest(request, resourceFormat, new Headers.Builder().build(), null, retryCount, timeout);
|
||||
}
|
||||
|
||||
public Bundle issuePostFeedRequest(URI resourceUri,
|
||||
Map<String, String> parameters,
|
||||
String resourceName,
|
||||
Resource resource,
|
||||
String resourceFormat) throws IOException {
|
||||
String boundary = "----WebKitFormBoundarykbMUo6H8QaUnYtRy";
|
||||
byte[] payload = ByteUtils.encodeFormSubmission(parameters, resourceName, resource, boundary);
|
||||
RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL())
|
||||
.post(body);
|
||||
|
||||
return executeBundleRequest(request, resourceFormat, new Headers.Builder().build(), null, retryCount, timeout);
|
||||
}
|
||||
|
||||
public Bundle postBatchRequest(URI resourceUri,
|
||||
byte[] payload,
|
||||
String resourceFormat,
|
||||
String message,
|
||||
int timeout) throws IOException {
|
||||
if (payload == null) throw new EFhirClientException("POST requests require a non-null payload");
|
||||
RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL())
|
||||
.post(body);
|
||||
|
||||
return executeBundleRequest(request, resourceFormat, new Headers.Builder().build(), message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> Bundle executeBundleRequest(Request.Builder request,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
String message,
|
||||
int retryCount,
|
||||
long timeout) throws IOException {
|
||||
return new FhirRequestBuilder(request)
|
||||
.withLogger(logger)
|
||||
.withResourceFormat(resourceFormat)
|
||||
.withRetryCount(retryCount)
|
||||
.withMessage(message)
|
||||
.withHeaders(headers == null ? new Headers.Builder().build() : headers)
|
||||
.withTimeout(timeout, TimeUnit.MILLISECONDS)
|
||||
.executeAsBatch();
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> executeFhirRequest(Request.Builder request,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
String message,
|
||||
int retryCount,
|
||||
long timeout) throws IOException {
|
||||
return new FhirRequestBuilder(request)
|
||||
.withLogger(logger)
|
||||
.withResourceFormat(resourceFormat)
|
||||
.withRetryCount(retryCount)
|
||||
.withMessage(message)
|
||||
.withHeaders(headers == null ? new Headers.Builder().build() : headers)
|
||||
.withTimeout(timeout, TimeUnit.MILLISECONDS)
|
||||
.execute();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
package org.hl7.fhir.r4.utils.client.network;
|
||||
|
||||
import okhttp3.internal.http2.Header;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 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<Header> headers;
|
||||
|
||||
public ClientHeaders() {
|
||||
this.headers = new ArrayList<>();
|
||||
}
|
||||
|
||||
public ClientHeaders(ArrayList<Header> headers) {
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public ArrayList<Header> headers() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a header to the list of stored headers for network operations.
|
||||
*
|
||||
* @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 {
|
||||
if (headers.contains(header)) {
|
||||
throw new FHIRException("Attempting to add duplicate header, <" + header.name + ", "
|
||||
+ header.value + ">.");
|
||||
}
|
||||
headers.add(header);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a header to the list of stored headers for network operations.
|
||||
*
|
||||
* @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<Header> 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.
|
||||
* @throws FHIRException if the header passed in does not exist within the stored list.
|
||||
*/
|
||||
public ClientHeaders removeHeader(Header 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.");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the passed in headers from the list of stored headers.
|
||||
* @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<Header> headerList) throws FHIRException {
|
||||
headerList.forEach(this::removeHeader);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all stored {@link Header}.
|
||||
*/
|
||||
public ClientHeaders clearHeaders() {
|
||||
headers.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.headers.stream()
|
||||
.map(header -> "\t" + header.name + ":" + header.value)
|
||||
.collect(Collectors.joining(",\n", "{\n", "\n}"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,328 @@
|
|||
package org.hl7.fhir.r4.utils.client.network;
|
||||
|
||||
import okhttp3.*;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.r4.formats.IParser;
|
||||
import org.hl7.fhir.r4.formats.JsonParser;
|
||||
import org.hl7.fhir.r4.formats.XmlParser;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.hl7.fhir.r4.model.OperationOutcome;
|
||||
import org.hl7.fhir.r4.model.Resource;
|
||||
import org.hl7.fhir.r4.utils.ResourceUtilities;
|
||||
import org.hl7.fhir.r4.utils.client.EFhirClientException;
|
||||
import org.hl7.fhir.r4.utils.client.ResourceFormat;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
|
||||
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;
|
||||
|
||||
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 String resourceFormat = null;
|
||||
private Headers headers = null;
|
||||
private String message = null;
|
||||
private int retryCount = 1;
|
||||
/**
|
||||
* The timeout quantity. Used in combination with {@link FhirRequestBuilder#timeoutUnit}.
|
||||
*/
|
||||
private long timeout = 5000;
|
||||
/**
|
||||
* Time unit for {@link FhirRequestBuilder#timeout}.
|
||||
*/
|
||||
private TimeUnit timeoutUnit = TimeUnit.MILLISECONDS;
|
||||
/**
|
||||
* {@link ToolingClientLogger} for log output.
|
||||
*/
|
||||
private ToolingClientLogger logger = null;
|
||||
|
||||
public FhirRequestBuilder(Request.Builder httpRequest) {
|
||||
this.httpRequest = httpRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds necessary default headers, formatting headers, and any passed in {@link Headers} 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);
|
||||
if (format != null) addResourceFormatHeaders(request, format);
|
||||
if (headers != null) addHeaders(request, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds necessary headers for all REST requests.
|
||||
* <li>User-Agent : hapi-fhir-tooling-client</li>
|
||||
* <li>Accept-Charset : {@link FhirRequestBuilder#DEFAULT_CHARSET}</li>
|
||||
*
|
||||
* @param request {@link Request.Builder} to add default headers to.
|
||||
*/
|
||||
protected static void addDefaultHeaders(Request.Builder request) {
|
||||
request.addHeader("User-Agent", "hapi-fhir-tooling-client");
|
||||
request.addHeader("Accept-Charset", DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds necessary headers for the given resource format provided.
|
||||
*
|
||||
* @param request {@link Request.Builder} to add default headers to.
|
||||
*/
|
||||
protected static void addResourceFormatHeaders(Request.Builder request, String format) {
|
||||
request.addHeader("Accept", format);
|
||||
request.addHeader("Content-Type", format + ";charset=" + DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through the passed in {@link Headers} and adds them to the provided {@link Request.Builder}.
|
||||
*
|
||||
* @param request {@link Request.Builder} to add headers to.
|
||||
* @param headers {@link Headers} to add to request.
|
||||
*/
|
||||
protected static void addHeaders(Request.Builder request, Headers headers) {
|
||||
headers.forEach(header -> request.addHeader(header.getFirst(), header.getSecond()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if any of the {@link org.hl7.fhir.r4.model.OperationOutcome.OperationOutcomeIssueComponent} within the
|
||||
* provided {@link OperationOutcome} have an {@link org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity} of
|
||||
* {@link org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity#ERROR} or
|
||||
* {@link org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity#FATAL}
|
||||
*
|
||||
* @param oo {@link OperationOutcome} to evaluate.
|
||||
* @return {@link Boolean#TRUE} if an error exists.
|
||||
*/
|
||||
protected static boolean hasError(OperationOutcome oo) {
|
||||
return (oo.getIssue().stream()
|
||||
.anyMatch(issue -> issue.getSeverity() == OperationOutcome.IssueSeverity.ERROR
|
||||
|| issue.getSeverity() == OperationOutcome.IssueSeverity.FATAL));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the 'location' header from the passes in {@link Headers}. If no value for 'location' exists, the
|
||||
* value for 'content-location' is returned. If neither header exists, we return null.
|
||||
*
|
||||
* @param headers {@link Headers} to evaluate
|
||||
* @return {@link String} header value, or null if no location headers are set.
|
||||
*/
|
||||
protected static String getLocationHeader(Headers headers) {
|
||||
Map<String, List<String>> headerMap = headers.toMultimap();
|
||||
if (headerMap.containsKey(LOCATION_HEADER)) {
|
||||
return headerMap.get(LOCATION_HEADER).get(0);
|
||||
} else if (headerMap.containsKey(CONTENT_LOCATION_HEADER)) {
|
||||
return headerMap.get(CONTENT_LOCATION_HEADER).get(0);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We only ever want to have one copy of the HttpClient kicking around at any given time. If we need to make changes
|
||||
* to any configuration, such as proxy settings, timeout, caches, etc, we can do a per-call configuration through
|
||||
* the {@link OkHttpClient#newBuilder()} method. That will return a builder that shares the same connection pool,
|
||||
* dispatcher, and configuration with the original client.
|
||||
* </p>
|
||||
* The {@link OkHttpClient} uses the proxy auth properties set in the current system properties. The reason we don't
|
||||
* set the proxy address and authentication explicitly, is due to the fact that this class is often used in conjunction
|
||||
* with other http client tools which rely on the system.properties settings to determine proxy settings. It's easier
|
||||
* to keep the method consistent across the board. ...for now.
|
||||
*
|
||||
* @return {@link OkHttpClient} instance
|
||||
*/
|
||||
protected OkHttpClient getHttpClient() {
|
||||
if (okHttpClient == null) {
|
||||
okHttpClient = new OkHttpClient();
|
||||
}
|
||||
|
||||
Authenticator proxyAuthenticator = (route, response) -> {
|
||||
String credential = Credentials.basic(System.getProperty(HTTP_PROXY_USER), System.getProperty(HTTP_PROXY_PASS));
|
||||
return response.request().newBuilder()
|
||||
.header(HEADER_PROXY_AUTH, credential)
|
||||
.build();
|
||||
};
|
||||
|
||||
return okHttpClient.newBuilder()
|
||||
.addInterceptor(new RetryInterceptor(retryCount))
|
||||
.connectTimeout(timeout, timeoutUnit)
|
||||
.writeTimeout(timeout, timeoutUnit)
|
||||
.readTimeout(timeout, timeoutUnit)
|
||||
.proxyAuthenticator(proxyAuthenticator)
|
||||
.build();
|
||||
}
|
||||
|
||||
public FhirRequestBuilder withResourceFormat(String resourceFormat) {
|
||||
this.resourceFormat = resourceFormat;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FhirRequestBuilder withHeaders(Headers headers) {
|
||||
this.headers = headers;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FhirRequestBuilder withMessage(String message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FhirRequestBuilder withRetryCount(int retryCount) {
|
||||
this.retryCount = retryCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FhirRequestBuilder withLogger(ToolingClientLogger logger) {
|
||||
this.logger = logger;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FhirRequestBuilder withTimeout(long timeout, TimeUnit unit) {
|
||||
this.timeout = timeout;
|
||||
this.timeoutUnit = unit;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected Request buildRequest() {
|
||||
return httpRequest.build();
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> execute() throws IOException {
|
||||
formatHeaders(httpRequest, resourceFormat, null);
|
||||
Response response = getHttpClient().newCall(httpRequest.build()).execute();
|
||||
T resource = unmarshalReference(response, resourceFormat);
|
||||
return new ResourceRequest<T>(resource, response.code(), getLocationHeader(response.headers()));
|
||||
}
|
||||
|
||||
public Bundle executeAsBatch() throws IOException {
|
||||
formatHeaders(httpRequest, resourceFormat, null);
|
||||
Response response = getHttpClient().newCall(httpRequest.build()).execute();
|
||||
return unmarshalFeed(response, resourceFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmarshalls a resource from the response stream.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T extends Resource> T unmarshalReference(Response response, String format) {
|
||||
T resource = null;
|
||||
OperationOutcome error = null;
|
||||
|
||||
if (response.body() != null) {
|
||||
try {
|
||||
byte[] body = response.body().bytes();
|
||||
log(response.code(), response.headers(), body);
|
||||
resource = (T) getParser(format).parse(body);
|
||||
if (resource instanceof OperationOutcome && hasError((OperationOutcome) resource)) {
|
||||
error = (OperationOutcome) resource;
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new EFhirClientException("Error reading Http Response: " + ioe.getMessage(), ioe);
|
||||
} catch (Exception e) {
|
||||
throw new EFhirClientException("Error parsing response message: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (error != null) {
|
||||
throw new EFhirClientException("Error from server: " + ResourceUtilities.getErrorDescription(error), error);
|
||||
}
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmarshalls Bundle from response stream.
|
||||
*/
|
||||
protected Bundle unmarshalFeed(Response 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");
|
||||
if (body != null) {
|
||||
if (contentType.contains(ResourceFormat.RESOURCE_XML.getHeader()) || contentType.contains("text/xml+fhir")) {
|
||||
Resource rf = getParser(format).parse(body);
|
||||
if (rf instanceof Bundle)
|
||||
feed = (Bundle) rf;
|
||||
else if (rf instanceof OperationOutcome && hasError((OperationOutcome) rf)) {
|
||||
error = (OperationOutcome) rf;
|
||||
} else {
|
||||
throw new EFhirClientException("Error reading server response: a resource was returned instead");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new EFhirClientException("Error reading Http Response", ioe);
|
||||
} catch (Exception e) {
|
||||
throw new EFhirClientException("Error parsing response message", e);
|
||||
}
|
||||
if (error != null) {
|
||||
throw new EFhirClientException("Error from server: " + ResourceUtilities.getErrorDescription(error), error);
|
||||
}
|
||||
return feed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the appropriate parser based on the format type passed in. Defaults to XML parser if a blank format is
|
||||
* provided...because reasons.
|
||||
* <p>
|
||||
* Currently supports only "json" and "xml" formats.
|
||||
*
|
||||
* @param format One of "json" or "xml".
|
||||
* @return {@link JsonParser} or {@link XmlParser}
|
||||
*/
|
||||
protected IParser getParser(String format) {
|
||||
if (StringUtils.isBlank(format)) {
|
||||
format = ResourceFormat.RESOURCE_XML.getHeader();
|
||||
}
|
||||
if (format.equalsIgnoreCase("json") || format.equalsIgnoreCase(ResourceFormat.RESOURCE_JSON.getHeader())) {
|
||||
return new JsonParser();
|
||||
} else if (format.equalsIgnoreCase("xml") || format.equalsIgnoreCase(ResourceFormat.RESOURCE_XML.getHeader())) {
|
||||
return new XmlParser();
|
||||
} else {
|
||||
throw new EFhirClientException("Invalid format: " + format);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the given {@link Response}, using the current {@link ToolingClientLogger}. If the current
|
||||
* {@link FhirRequestBuilder#logger} is null, no action is taken.
|
||||
*
|
||||
* @param responseCode HTTP response code
|
||||
* @param responseHeaders {@link Headers} from response
|
||||
* @param responseBody Byte array response
|
||||
*/
|
||||
protected void log(int responseCode, Headers responseHeaders, byte[] responseBody) {
|
||||
if (logger != null) {
|
||||
List<String> headerList = new ArrayList<>(Collections.emptyList());
|
||||
Map<String, List<String>> headerMap = responseHeaders.toMultimap();
|
||||
headerMap.keySet().forEach(key -> headerMap.get(key).forEach(value -> headerList.add(key + ":" + value)));
|
||||
|
||||
try {
|
||||
logger.logResponse(Integer.toString(responseCode), headerList, responseBody);
|
||||
} catch (Exception e) {
|
||||
System.out.println("Error parsing response body passed in to logger ->\n" + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
// else { // TODO fix logs
|
||||
// System.out.println("Call to log HTTP response with null ToolingClientLogger set... are you forgetting to " +
|
||||
// "initialize your logger?");
|
||||
// }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package org.hl7.fhir.r4.utils.client.network;
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
import org.hl7.fhir.r4.model.Resource;
|
||||
|
||||
public class ResourceRequest<T extends Resource> {
|
||||
private T payload;
|
||||
private int httpStatus = -1;
|
||||
private String location;
|
||||
|
||||
public ResourceRequest(T payload, int httpStatus, String location) {
|
||||
this.payload = payload;
|
||||
this.httpStatus = httpStatus;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public int getHttpStatus() {
|
||||
return httpStatus;
|
||||
}
|
||||
|
||||
public T getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
public T getReference() {
|
||||
T payloadResource = null;
|
||||
if (payload != null) {
|
||||
payloadResource = payload;
|
||||
}
|
||||
return payloadResource;
|
||||
}
|
||||
|
||||
public boolean isSuccessfulRequest() {
|
||||
return this.httpStatus / 100 == 2;
|
||||
}
|
||||
|
||||
public boolean isUnsuccessfulRequest() {
|
||||
return !isSuccessfulRequest();
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
return location;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package org.hl7.fhir.r4.utils.client.network;
|
||||
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* An {@link Interceptor} for {@link okhttp3.OkHttpClient} that controls the number of times we retry a to execute a
|
||||
* given request, before reporting a failure. This includes unsuccessful return codes and timeouts.
|
||||
*/
|
||||
public class RetryInterceptor implements Interceptor {
|
||||
|
||||
// Delay between retying failed requests, in millis
|
||||
private final long RETRY_TIME = 2000;
|
||||
|
||||
// Maximum number of times to retry the request before failing
|
||||
private final int maxRetry;
|
||||
|
||||
// Internal counter for tracking the number of times we've tried this request
|
||||
private int retryCounter = 0;
|
||||
|
||||
public RetryInterceptor(int maxRetry) {
|
||||
this.maxRetry = maxRetry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response intercept(Interceptor.Chain chain) throws IOException {
|
||||
Request request = chain.request();
|
||||
Response response = null;
|
||||
|
||||
do {
|
||||
try {
|
||||
// If we are retrying a failed request that failed due to a bad response from the server, we must close it first
|
||||
if (response != null) {
|
||||
System.out.println("Previous " + chain.request().method() + " attempt returned HTTP<" + (response.code())
|
||||
+ "> from url -> " + chain.request().url() + ".");
|
||||
response.close();
|
||||
}
|
||||
// System.out.println(chain.request().method() + " attempt <" + (retryCounter + 1) + "> to url -> " + chain.request().url());
|
||||
response = chain.proceed(request);
|
||||
} catch (IOException e) {
|
||||
try {
|
||||
// Include a small break in between requests.
|
||||
Thread.sleep(RETRY_TIME);
|
||||
} catch (InterruptedException e1) {
|
||||
System.out.println(chain.request().method() + " to url -> " + chain.request().url() + " interrupted on try <" + retryCounter + ">");
|
||||
}
|
||||
} finally {
|
||||
retryCounter++;
|
||||
}
|
||||
} while ((response == null || !response.isSuccessful()) && (retryCounter <= maxRetry + 1));
|
||||
|
||||
/*
|
||||
* if something has gone wrong, and we are unable to complete the request, we still need to initialize the return
|
||||
* response so we don't get a null pointer exception.
|
||||
*/
|
||||
return response != null ? response : chain.proceed(request);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,59 +1,57 @@
|
|||
package org.hl7.fhir.r5.terminologies;
|
||||
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.model.*;
|
||||
import org.hl7.fhir.r5.utils.client.network.ClientHeaders;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.model.Bundle;
|
||||
import org.hl7.fhir.r5.model.CanonicalResource;
|
||||
import org.hl7.fhir.r5.model.CapabilityStatement;
|
||||
import org.hl7.fhir.r5.model.Parameters;
|
||||
import org.hl7.fhir.r5.model.TerminologyCapabilities;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
|
||||
public interface TerminologyClient {
|
||||
public String getAddress();
|
||||
public TerminologyCapabilities getTerminologyCapabilities() throws FHIRException;
|
||||
public ValueSet expandValueset(ValueSet vs, Parameters p, Map<String, String> params) throws FHIRException;
|
||||
public Parameters validateCS(Parameters pin) throws FHIRException;
|
||||
public Parameters validateVS(Parameters pin) throws FHIRException;
|
||||
public TerminologyClient setTimeout(int i) throws FHIRException;
|
||||
public TerminologyClient setLogger(ToolingClientLogger txLog) throws FHIRException;
|
||||
public int getRetryCount() throws FHIRException;
|
||||
public TerminologyClient setRetryCount(int retryCount) throws FHIRException;
|
||||
public CapabilityStatement getCapabilitiesStatementQuick() throws FHIRException;
|
||||
public Parameters lookupCode(Map<String, String> params) throws FHIRException;
|
||||
public Bundle validateBatch(Bundle batch);
|
||||
public CanonicalResource read(String type, String id);
|
||||
|
||||
String getAddress();
|
||||
TerminologyCapabilities getTerminologyCapabilities() throws FHIRException;
|
||||
ValueSet expandValueset(ValueSet vs, Parameters p, Map<String, String> params) throws FHIRException;
|
||||
Parameters validateCS(Parameters pin) throws FHIRException;
|
||||
Parameters validateVS(Parameters pin) throws FHIRException;
|
||||
TerminologyClient setTimeout(int i) throws FHIRException;
|
||||
TerminologyClient setLogger(ToolingClientLogger txLog) throws FHIRException;
|
||||
int getRetryCount() throws FHIRException;
|
||||
TerminologyClient setRetryCount(int retryCount) throws FHIRException;
|
||||
CapabilityStatement getCapabilitiesStatementQuick() throws FHIRException;
|
||||
Parameters lookupCode(Map<String, String> params) throws FHIRException;
|
||||
Bundle validateBatch(Bundle batch);
|
||||
CanonicalResource read(String type, String id);
|
||||
ClientHeaders getClientHeaders();
|
||||
TerminologyClient setClientHeaders(ClientHeaders clientHeaders);
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package org.hl7.fhir.r5.utils.client;
|
||||
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.internal.http2.Header;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
|
||||
/*
|
||||
|
@ -35,6 +37,7 @@ import org.hl7.fhir.r5.model.*;
|
|||
import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent;
|
||||
import org.hl7.fhir.r5.utils.client.network.ByteUtils;
|
||||
import org.hl7.fhir.r5.utils.client.network.Client;
|
||||
import org.hl7.fhir.r5.utils.client.network.ClientHeaders;
|
||||
import org.hl7.fhir.r5.utils.client.network.ResourceRequest;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
@ -43,8 +46,7 @@ import java.io.IOException;
|
|||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Very Simple RESTful client. This is purely for use in the standalone
|
||||
|
@ -90,8 +92,11 @@ public class FHIRToolingClient {
|
|||
private int maxResultSetSize = -1;//_count
|
||||
private CapabilityStatement capabilities;
|
||||
private Client client = new Client();
|
||||
private ArrayList<Header> headers = new ArrayList<>();
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
//Pass enpoint for client - URI
|
||||
//Pass endpoint for client - URI
|
||||
public FHIRToolingClient(String baseServiceUrl) throws URISyntaxException {
|
||||
preferredResourceFormat = ResourceFormat.RESOURCE_XML;
|
||||
initialize(baseServiceUrl);
|
||||
|
@ -104,6 +109,14 @@ public class FHIRToolingClient {
|
|||
checkCapabilities();
|
||||
}
|
||||
|
||||
public Client getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void setClient(Client client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
private void checkCapabilities() {
|
||||
try {
|
||||
capabilities = getCapabilitiesStatementQuick();
|
||||
|
@ -131,7 +144,10 @@ public class FHIRToolingClient {
|
|||
TerminologyCapabilities capabilities = null;
|
||||
try {
|
||||
capabilities = (TerminologyCapabilities) client.issueGetResourceRequest(resourceAddress.resolveMetadataTxCaps(),
|
||||
getPreferredResourceFormat(), "TerminologyCapabilities", TIMEOUT_NORMAL).getReference();
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"TerminologyCapabilities",
|
||||
TIMEOUT_NORMAL).getReference();
|
||||
} catch (Exception e) {
|
||||
throw new FHIRException("Error fetching the server's terminology capabilities", e);
|
||||
}
|
||||
|
@ -142,7 +158,10 @@ public class FHIRToolingClient {
|
|||
CapabilityStatement conformance = null;
|
||||
try {
|
||||
conformance = (CapabilityStatement) client.issueGetResourceRequest(resourceAddress.resolveMetadataUri(false),
|
||||
getPreferredResourceFormat(), "CapabilitiesStatement", TIMEOUT_NORMAL).getReference();
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"CapabilitiesStatement",
|
||||
TIMEOUT_NORMAL).getReference();
|
||||
} catch (Exception e) {
|
||||
throw new FHIRException("Error fetching the server's conformance statement", e);
|
||||
}
|
||||
|
@ -153,7 +172,10 @@ public class FHIRToolingClient {
|
|||
if (capabilities != null) return capabilities;
|
||||
try {
|
||||
capabilities = (CapabilityStatement) client.issueGetResourceRequest(resourceAddress.resolveMetadataUri(true),
|
||||
getPreferredResourceFormat(), "CapabilitiesStatement-Quick", TIMEOUT_NORMAL).getReference();
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"CapabilitiesStatement-Quick",
|
||||
TIMEOUT_NORMAL).getReference();
|
||||
} catch (Exception e) {
|
||||
throw new FHIRException("Error fetching the server's capability statement: "+e.getMessage(), e);
|
||||
}
|
||||
|
@ -164,7 +186,10 @@ public class FHIRToolingClient {
|
|||
ResourceRequest<T> result = null;
|
||||
try {
|
||||
result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),
|
||||
getPreferredResourceFormat(), "Read " + resourceClass.getName() + "/" + id, TIMEOUT_NORMAL);
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"Read " + resourceClass.getName() + "/" + id,
|
||||
TIMEOUT_NORMAL);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
|
||||
}
|
||||
|
@ -178,7 +203,10 @@ public class FHIRToolingClient {
|
|||
ResourceRequest<T> result = null;
|
||||
try {
|
||||
result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndIdAndVersion(resourceClass, id, version),
|
||||
getPreferredResourceFormat(), "VRead " + resourceClass.getName() + "/" + id + "/?_history/" + version, TIMEOUT_NORMAL);
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"VRead " + resourceClass.getName() + "/" + id + "/?_history/" + version,
|
||||
TIMEOUT_NORMAL);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
|
||||
}
|
||||
|
@ -192,7 +220,10 @@ public class FHIRToolingClient {
|
|||
ResourceRequest<T> result = null;
|
||||
try {
|
||||
result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndCanonical(resourceClass, canonicalURL),
|
||||
getPreferredResourceFormat(), "Read " + resourceClass.getName() + "?url=" + canonicalURL, TIMEOUT_NORMAL);
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"Read " + resourceClass.getName() + "?url=" + canonicalURL,
|
||||
TIMEOUT_NORMAL);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
|
||||
}
|
||||
|
@ -211,8 +242,11 @@ public class FHIRToolingClient {
|
|||
org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null;
|
||||
try {
|
||||
result = client.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resource.getClass(), resource.getId()),
|
||||
ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(),
|
||||
"Update " + resource.fhirType() + "/" + resource.getId(), TIMEOUT_OPERATION);
|
||||
ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat())),
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"Update " + resource.fhirType() + "/" + resource.getId(),
|
||||
TIMEOUT_OPERATION);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
|
||||
}
|
||||
|
@ -236,7 +270,10 @@ public class FHIRToolingClient {
|
|||
try {
|
||||
result = client.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),
|
||||
ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat())),
|
||||
getPreferredResourceFormat(), "Update " + resource.fhirType() + "/" + id, TIMEOUT_OPERATION);
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"Update " + resource.fhirType() + "/" + id,
|
||||
TIMEOUT_OPERATION);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
|
||||
}
|
||||
|
@ -274,7 +311,7 @@ public class FHIRToolingClient {
|
|||
"POST " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG);
|
||||
} else {
|
||||
client.getLogger().logRequest("GET", url.toString(), null, null);
|
||||
result = client.issueGetResourceRequest(url, getPreferredResourceFormat(), "GET " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG);
|
||||
result = client.issueGetResourceRequest(url, getPreferredResourceFormat(), generateHeaders(), "GET " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG);
|
||||
}
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
|
||||
|
@ -307,7 +344,10 @@ public class FHIRToolingClient {
|
|||
public <T extends Resource> OperationOutcome validate(Class<T> resourceClass, T resource, String id) {
|
||||
ResourceRequest<T> result = null;
|
||||
try {
|
||||
result = client.issuePostRequest(resourceAddress.resolveValidateUri(resourceClass, id), ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), "POST " + resourceClass.getName() + (id != null ? "/" + id : "") + "/$validate", TIMEOUT_OPERATION_LONG);
|
||||
result = client.issuePostRequest(resourceAddress.resolveValidateUri(resourceClass, id),
|
||||
ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat())),
|
||||
getPreferredResourceFormat(), generateHeaders(),
|
||||
"POST " + resourceClass.getName() + (id != null ? "/" + id : "") + "/$validate", TIMEOUT_OPERATION_LONG);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
|
||||
}
|
||||
|
@ -362,7 +402,11 @@ public class FHIRToolingClient {
|
|||
org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null;
|
||||
try {
|
||||
result = client.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand"),
|
||||
ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), null, "ValueSet/$expand?url=" + source.getUrl(), TIMEOUT_OPERATION_EXPAND);
|
||||
ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat())),
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"ValueSet/$expand?url=" + source.getUrl(),
|
||||
TIMEOUT_OPERATION_EXPAND);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
|
||||
}
|
||||
|
@ -376,7 +420,11 @@ public class FHIRToolingClient {
|
|||
public Parameters lookupCode(Map<String, String> params) {
|
||||
org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null;
|
||||
try {
|
||||
result = client.issueGetResourceRequest(resourceAddress.resolveOperationUri(CodeSystem.class, "lookup", params), getPreferredResourceFormat(), "CodeSystem/$lookup", TIMEOUT_NORMAL);
|
||||
result = client.issueGetResourceRequest(resourceAddress.resolveOperationUri(CodeSystem.class, "lookup", params),
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"CodeSystem/$lookup",
|
||||
TIMEOUT_NORMAL);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
@ -394,8 +442,13 @@ public class FHIRToolingClient {
|
|||
}
|
||||
org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null;
|
||||
try {
|
||||
|
||||
result = client.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand", params),
|
||||
ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), null, "ValueSet/$expand?url=" + source.getUrl(), TIMEOUT_OPERATION_EXPAND);
|
||||
ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat())),
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"ValueSet/$expand?url=" + source.getUrl(),
|
||||
TIMEOUT_OPERATION_EXPAND);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
|
||||
}
|
||||
|
@ -415,7 +468,11 @@ public class FHIRToolingClient {
|
|||
ResourceRequest<Resource> result = null;
|
||||
try {
|
||||
result = client.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
|
||||
ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), null, "Closure?name=" + name, TIMEOUT_NORMAL);
|
||||
ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat())),
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"Closure?name=" + name,
|
||||
TIMEOUT_NORMAL);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
|
||||
}
|
||||
|
@ -432,7 +489,11 @@ public class FHIRToolingClient {
|
|||
org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null;
|
||||
try {
|
||||
result = client.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
|
||||
ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), null, "UpdateClosure?name=" + name, TIMEOUT_OPERATION);
|
||||
ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat())),
|
||||
getPreferredResourceFormat(),
|
||||
generateHeaders(),
|
||||
"UpdateClosure?name=" + name,
|
||||
TIMEOUT_OPERATION);
|
||||
if (result.isUnsuccessfulRequest()) {
|
||||
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
|
||||
}
|
||||
|
@ -442,6 +503,22 @@ public class FHIRToolingClient {
|
|||
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();
|
||||
}
|
||||
|
@ -466,6 +543,31 @@ public class FHIRToolingClient {
|
|||
client.setRetryCount(retryCount);
|
||||
}
|
||||
|
||||
public void setClientHeaders(ArrayList<Header> headers) {
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
private Headers generateHeaders() {
|
||||
Headers.Builder builder = new Headers.Builder();
|
||||
// Add basic auth header if it exists
|
||||
if (basicAuthHeaderExists()) {
|
||||
builder.add(getAuthorizationHeader().toString());
|
||||
}
|
||||
// Add any other headers
|
||||
if(this.headers != null) {
|
||||
this.headers.forEach(header -> builder.add(header.toString()));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public boolean basicAuthHeaderExists() {
|
||||
return (username != null) && (password != null);
|
||||
}
|
||||
|
||||
public Header getAuthorizationHeader() {
|
||||
String usernamePassword = username + ":" + password;
|
||||
String base64usernamePassword = Base64.getEncoder().encodeToString(usernamePassword.getBytes());
|
||||
return new Header("Authorization", "Basic " + base64usernamePassword);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,14 +10,7 @@ import org.hl7.fhir.r5.utils.client.EFhirClientException;
|
|||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URLConnection;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
@ -25,28 +18,10 @@ public class Client {
|
|||
|
||||
public static final String DEFAULT_CHARSET = "UTF-8";
|
||||
private static final long DEFAULT_TIMEOUT = 5000;
|
||||
private String username;
|
||||
private String password;
|
||||
private ToolingClientLogger logger;
|
||||
private int retryCount;
|
||||
private long timeout = DEFAULT_TIMEOUT;
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public ToolingClientLogger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
@ -79,25 +54,29 @@ public class Client {
|
|||
.method("OPTIONS", null)
|
||||
.url(optionsUri.toURL());
|
||||
|
||||
return executeFhirRequest(request, resourceFormat, null, message, retryCount, timeout);
|
||||
return executeFhirRequest(request, resourceFormat, new Headers.Builder().build(), message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issueGetResourceRequest(URI resourceUri,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL());
|
||||
|
||||
return executeFhirRequest(request, resourceFormat, null, message, retryCount, timeout);
|
||||
return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public int tester(int trytry) {
|
||||
return 5;
|
||||
}
|
||||
public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri,
|
||||
byte[] payload,
|
||||
String resourceFormat,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
return issuePutRequest(resourceUri, payload, resourceFormat, null, message, timeout);
|
||||
return issuePutRequest(resourceUri, payload, resourceFormat, new Headers.Builder().build(), message, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri,
|
||||
|
@ -120,7 +99,7 @@ public class Client {
|
|||
String resourceFormat,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
return issuePostRequest(resourceUri, payload, resourceFormat, null, message, timeout);
|
||||
return issuePostRequest(resourceUri, payload, resourceFormat, new Headers.Builder().build(), message, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri,
|
||||
|
@ -142,14 +121,14 @@ public class Client {
|
|||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL())
|
||||
.delete();
|
||||
return executeFhirRequest(request, null, null, null, retryCount, timeout).isSuccessfulRequest();
|
||||
return executeFhirRequest(request, null, new Headers.Builder().build(), null, retryCount, timeout).isSuccessfulRequest();
|
||||
}
|
||||
|
||||
public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat) throws IOException {
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL());
|
||||
|
||||
return executeBundleRequest(request, resourceFormat, null, null, retryCount, timeout);
|
||||
return executeBundleRequest(request, resourceFormat, new Headers.Builder().build(), null, retryCount, timeout);
|
||||
}
|
||||
|
||||
public Bundle issuePostFeedRequest(URI resourceUri,
|
||||
|
@ -164,7 +143,7 @@ public class Client {
|
|||
.url(resourceUri.toURL())
|
||||
.post(body);
|
||||
|
||||
return executeBundleRequest(request, resourceFormat, null, null, retryCount, timeout);
|
||||
return executeBundleRequest(request, resourceFormat, new Headers.Builder().build(), null, retryCount, timeout);
|
||||
}
|
||||
|
||||
public Bundle postBatchRequest(URI resourceUri,
|
||||
|
@ -178,10 +157,10 @@ public class Client {
|
|||
.url(resourceUri.toURL())
|
||||
.post(body);
|
||||
|
||||
return executeBundleRequest(request, resourceFormat, null, message, retryCount, timeout);
|
||||
return executeBundleRequest(request, resourceFormat, new Headers.Builder().build(), message, retryCount, timeout);
|
||||
}
|
||||
|
||||
protected <T extends Resource> Bundle executeBundleRequest(Request.Builder request,
|
||||
public <T extends Resource> Bundle executeBundleRequest(Request.Builder request,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
String message,
|
||||
|
@ -197,7 +176,7 @@ public class Client {
|
|||
.executeAsBatch();
|
||||
}
|
||||
|
||||
protected <T extends Resource> ResourceRequest<T> executeFhirRequest(Request.Builder request,
|
||||
public <T extends Resource> ResourceRequest<T> executeFhirRequest(Request.Builder request,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
String message,
|
||||
|
@ -212,23 +191,4 @@ public class Client {
|
|||
.withTimeout(timeout, TimeUnit.MILLISECONDS)
|
||||
.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated It does not appear as though this method is actually being used. Will be removed in a future release
|
||||
* unless a case is made to keep it.
|
||||
*/
|
||||
@Deprecated
|
||||
public Calendar getLastModifiedResponseHeaderAsCalendarObject(URLConnection serverConnection) {
|
||||
String dateTime = null;
|
||||
try {
|
||||
dateTime = serverConnection.getHeaderField("Last-Modified");
|
||||
SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", new Locale("en", "US"));
|
||||
Date lastModifiedTimestamp = format.parse(dateTime);
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(lastModifiedTimestamp);
|
||||
return calendar;
|
||||
} catch (ParseException pe) {
|
||||
throw new EFhirClientException("Error parsing Last-Modified response header " + dateTime, pe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
package org.hl7.fhir.r5.utils.client.network;
|
||||
|
||||
import okhttp3.internal.http2.Header;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 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<Header> headers;
|
||||
|
||||
public ClientHeaders() {
|
||||
this.headers = new ArrayList<>();
|
||||
}
|
||||
|
||||
public ClientHeaders(ArrayList<Header> headers) {
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public ArrayList<Header> headers() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a header to the list of stored headers for network operations.
|
||||
*
|
||||
* @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 {
|
||||
if (headers.contains(header)) {
|
||||
throw new FHIRException("Attempting to add duplicate header, <" + header.name + ", "
|
||||
+ header.value + ">.");
|
||||
}
|
||||
headers.add(header);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a header to the list of stored headers for network operations.
|
||||
*
|
||||
* @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<Header> 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.
|
||||
* @throws FHIRException if the header passed in does not exist within the stored list.
|
||||
*/
|
||||
public ClientHeaders removeHeader(Header 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.");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the passed in headers from the list of stored headers.
|
||||
* @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<Header> headerList) throws FHIRException {
|
||||
headerList.forEach(this::removeHeader);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all stored {@link Header}.
|
||||
*/
|
||||
public ClientHeaders clearHeaders() {
|
||||
headers.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.headers.stream()
|
||||
.map(header -> "\t" + header.name + ":" + header.value)
|
||||
.collect(Collectors.joining(",\n", "{\n", "\n}"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,241 @@
|
|||
package org.hl7.fhir.r5.utils.client;
|
||||
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.internal.http2.Header;
|
||||
import org.hl7.fhir.r5.model.*;
|
||||
import org.hl7.fhir.r5.utils.client.network.Client;
|
||||
import org.hl7.fhir.r5.utils.client.network.ResourceRequest;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
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 java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
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");
|
||||
|
||||
private Client mockClient;
|
||||
private FHIRToolingClient toolingClient;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException, URISyntaxException {
|
||||
mockClient = Mockito.mock(Client.class);
|
||||
ResourceRequest<Resource> resourceResourceRequest = new ResourceRequest<>(generateBundle(), 200, "");
|
||||
|
||||
//GET
|
||||
Mockito.when(mockClient.issueGetResourceRequest(Mockito.any(URI.class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), Mockito.anyString(), Mockito.anyLong()))
|
||||
.thenReturn(resourceResourceRequest);
|
||||
Mockito.when(mockClient.issueGetResourceRequest(Mockito.any(URI.class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), 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()))
|
||||
.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()))
|
||||
.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);
|
||||
//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);
|
||||
Mockito.when(mockClient.issuePostRequest(Mockito.any(URI.class), Mockito.any(byte[].class), Mockito.anyString(),
|
||||
Mockito.any(Headers.class), 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()))
|
||||
.thenReturn(generateBundle());
|
||||
toolingClient = new FHIRToolingClient(TX_ADDR);
|
||||
toolingClient.setClient(mockClient);
|
||||
}
|
||||
|
||||
private ArrayList<Header> getHeaders() {
|
||||
return new ArrayList<>(Arrays.asList(h1, h2, h3));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
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;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
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(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());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTerminologyCapabilities() throws IOException {
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.getTerminologyCapabilities();
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCapabilitiesStatement() throws IOException {
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.getCapabilitiesStatement();
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCapabilitiesStatementQuick() throws IOException {
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.getCapabilitiesStatementQuick();
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void read() throws IOException {
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.read(Patient.class, "id");
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void vread() throws IOException {
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.vread(Patient.class, "id", "version");
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCanonical() throws IOException {
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.getCanonical(Patient.class, "canonicalURL");
|
||||
Mockito.verify(mockClient).issueGetResourceRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.anyString(),
|
||||
headersArgumentCaptor.capture(), ArgumentMatchers.anyString(), ArgumentMatchers.anyLong());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void update() throws IOException {
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.update(generatePatient());
|
||||
Mockito.verify(mockClient).issuePutRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.any(byte[].class),
|
||||
ArgumentMatchers.anyString(), headersArgumentCaptor.capture(), ArgumentMatchers.anyString(),
|
||||
ArgumentMatchers.anyLong());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate() throws IOException {
|
||||
ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
|
||||
toolingClient.setClientHeaders(getHeaders());
|
||||
toolingClient.validate(Patient.class, generatePatient(), "id");
|
||||
Mockito.verify(mockClient).issuePostRequest(ArgumentMatchers.any(URI.class), ArgumentMatchers.any(byte[].class),
|
||||
ArgumentMatchers.anyString(), headersArgumentCaptor.capture(), ArgumentMatchers.anyString(),
|
||||
ArgumentMatchers.anyLong());
|
||||
|
||||
Headers argumentCaptorValue = headersArgumentCaptor.getValue();
|
||||
checkHeaders(argumentCaptorValue);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
package org.hl7.fhir.r5.utils.client.network;
|
||||
|
||||
import okhttp3.internal.http2.Header;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
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;
|
||||
|
||||
class ClientHeadersTest {
|
||||
|
||||
ClientHeaders clientHeaders;
|
||||
|
||||
Header h1 = new Header("header1", "value1");
|
||||
Header h2 = new Header("header2", "value2");
|
||||
Header h3 = new Header("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<Header> 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<Header> 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 Header("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<Header> headersToAddList = Arrays.asList(h1, h2, h3);
|
||||
List<Header> 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<Header> headersToAddList = Arrays.asList(h1, h3);
|
||||
List<Header> headersToRemoveList = Arrays.asList(h2, h3);
|
||||
clientHeaders.addHeaders(headersToAddList);
|
||||
Assertions.assertThrows(FHIRException.class, () -> clientHeaders.removeHeaders(headersToRemoveList));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clearHeaders() {
|
||||
List<Header> headersToAddList = Arrays.asList(h1, h3);
|
||||
clientHeaders.addHeaders(headersToAddList);
|
||||
Assertions.assertEquals(2, clientHeaders.headers().size());
|
||||
clientHeaders.clearHeaders();
|
||||
Assertions.assertEquals(0, clientHeaders.headers().size());
|
||||
}
|
||||
}
|
|
@ -23,14 +23,14 @@ class ClientTest {
|
|||
private HttpUrl serverUrl;
|
||||
private Client client;
|
||||
|
||||
private Address address = new Address()
|
||||
private final Address address = new Address()
|
||||
.setCity("Toronto")
|
||||
.setState("Ontario")
|
||||
.setCountry("Canada");
|
||||
private HumanName humanName = new HumanName()
|
||||
private final HumanName humanName = new HumanName()
|
||||
.addGiven("Mark")
|
||||
.setFamily("Iantorno");
|
||||
private Patient patient = new Patient()
|
||||
private final Patient patient = new Patient()
|
||||
.addName(humanName)
|
||||
.addAddress(address)
|
||||
.setGender(Enumerations.AdministrativeGender.MALE);
|
||||
|
@ -58,7 +58,7 @@ class ClientTest {
|
|||
.setBody(new String(generateResourceBytes(patient)))
|
||||
);
|
||||
ResourceRequest<Resource> resourceRequest = client.issueGetResourceRequest(new URI(serverUrl.toString()),
|
||||
"json", null, TIMEOUT);
|
||||
"json", null, null, TIMEOUT);
|
||||
Assertions.assertTrue(resourceRequest.isSuccessfulRequest());
|
||||
Assertions.assertTrue(patient.equalsDeep(resourceRequest.getPayload()),
|
||||
"GET request returned resource does not match expected.");
|
||||
|
@ -80,7 +80,7 @@ class ClientTest {
|
|||
client.setRetryCount(failedAttempts + 1);
|
||||
|
||||
ResourceRequest<Resource> resourceRequest = client.issueGetResourceRequest(new URI(serverUrl.toString()),
|
||||
"json", null, TIMEOUT);
|
||||
"json", null, null, TIMEOUT);
|
||||
Assertions.assertTrue(resourceRequest.isSuccessfulRequest());
|
||||
Assertions.assertTrue(patient.equalsDeep(resourceRequest.getPayload()),
|
||||
"GET request returned resource does not match expected.");
|
||||
|
@ -102,7 +102,7 @@ class ClientTest {
|
|||
client.setRetryCount(failedAttempts + 1);
|
||||
|
||||
ResourceRequest<Resource> resourceRequest = client.issueGetResourceRequest(new URI(serverUrl.toString()),
|
||||
"json", null, TIMEOUT);
|
||||
"json", null, null, TIMEOUT);
|
||||
Assertions.assertTrue(resourceRequest.isSuccessfulRequest());
|
||||
Assertions.assertTrue(patient.equalsDeep(resourceRequest.getPayload()),
|
||||
"GET request returned resource does not match expected.");
|
||||
|
|
Loading…
Reference in New Issue