diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/VSACImporter.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/VSACImporter.java index 6ea8d60be..90cc8c7ab 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/VSACImporter.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/VSACImporter.java @@ -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 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 importValueSet(byte[] source) throws Exception { -// List res = new ArrayList(); -// Element x = loadXml(new ByteArrayInputStream(source)).getDocumentElement(); -// List 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; -// } } diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR2.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR2.java index 04f733a3e..7675cf011 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR2.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR2.java @@ -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; + } } \ No newline at end of file diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR3.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR3.java index 05efb7e57..fe4b1e607 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR3.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR3.java @@ -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; + } } \ No newline at end of file diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR4.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR4.java index 35251fba6..608ab860f 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR4.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR4.java @@ -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; + } } \ No newline at end of file diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR5.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR5.java index e6489dee4..f3c9041bd 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR5.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR5.java @@ -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; + } } \ No newline at end of file diff --git a/org.hl7.fhir.core.generator/configuration/ResourceType.java b/org.hl7.fhir.core.generator/configuration/ResourceType.java index 7e06f2aec..c3f7045d7 100644 --- a/org.hl7.fhir.core.generator/configuration/ResourceType.java +++ b/org.hl7.fhir.core.generator/configuration/ResourceType.java @@ -20,7 +20,7 @@ public enum ResourceType { public static ResourceType fromCode(String code) throws FHIRException {; {{types-fromCode}} - throw new FHIRException("Unknown resource type"+code); + throw new FHIRException("Unknown resource type "+code); } } \ No newline at end of file diff --git a/org.hl7.fhir.dstu3/pom.xml b/org.hl7.fhir.dstu3/pom.xml index d88b12927..2473711dd 100644 --- a/org.hl7.fhir.dstu3/pom.xml +++ b/org.hl7.fhir.dstu3/pom.xml @@ -51,6 +51,19 @@ true + + + org.apache.httpcomponents + httpclient + true + + + com.squareup.okhttp3 + okhttp + 4.9.0 + true + + org.apache.poi diff --git a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/context/BaseWorkerContext.java b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/context/BaseWorkerContext.java index db33d84b8..46141e8a2 100644 --- a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/context/BaseWorkerContext.java +++ b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/context/BaseWorkerContext.java @@ -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. + + */ diff --git a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/ClientUtils.java b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/ClientUtils.java deleted file mode 100644 index 9508a02ac..000000000 --- a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/ClientUtils.java +++ /dev/null @@ -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 ResourceRequest issueOptionsRequest(URI optionsUri, String resourceFormat, String message, int timeout) { - HttpOptions options = new HttpOptions(optionsUri); - return issueResourceRequest(resourceFormat, options, message, timeout); - } - - public ResourceRequest issueGetResourceRequest(URI resourceUri, String resourceFormat, String message, int timeout) { - HttpGet httpget = new HttpGet(resourceUri); - return issueResourceRequest(resourceFormat, httpget, message, timeout); - } - - public ResourceRequest issuePutRequest(URI resourceUri, byte[] payload, String resourceFormat, List
headers, String message, int timeout) { - HttpPut httpPut = new HttpPut(resourceUri); - return issueResourceRequest(resourceFormat, httpPut, payload, headers, message, timeout); - } - - public ResourceRequest 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 ResourceRequest issuePostRequest(URI resourceUri, byte[] payload, String resourceFormat, List
headers, String message, int timeout) { - HttpPost httpPost = new HttpPost(resourceUri); - return issueResourceRequest(resourceFormat, httpPost, payload, headers, message, timeout); - } - - - public ResourceRequest 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 ResourceRequest issueResourceRequest(String resourceFormat, HttpUriRequest request, String message, int timeout) { - return issueResourceRequest(resourceFormat, request, null, message, timeout); - } - - /** - * @param resourceFormat - * @param options - * @return - */ - protected ResourceRequest 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 ResourceRequest issueResourceRequest(String resourceFormat, HttpUriRequest request, byte[] payload, List
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(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
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 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 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 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 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 headers = new ArrayList<>(); - for (Header h : request.getAllHeaders()) { - headers.add(h.toString()); - } - logger.logRequest(request.getMethod(), request.getURI().toString(), headers, null); - } - } - private void log(HttpEntityEnclosingRequestBase request) { - if (logger != null) { - List headers = new ArrayList<>(); - for (Header h : request.getAllHeaders()) { - headers.add(h.toString()); - } - byte[] cnt = null; - InputStream s; - try { - s = request.getEntity().getContent(); - cnt = IOUtils.toByteArray(s); - s.close(); - } catch (Exception e) { - } - logger.logRequest(request.getMethod(), request.getURI().toString(), headers, cnt); - } - } - - private byte[] log(HttpResponse response) { - byte[] cnt = null; - try { - InputStream s = response.getEntity().getContent(); - cnt = IOUtils.toByteArray(s); - s.close(); - } catch (Exception e) { - } - if (logger != null) { - List headers = new ArrayList<>(); - for (Header h : response.getAllHeaders()) { - headers.add(h.toString()); - } - logger.logResponse(response.getStatusLine().toString(), headers, cnt); - } - 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; - } - - -} \ No newline at end of file diff --git a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/EFhirClientException.java b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/EFhirClientException.java index 6ca535497..93997be8a 100644 --- a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/EFhirClientException.java +++ b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/EFhirClientException.java @@ -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) { diff --git a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/FHIRToolingClient.java b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/FHIRToolingClient.java index 33c563a0e..e9fa37ede 100644 --- a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/FHIRToolingClient.java +++ b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/FHIRToolingClient.java @@ -1,769 +1,457 @@ 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.net.URI; -import java.net.URISyntaxException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.apache.http.Header; -import org.apache.http.HttpHost; -import org.hl7.fhir.dstu3.model.Bundle; -import org.hl7.fhir.dstu3.model.CapabilityStatement; -import org.hl7.fhir.dstu3.model.CodeSystem; -import org.hl7.fhir.dstu3.model.Coding; -import org.hl7.fhir.dstu3.model.ConceptMap; -import org.hl7.fhir.dstu3.model.ExpansionProfile; -import org.hl7.fhir.dstu3.model.OperationOutcome; -import org.hl7.fhir.dstu3.model.Parameters; +import okhttp3.Headers; +import okhttp3.internal.http2.Header; +import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent; -import org.hl7.fhir.dstu3.model.PrimitiveType; -import org.hl7.fhir.dstu3.model.Resource; -import org.hl7.fhir.dstu3.model.StringType; -import org.hl7.fhir.dstu3.model.ValueSet; +import org.hl7.fhir.dstu3.utils.client.network.ByteUtils; +import org.hl7.fhir.dstu3.utils.client.network.Client; +import org.hl7.fhir.dstu3.utils.client.network.ResourceRequest; import org.hl7.fhir.utilities.ToolingClientLogger; import org.hl7.fhir.utilities.Utilities; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + /** - * Very Simple RESTful client. This is purely for use in the standalone + * Very Simple RESTful client. This is purely for use in the standalone * tools jar packages. It doesn't support many features, only what the tools * need. - * + *

* To use, initialize class and set base service URI as follows: - * + * *


  * FHIRSimpleClient fhirClient = new FHIRSimpleClient();
  * fhirClient.initialize("http://my.fhir.domain/myServiceRoot");
  * 
- * + *

* Default Accept and Content-Type headers are application/fhir+xml and application/fhir+json. - * + *

* These can be changed by invoking the following setter functions: - * + * *


  * setPreferredResourceFormat()
  * setPreferredFeedFormat()
  * 
- * - * TODO Review all sad paths. - * - * @author Claude Nanjo + *

+ * TODO Review all sad paths. * + * @author Claude Nanjo */ public class FHIRToolingClient { - - public static final String DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssK"; - public static final String DATE_FORMAT = "yyyy-MM-dd"; - public static final String hostKey = "http.proxyHost"; - public static final String portKey = "http.proxyPort"; - private static final int TIMEOUT_NORMAL = 15; - private static final int TIMEOUT_OPERATION = 30; - private static final int TIMEOUT_OPERATION_LONG = 60; - private static final int TIMEOUT_OPERATION_EXPAND = 120; - - private String base; - private ResourceAddress resourceAddress; - private ResourceFormat preferredResourceFormat; - private HttpHost proxy; - private int maxResultSetSize = -1;//_count - private CapabilityStatement capabilities; - private ClientUtils utils = new ClientUtils(); - - //Pass enpoint for client - URI - public FHIRToolingClient(String baseServiceUrl) throws URISyntaxException { - preferredResourceFormat = ResourceFormat.RESOURCE_XML; - detectProxy(); - initialize(baseServiceUrl); - } - - public FHIRToolingClient(String baseServiceUrl, String username, String password) throws URISyntaxException { + public static final String DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssK"; + public static final String DATE_FORMAT = "yyyy-MM-dd"; + public static final String hostKey = "http.proxyHost"; + public static final String portKey = "http.proxyPort"; + + private static final int TIMEOUT_NORMAL = 1500; + private static final int TIMEOUT_OPERATION = 30000; + private static final int TIMEOUT_ENTRY = 500; + private static final int TIMEOUT_OPERATION_LONG = 60000; + private static final int TIMEOUT_OPERATION_EXPAND = 120000; + + private String base; + private ResourceAddress resourceAddress; + private ResourceFormat preferredResourceFormat; + private int maxResultSetSize = -1;//_count + private CapabilityStatement capabilities; + private Client client = new Client(); + private ArrayList

headers = new ArrayList<>(); + private String username; + private String password; + + //Pass endpoint for client - URI + public FHIRToolingClient(String baseServiceUrl) throws URISyntaxException { preferredResourceFormat = ResourceFormat.RESOURCE_XML; - utils.setUsername(username); - utils.setPassword(password); - detectProxy(); initialize(baseServiceUrl); } - - public void configureProxy(String proxyHost, int proxyPort) { - utils.setProxy(new HttpHost(proxyHost, proxyPort)); - } - - public void detectProxy() { - String host = System.getenv(hostKey); - String port = System.getenv(portKey); - if(host==null) { - host = System.getProperty(hostKey); - } - - if(port==null) { - port = System.getProperty(portKey); - } - - if(host!=null && port!=null) { - this.configureProxy(host, Integer.parseInt(port)); - } - } - - public void initialize(String baseServiceUrl) throws URISyntaxException { - base = baseServiceUrl; - resourceAddress = new ResourceAddress(baseServiceUrl); - this.maxResultSetSize = -1; - checkCapabilities(); - } - - private void checkCapabilities() { - try { + public void initialize(String baseServiceUrl) throws URISyntaxException { + base = baseServiceUrl; + resourceAddress = new ResourceAddress(baseServiceUrl); + this.maxResultSetSize = -1; + checkCapabilities(); + } + + public Client getClient() { + return client; + } + + public void setClient(Client client) { + this.client = client; + } + + private void checkCapabilities() { + try { capabilities = getCapabilitiesStatementQuick(); - } catch (Throwable e) { - } - } + } catch (Throwable e) { + } + } public String getPreferredResourceFormat() { return preferredResourceFormat.getHeader(); } - - public void setPreferredResourceFormat(ResourceFormat resourceFormat) { - preferredResourceFormat = resourceFormat; - } - - public int getMaximumRecordCount() { - return maxResultSetSize; - } - - public void setMaximumRecordCount(int maxResultSetSize) { - this.maxResultSetSize = maxResultSetSize; - } - - public CapabilityStatement getCapabilitiesStatement() { - CapabilityStatement conformance = null; - try { - conformance = (CapabilityStatement)utils.issueGetResourceRequest(resourceAddress.resolveMetadataUri(false), getPreferredResourceFormat(), "CapabilitiesStatement", TIMEOUT_NORMAL).getReference(); - } catch(Exception e) { - handleException("An error has occurred while trying to fetch the server's conformance statement", e); - } - return conformance; - } - - public CapabilityStatement getCapabilitiesStatementQuick() throws EFhirClientException { - if (capabilities != null) - return capabilities; + + public void setPreferredResourceFormat(ResourceFormat resourceFormat) { + preferredResourceFormat = resourceFormat; + } + + public int getMaximumRecordCount() { + return maxResultSetSize; + } + + public void setMaximumRecordCount(int maxResultSetSize) { + this.maxResultSetSize = maxResultSetSize; + } + + public Parameters getTerminologyCapabilities() { + Parameters capabilities = null; try { - capabilities = (CapabilityStatement)utils.issueGetResourceRequest(resourceAddress.resolveMetadataUri(true), getPreferredResourceFormat(), "CapabilitiesStatement-Quick", TIMEOUT_NORMAL).getReference(); - } catch(Exception e) { - handleException("An error has occurred while trying to fetch the server's conformance statement", e); + capabilities = (Parameters) client.issueGetResourceRequest(resourceAddress.resolveMetadataTxCaps(), + getPreferredResourceFormat(), + generateHeaders(), + "TerminologyCapabilities", + TIMEOUT_NORMAL).getReference(); + } catch (Exception e) { + throw new FHIRException("Error fetching the server's terminology capabilities", e); } return capabilities; } - - public T read(Class resourceClass, String id) {//TODO Change this to AddressableResource - ResourceRequest result = null; - try { - result = utils.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id), getPreferredResourceFormat(), "Read "+resourceClass.getName()+"/"+id, TIMEOUT_NORMAL); - result.addErrorStatus(410);//gone - result.addErrorStatus(404);//unknown - result.addSuccessStatus(200);//Only one for now - if(result.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); - } - } catch (Exception e) { - handleException("An error has occurred while trying to read this resource", e); - } - return result.getPayload(); - } - public T vread(Class resourceClass, String id, String version) { - ResourceRequest result = null; - try { - result = utils.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndIdAndVersion(resourceClass, id, version), getPreferredResourceFormat(), "VRead "+resourceClass.getName()+"/"+id+"/?_history/"+version, TIMEOUT_NORMAL); - result.addErrorStatus(410);//gone - result.addErrorStatus(404);//unknown - result.addErrorStatus(405);//unknown - result.addSuccessStatus(200);//Only one for now - if(result.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); - } - } catch (Exception e) { - handleException("An error has occurred while trying to read this version of the resource", e); - } - return result.getPayload(); - } - - // GET fhir/ValueSet?url=http://hl7.org/fhir/ValueSet/clinical-findings&version=0.8 + public CapabilityStatement getCapabilitiesStatement() { + CapabilityStatement conformance = null; + try { + conformance = (CapabilityStatement) client.issueGetResourceRequest(resourceAddress.resolveMetadataUri(false), + getPreferredResourceFormat(), + generateHeaders(), + "CapabilitiesStatement", + TIMEOUT_NORMAL).getReference(); + } catch (Exception e) { + throw new FHIRException("Error fetching the server's conformance statement", e); + } + return conformance; + } + + public CapabilityStatement getCapabilitiesStatementQuick() throws EFhirClientException { + if (capabilities != null) return capabilities; + try { + capabilities = (CapabilityStatement) client.issueGetResourceRequest(resourceAddress.resolveMetadataUri(true), + getPreferredResourceFormat(), + generateHeaders(), + "CapabilitiesStatement-Quick", + TIMEOUT_NORMAL).getReference(); + } catch (Exception e) { + throw new FHIRException("Error fetching the server's capability statement: "+e.getMessage(), e); + } + return capabilities; + } + + public T read(Class resourceClass, String id) {//TODO Change this to AddressableResource + ResourceRequest result = null; + try { + result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id), + getPreferredResourceFormat(), + generateHeaders(), + "Read " + resourceClass.getName() + "/" + id, + TIMEOUT_NORMAL); + if (result.isUnsuccessfulRequest()) { + throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); + } + } catch (Exception e) { + throw new FHIRException(e); + } + return result.getPayload(); + } + + public T vread(Class resourceClass, String id, String version) { + ResourceRequest result = null; + try { + result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndIdAndVersion(resourceClass, id, version), + 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()); + } + } catch (Exception e) { + throw new FHIRException("Error trying to read this version of the resource", e); + } + return result.getPayload(); + } public T getCanonical(Class resourceClass, String canonicalURL) { ResourceRequest result = null; try { - result = utils.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndCanonical(resourceClass, canonicalURL), getPreferredResourceFormat(), "Read "+resourceClass.getName()+"?url="+canonicalURL, TIMEOUT_NORMAL); - result.addErrorStatus(410);//gone - result.addErrorStatus(404);//unknown - result.addErrorStatus(405);//unknown - result.addSuccessStatus(200);//Only one for now - if(result.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); + result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndCanonical(resourceClass, canonicalURL), + getPreferredResourceFormat(), + generateHeaders(), + "Read " + resourceClass.getName() + "?url=" + canonicalURL, + TIMEOUT_NORMAL); + if (result.isUnsuccessfulRequest()) { + throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); } } catch (Exception e) { handleException("An error has occurred while trying to read this version of the resource", e); } Bundle bnd = (Bundle) result.getPayload(); if (bnd.getEntry().size() == 0) - throw new EFhirClientException("No matching resource found for canonical URL '"+canonicalURL+"'"); + throw new EFhirClientException("No matching resource found for canonical URL '" + canonicalURL + "'"); if (bnd.getEntry().size() > 1) - throw new EFhirClientException("Multiple matching resources found for canonical URL '"+canonicalURL+"'"); + throw new EFhirClientException("Multiple matching resources found for canonical URL '" + canonicalURL + "'"); return (T) bnd.getEntry().get(0).getResource(); } - - + public Resource update(Resource resource) { - ResourceRequest result = null; + org.hl7.fhir.dstu3.utils.client.network.ResourceRequest result = null; try { - List
headers = null; - result = utils.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resource.getClass(), resource.getId()),utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, - "Update "+resource.fhirType()+"/"+resource.getId(), TIMEOUT_OPERATION); - result.addErrorStatus(410);//gone - result.addErrorStatus(404);//unknown - result.addErrorStatus(405); - result.addErrorStatus(422);//Unprocessable Entity - result.addSuccessStatus(200); - result.addSuccessStatus(201); - if(result.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); + result = client.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resource.getClass(), resource.getId()), + 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()); } - } catch(Exception e) { + } catch (Exception e) { throw new EFhirClientException("An error has occurred while trying to update this resource", e); } // TODO oe 26.1.2015 could be made nicer if only OperationOutcome locationheader is returned with an operationOutcome would be returned (and not the resource also) we make another read try { - OperationOutcome operationOutcome = (OperationOutcome)result.getPayload(); + OperationOutcome operationOutcome = (OperationOutcome) result.getPayload(); ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = ResourceAddress.parseCreateLocation(result.getLocation()); - return this.vread(resource.getClass(), resVersionedIdentifier.getId(),resVersionedIdentifier.getVersionId()); - } catch(ClassCastException e) { + return this.vread(resource.getClass(), resVersionedIdentifier.getId(), resVersionedIdentifier.getVersionId()); + } catch (ClassCastException e) { // if we fall throught we have the correct type already in the create } return result.getPayload(); } - public T update(Class resourceClass, T resource, String id) { - ResourceRequest result = null; - try { - List
headers = null; - result = utils.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, - "Update "+resource.fhirType()+"/"+id, TIMEOUT_OPERATION); - result.addErrorStatus(410);//gone - result.addErrorStatus(404);//unknown - result.addErrorStatus(405); - result.addErrorStatus(422);//Unprocessable Entity - result.addSuccessStatus(200); - result.addSuccessStatus(201); - if(result.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); - } - } catch(Exception e) { - throw new EFhirClientException("An error has occurred while trying to update this resource", e); - } - // TODO oe 26.1.2015 could be made nicer if only OperationOutcome locationheader is returned with an operationOutcome would be returned (and not the resource also) we make another read - try { - OperationOutcome operationOutcome = (OperationOutcome)result.getPayload(); - ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = ResourceAddress.parseCreateLocation(result.getLocation()); - return this.vread(resourceClass, resVersionedIdentifier.getId(),resVersionedIdentifier.getVersionId()); - } catch(ClassCastException e) { - // if we fall throught we have the correct type already in the create - } - - return result.getPayload(); - } - -// -// public boolean delete(Class resourceClass, String id) { -// try { -// return utils.issueDeleteRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id), proxy); -// } catch(Exception e) { -// throw new EFhirClientException("An error has occurred while trying to delete this resource", e); -// } -// -// } - -// -// public OperationOutcome create(Class resourceClass, T resource) { -// ResourceRequest resourceRequest = null; -// try { -// List
headers = null; -// resourceRequest = utils.issuePostRequest(resourceAddress.resolveGetUriFromResourceClass(resourceClass),utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, proxy); -// resourceRequest.addSuccessStatus(201); -// if(resourceRequest.isUnsuccessfulRequest()) { -// throw new EFhirClientException("Server responded with HTTP error code " + resourceRequest.getHttpStatus(), (OperationOutcome)resourceRequest.getPayload()); -// } -// } catch(Exception e) { -// handleException("An error has occurred while trying to create this resource", e); -// } -// OperationOutcome operationOutcome = null;; -// try { -// operationOutcome = (OperationOutcome)resourceRequest.getPayload(); -// ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = -// ResourceAddress.parseCreateLocation(resourceRequest.getLocation()); -// OperationOutcomeIssueComponent issue = operationOutcome.addIssue(); -// issue.setSeverity(IssueSeverity.INFORMATION); -// issue.setUserData(ResourceAddress.ResourceVersionedIdentifier.class.toString(), -// resVersionedIdentifier); -// return operationOutcome; -// } catch(ClassCastException e) { -// // some server (e.g. grahams) returns the resource directly -// operationOutcome = new OperationOutcome(); -// OperationOutcomeIssueComponent issue = operationOutcome.addIssue(); -// issue.setSeverity(IssueSeverity.INFORMATION); -// issue.setUserData(ResourceRequest.class.toString(), -// resourceRequest.getPayload()); -// return operationOutcome; -// } -// } - -// -// public Bundle history(Calendar lastUpdate, Class resourceClass, String id) { -// Bundle history = null; -// try { -// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceId(resourceClass, id, lastUpdate, maxResultSetSize), getPreferredResourceFormat(), proxy); -// } catch (Exception e) { -// handleException("An error has occurred while trying to retrieve history information for this resource", e); -// } -// return history; -// } - -// -// public Bundle history(Date lastUpdate, Class resourceClass, String id) { -// Bundle history = null; -// try { -// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceId(resourceClass, id, lastUpdate, maxResultSetSize), getPreferredResourceFormat(), proxy); -// } catch (Exception e) { -// handleException("An error has occurred while trying to retrieve history information for this resource", e); -// } -// return history; -// } -// -// -// public Bundle history(Calendar lastUpdate, Class resourceClass) { -// Bundle history = null; -// try { -// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceType(resourceClass, lastUpdate, maxResultSetSize), getPreferredResourceFormat(), proxy); -// } catch (Exception e) { -// handleException("An error has occurred while trying to retrieve history information for this resource type", e); -// } -// return history; -// } -// -// -// public Bundle history(Date lastUpdate, Class resourceClass) { -// Bundle history = null; -// try { -// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceType(resourceClass, lastUpdate, maxResultSetSize), getPreferredResourceFormat(), proxy); -// } catch (Exception e) { -// handleException("An error has occurred while trying to retrieve history information for this resource type", e); -// } -// return history; -// } -// -// -// public Bundle history(Class resourceClass) { -// Bundle history = null; -// try { -// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceType(resourceClass, maxResultSetSize), getPreferredResourceFormat(), proxy); -// } catch (Exception e) { -// handleException("An error has occurred while trying to retrieve history information for this resource type", e); -// } -// return history; -// } -// -// -// public Bundle history(Class resourceClass, String id) { -// Bundle history = null; -// try { -// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceId(resourceClass, id, maxResultSetSize), getPreferredResourceFormat(), proxy); -// } catch (Exception e) { -// handleException("An error has occurred while trying to retrieve history information for this resource", e); -// } -// return history; -// } -// -// -// public Bundle history(Date lastUpdate) { -// Bundle history = null; -// try { -// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForAllResources(lastUpdate, maxResultSetSize), getPreferredResourceFormat(), proxy); -// } catch (Exception e) { -// handleException("An error has occurred while trying to retrieve history since last update",e); -// } -// return history; -// } -// -// -// public Bundle history(Calendar lastUpdate) { -// Bundle history = null; -// try { -// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForAllResources(lastUpdate, maxResultSetSize), getPreferredResourceFormat(), proxy); -// } catch (Exception e) { -// handleException("An error has occurred while trying to retrieve history since last update",e); -// } -// return history; -// } -// -// -// public Bundle history() { -// Bundle history = null; -// try { -// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForAllResources(maxResultSetSize), getPreferredResourceFormat(), proxy); -// } catch (Exception e) { -// handleException("An error has occurred while trying to retrieve history since last update",e); -// } -// return history; -// } -// -// -// public Bundle search(Class resourceClass, Map parameters) { -// Bundle searchResults = null; -// try { -// searchResults = utils.issueGetFeedRequest(resourceAddress.resolveSearchUri(resourceClass, parameters), getPreferredResourceFormat(), proxy); -// } catch (Exception e) { -// handleException("Error performing search with parameters " + parameters, e); -// } -// return searchResults; -// } -// -// -// public Bundle searchPost(Class resourceClass, T resource, Map parameters) { -// Bundle searchResults = null; -// try { -// searchResults = utils.issuePostFeedRequest(resourceAddress.resolveSearchUri(resourceClass, new HashMap()), parameters, "src", resource, getPreferredResourceFormat()); -// } catch (Exception e) { -// handleException("Error performing search with parameters " + parameters, e); -// } -// return searchResults; -// } - - - public Parameters operateType(Class resourceClass, String name, Parameters params) { - boolean complex = false; - for (ParametersParameterComponent p : params.getParameter()) - complex = complex || !(p.getValue() instanceof PrimitiveType); - Parameters searchResults = null; - String ps = ""; - try { - if (!complex) - for (ParametersParameterComponent p : params.getParameter()) - if (p.getValue() instanceof PrimitiveType) - ps += p.getName() + "=" + Utilities.encodeUri(((PrimitiveType) p.getValue()).asStringValue())+"&"; - ResourceRequest result; - if (complex) - result = utils.issuePostRequest(resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps), utils.getResourceAsByteArray(params, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), - "POST "+resourceClass.getName()+"/$"+name, TIMEOUT_OPERATION_LONG); - else - result = utils.issueGetResourceRequest(resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps), getPreferredResourceFormat(), "GET "+resourceClass.getName()+"/$"+name, TIMEOUT_OPERATION_LONG); - result.addErrorStatus(410);//gone - result.addErrorStatus(404);//unknown - result.addSuccessStatus(200);//Only one for now - if(result.isUnsuccessfulRequest()) - throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); - if (result.getPayload() instanceof Parameters) - return (Parameters) result.getPayload(); - else { - Parameters p_out = new Parameters(); - p_out.addParameter().setName("return").setResource(result.getPayload()); - return p_out; - } - } catch (Exception e) { - handleException("Error performing operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e); - } - return null; - } - - - public Bundle transaction(Bundle batch) { - Bundle transactionResult = null; - try { - transactionResult = utils.postBatchRequest(resourceAddress.getBaseServiceUri(), utils.getFeedAsByteArray(batch, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), "transaction", TIMEOUT_NORMAL+batch.getEntry().size()); - } catch (Exception e) { - handleException("An error occurred trying to process this transaction request", e); - } - return transactionResult; - } - - @SuppressWarnings("unchecked") - public OperationOutcome validate(Class resourceClass, T resource, String id) { - ResourceRequest result = null; - try { - result = utils.issuePostRequest(resourceAddress.resolveValidateUri(resourceClass, id), utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), "POST "+resourceClass.getName()+(id != null ? "/"+id : "")+"/$validate", TIMEOUT_OPERATION_LONG); - result.addErrorStatus(400);//gone - result.addErrorStatus(422);//Unprocessable Entity - result.addSuccessStatus(200);//OK - if(result.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); - } - } catch(Exception e) { - handleException("An error has occurred while trying to validate this resource", e); - } - return (OperationOutcome)result.getPayload(); - } - - /* change to meta operations - - public List getAllTags() { - TagListRequest result = null; - try { - result = utils.issueGetRequestForTagList(resourceAddress.resolveGetAllTags(), getPreferredResourceFormat(), null, proxy); - } catch (Exception e) { - handleException("An error has occurred while trying to retrieve all tags", e); - } - return result.getPayload(); - } - - - public List getAllTagsForResourceType(Class resourceClass) { - TagListRequest result = null; - try { - result = utils.issueGetRequestForTagList(resourceAddress.resolveGetAllTagsForResourceType(resourceClass), getPreferredResourceFormat(), null, proxy); - } catch (Exception e) { - handleException("An error has occurred while trying to retrieve tags for this resource type", e); - } - return result.getPayload(); - } - - - public List getTagsForReference(Class resource, String id) { - TagListRequest result = null; - try { - result = utils.issueGetRequestForTagList(resourceAddress.resolveGetTagsForReference(resource, id), getPreferredResourceFormat(), null, proxy); - } catch (Exception e) { - handleException("An error has occurred while trying to retrieve tags for this resource", e); - } - return result.getPayload(); - } - - - public List getTagsForResourceVersion(Class resource, String id, String versionId) { - TagListRequest result = null; - try { - result = utils.issueGetRequestForTagList(resourceAddress.resolveGetTagsForResourceVersion(resource, id, versionId), getPreferredResourceFormat(), null, proxy); - } catch (Exception e) { - handleException("An error has occurred while trying to retrieve tags for this resource version", e); - } - return result.getPayload(); - } - -// -// public boolean deleteTagsForReference(Class resourceClass, String id) { -// try { -// return utils.issueDeleteRequest(resourceAddress.resolveGetTagsForReference(resourceClass, id), proxy); -// } catch(Exception e) { -// handleException("An error has occurred while trying to retrieve tags for this resource version", e); -// throw new EFhirClientException("An error has occurred while trying to delete this resource", e); -// } -// -// } -// -// -// public boolean deleteTagsForResourceVersion(Class resourceClass, String id, List tags, String version) { -// try { -// return utils.issueDeleteRequest(resourceAddress.resolveGetTagsForResourceVersion(resourceClass, id, version), proxy); -// } catch(Exception e) { -// handleException("An error has occurred while trying to retrieve tags for this resource version", e); -// throw new EFhirClientException("An error has occurred while trying to delete this resource", e); -// } -// } - - - public List createTags(List tags, Class resourceClass, String id) { - TagListRequest request = null; - try { - request = utils.issuePostRequestForTagList(resourceAddress.resolveGetTagsForReference(resourceClass, id),utils.getTagListAsByteArray(tags, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), null, proxy); - request.addSuccessStatus(201); - request.addSuccessStatus(200); - if(request.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server responded with HTTP error code " + request.getHttpStatus()); - } - } catch(Exception e) { - handleException("An error has occurred while trying to set tags for this resource", e); - } - return request.getPayload(); - } - - - public List createTags(List tags, Class resourceClass, String id, String version) { - TagListRequest request = null; - try { - request = utils.issuePostRequestForTagList(resourceAddress.resolveGetTagsForResourceVersion(resourceClass, id, version),utils.getTagListAsByteArray(tags, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), null, proxy); - request.addSuccessStatus(201); - request.addSuccessStatus(200); - if(request.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server responded with HTTP error code " + request.getHttpStatus()); - } - } catch(Exception e) { - handleException("An error has occurred while trying to set the tags for this resource version", e); - } - return request.getPayload(); - } - - - public List deleteTags(List tags, Class resourceClass, String id, String version) { - TagListRequest request = null; - try { - request = utils.issuePostRequestForTagList(resourceAddress.resolveDeleteTagsForResourceVersion(resourceClass, id, version),utils.getTagListAsByteArray(tags, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), null, proxy); - request.addSuccessStatus(201); - request.addSuccessStatus(200); - if(request.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server responded with HTTP error code " + request.getHttpStatus()); - } - } catch(Exception e) { - handleException("An error has occurred while trying to delete the tags for this resource version", e); - } - return request.getPayload(); - } - */ - - /** - * Helper method to prevent nesting of previously thrown EFhirClientExceptions - * - * @param e - * @throws EFhirClientException - */ - protected void handleException(String message, Exception e) throws EFhirClientException { - if(e instanceof EFhirClientException) { - throw (EFhirClientException)e; - } else { - throw new EFhirClientException(message, e); - } - } - - /** - * Helper method to determine whether desired resource representation - * is Json or XML. - * - * @param format - * @return - */ - protected boolean isJson(String format) { - boolean isJson = false; - if(format.toLowerCase().contains("json")) { - isJson = true; - } - return isJson; - } - - public Bundle fetchFeed(String url) { - Bundle feed = null; - try { - feed = utils.issueGetFeedRequest(new URI(url), getPreferredResourceFormat()); - } catch (Exception e) { - handleException("An error has occurred while trying to retrieve history since last update",e); - } - return feed; - } - - public ValueSet expandValueset(ValueSet source, ExpansionProfile profile) { - List
headers = null; - Parameters p = new Parameters(); - p.addParameter().setName("valueSet").setResource(source); - if (profile != null) - p.addParameter().setName("profile").setResource(profile); - ResourceRequest result = utils.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand"), - utils.getResourceAsByteArray(p, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, "ValueSet/$expand?url="+source.getUrl(), TIMEOUT_OPERATION_EXPAND); - result.addErrorStatus(410);//gone - result.addErrorStatus(404);//unknown - result.addErrorStatus(405); - result.addErrorStatus(422);//Unprocessable Entity - result.addSuccessStatus(200); - result.addSuccessStatus(201); - if(result.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); + public T update(Class resourceClass, T resource, String id) { + ResourceRequest result = null; + try { + result = client.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id), + ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat())), + getPreferredResourceFormat(), + generateHeaders(), + "Update " + resource.fhirType() + "/" + id, + TIMEOUT_OPERATION); + if (result.isUnsuccessfulRequest()) { + throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); + } + } catch (Exception e) { + throw new EFhirClientException("An error has occurred while trying to update this resource", e); } - return (ValueSet) result.getPayload(); + // TODO oe 26.1.2015 could be made nicer if only OperationOutcome locationheader is returned with an operationOutcome would be returned (and not the resource also) we make another read + try { + OperationOutcome operationOutcome = (OperationOutcome) result.getPayload(); + ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = ResourceAddress.parseCreateLocation(result.getLocation()); + return this.vread(resourceClass, resVersionedIdentifier.getId(), resVersionedIdentifier.getVersionId()); + } catch (ClassCastException e) { + // if we fall through we have the correct type already in the create + } + + return result.getPayload(); } - + public Parameters operateType(Class resourceClass, String name, Parameters params) { + boolean complex = false; + for (ParametersParameterComponent p : params.getParameter()) + complex = complex || !(p.getValue() instanceof PrimitiveType); + String ps = ""; + try { + if (!complex) + for (ParametersParameterComponent p : params.getParameter()) + if (p.getValue() instanceof PrimitiveType) + ps += p.getName() + "=" + Utilities.encodeUri(((PrimitiveType) p.getValue()).asStringValue()) + "&"; + ResourceRequest result; + URI url = resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps); + if (complex) { + byte[] body = ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat())); + client.getLogger().logRequest("POST", url.toString(), null, body); + result = client.issuePostRequest(url, body, getPreferredResourceFormat(), + "POST " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG); + } else { + client.getLogger().logRequest("GET", url.toString(), null, null); + 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()); + } + if (result.getPayload() instanceof Parameters) { + return (Parameters) result.getPayload(); + } else { + Parameters p_out = new Parameters(); + p_out.addParameter().setName("return").setResource(result.getPayload()); + return p_out; + } + } catch (Exception e) { + handleException("Error performing operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e); + } + return null; + } + + + public Bundle transaction(Bundle batch) { + Bundle transactionResult = null; + try { + transactionResult = client.postBatchRequest(resourceAddress.getBaseServiceUri(), ByteUtils.resourceToByteArray(batch, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), "transaction", TIMEOUT_OPERATION + (TIMEOUT_ENTRY * batch.getEntry().size())); + } catch (Exception e) { + handleException("An error occurred trying to process this transaction request", e); + } + return transactionResult; + } + + @SuppressWarnings("unchecked") + public OperationOutcome validate(Class resourceClass, T resource, String id) { + ResourceRequest result = null; + try { + 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()); + } + } catch (Exception e) { + handleException("An error has occurred while trying to validate this resource", e); + } + return (OperationOutcome) result.getPayload(); + } + + /** + * Helper method to prevent nesting of previously thrown EFhirClientExceptions + * + * @param e + * @throws EFhirClientException + */ + protected void handleException(String message, Exception e) throws EFhirClientException { + if (e instanceof EFhirClientException) { + throw (EFhirClientException) e; + } else { + throw new EFhirClientException(message, e); + } + } + + /** + * Helper method to determine whether desired resource representation + * is Json or XML. + * + * @param format + * @return + */ + protected boolean isJson(String format) { + boolean isJson = false; + if (format.toLowerCase().contains("json")) { + isJson = true; + } + return isJson; + } + + public Bundle fetchFeed(String url) { + Bundle feed = null; + try { + feed = client.issueGetFeedRequest(new URI(url), getPreferredResourceFormat()); + } catch (Exception e) { + handleException("An error has occurred while trying to retrieve history since last update", e); + } + return feed; + } + + public ValueSet expandValueset(ValueSet source, Parameters expParams) { + Parameters p = expParams == null ? new Parameters() : expParams.copy(); + p.addParameter().setName("valueSet").setResource(source); + org.hl7.fhir.dstu3.utils.client.network.ResourceRequest result = null; + try { + result = client.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "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()); + } + } catch (IOException e) { + e.printStackTrace(); + } + return result == null ? null : (ValueSet) result.getPayload(); + } + + public Parameters lookupCode(Map params) { - ResourceRequest result = utils.issueGetResourceRequest(resourceAddress.resolveOperationUri(CodeSystem.class, "lookup", params), getPreferredResourceFormat(), "CodeSystem/$lookup", TIMEOUT_NORMAL); - result.addErrorStatus(410);//gone - result.addErrorStatus(404);//unknown - result.addErrorStatus(405); - result.addErrorStatus(422);//Unprocessable Entity - result.addSuccessStatus(200); - result.addSuccessStatus(201); - if(result.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); + org.hl7.fhir.dstu3.utils.client.network.ResourceRequest result = null; + try { + result = client.issueGetResourceRequest(resourceAddress.resolveOperationUri(CodeSystem.class, "lookup", params), + getPreferredResourceFormat(), + generateHeaders(), + "CodeSystem/$lookup", + TIMEOUT_NORMAL); + } catch (IOException e) { + e.printStackTrace(); + } + if (result.isUnsuccessfulRequest()) { + throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); } return (Parameters) result.getPayload(); } + public ValueSet expandValueset(ValueSet source, ExpansionProfile profile, Map params) { - List
headers = null; Parameters p = new Parameters(); p.addParameter().setName("valueSet").setResource(source); if (profile != null) p.addParameter().setName("profile").setResource(profile); - for (String n : params.keySet()) - p.addParameter().setName(n).setValue(new StringType(params.get(n))); - ResourceRequest result = utils.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand", params), - utils.getResourceAsByteArray(p, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, "ValueSet/$expand?url="+source.getUrl(), TIMEOUT_OPERATION_EXPAND); - result.addErrorStatus(410);//gone - result.addErrorStatus(404);//unknown - result.addErrorStatus(405); - result.addErrorStatus(422);//Unprocessable Entity - result.addSuccessStatus(200); - result.addSuccessStatus(201); - if(result.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); + + org.hl7.fhir.dstu3.utils.client.network.ResourceRequest result = null; + try { + result = client.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand", params), + 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()); + } + } catch (IOException e) { + e.printStackTrace(); } - return (ValueSet) result.getPayload(); + return result == null ? null : (ValueSet) result.getPayload(); } - -// public ValueSet expandValueset(ValueSet source, ExpansionProfile profile, Map params) { -// List
headers = null; -// ResourceRequest result = utils.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand", params), -// utils.getResourceAsByteArray(source, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, proxy); -// result.addErrorStatus(410);//gone -// result.addErrorStatus(404);//unknown -// result.addErrorStatus(405); -// result.addErrorStatus(422);//Unprocessable Entity -// result.addSuccessStatus(200); -// result.addSuccessStatus(201); -// if(result.isUnsuccessfulRequest()) { -// throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); -// } -// return (ValueSet) result.getPayload(); -// } - - + + public ValueSet expandValueset(ValueSet source, Parameters expParams, Map params) { + Parameters p = expParams == null ? new Parameters() : expParams.copy(); + p.addParameter().setName("valueSet").setResource(source); + for (String n : params.keySet()) { + p.addParameter().setName(n).setValue(new StringType(params.get(n))); + } + org.hl7.fhir.dstu3.utils.client.network.ResourceRequest result = null; + try { + + result = client.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand", params), + 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()); + } + } catch (IOException e) { + e.printStackTrace(); + } + return result == null ? null : (ValueSet) result.getPayload(); + } + public String getAddress() { return base; } @@ -771,85 +459,109 @@ public class FHIRToolingClient { public ConceptMap initializeClosure(String name) { Parameters params = new Parameters(); params.addParameter().setName("name").setValue(new StringType(name)); - List
headers = null; - ResourceRequest result = utils.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap()), - utils.getResourceAsByteArray(params, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, "Closure?name="+name, TIMEOUT_NORMAL); - result.addErrorStatus(410);//gone - result.addErrorStatus(404);//unknown - result.addErrorStatus(405); - result.addErrorStatus(422);//Unprocessable Entity - result.addSuccessStatus(200); - result.addSuccessStatus(201); - if(result.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); + ResourceRequest result = null; + try { + result = client.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap()), + 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()); + } + } catch (IOException e) { + e.printStackTrace(); } - return (ConceptMap) result.getPayload(); + return result == null ? null : (ConceptMap) result.getPayload(); } public ConceptMap updateClosure(String name, Coding coding) { Parameters params = new Parameters(); params.addParameter().setName("name").setValue(new StringType(name)); params.addParameter().setName("concept").setValue(coding); - List
headers = null; - ResourceRequest result = utils.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap()), - utils.getResourceAsByteArray(params, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, "UpdateClosure?name="+name, TIMEOUT_OPERATION); - result.addErrorStatus(410);//gone - result.addErrorStatus(404);//unknown - result.addErrorStatus(405); - result.addErrorStatus(422);//Unprocessable Entity - result.addSuccessStatus(200); - result.addSuccessStatus(201); - if(result.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); + org.hl7.fhir.dstu3.utils.client.network.ResourceRequest result = null; + try { + result = client.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap()), + 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()); + } + } catch (IOException e) { + e.printStackTrace(); } - return (ConceptMap) result.getPayload(); - } - - public int getTimeout() { - return utils.getTimeout(); - } - - public void setTimeout(int timeout) { - utils.setTimeout(timeout); + return result == null ? null : (ConceptMap) result.getPayload(); } public String getUsername() { - return utils.getUsername(); + return username; } public void setUsername(String username) { - utils.setUsername(username); + this.username = username; } public String getPassword() { - return utils.getPassword(); + return password; } public void setPassword(String password) { - utils.setPassword(password); + this.password = password; } - - public Parameters getTerminologyCapabilities() { - return (Parameters) utils.issueGetResourceRequest(resourceAddress.resolveMetadataTxCaps(), getPreferredResourceFormat(), "TerminologyCapabilities", TIMEOUT_NORMAL).getReference(); + public long getTimeout() { + return client.getTimeout(); } + public void setTimeout(long timeout) { + client.setTimeout(timeout); + } - public org.hl7.fhir.utilities.ToolingClientLogger getLogger() { - return utils.getLogger(); + public ToolingClientLogger getLogger() { + return client.getLogger(); } public void setLogger(ToolingClientLogger logger) { - utils.setLogger(logger); + client.setLogger(logger); } - + public int getRetryCount() { - return utils.getRetryCount(); + return client.getRetryCount(); } public void setRetryCount(int retryCount) { - utils.setRetryCount(retryCount); + client.setRetryCount(retryCount); } + public void setClientHeaders(ArrayList
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); + } +} -} \ No newline at end of file diff --git a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/ResourceAddress.java b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/ResourceAddress.java index 4bc3a5160..c03715b99 100644 --- a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/ResourceAddress.java +++ b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/ResourceAddress.java @@ -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 { } } - } \ No newline at end of file diff --git a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/ResourceRequest.java b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/ResourceRequest.java deleted file mode 100644 index 69a3231af..000000000 --- a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/ResourceRequest.java +++ /dev/null @@ -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 { - private T payload; - private int httpStatus = -1; - private String location; - private List successfulStatuses = new ArrayList(); - private List errorStatuses = new ArrayList(); - - public ResourceRequest(T payload, int httpStatus, List successfulStatuses, List 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; - } -} \ No newline at end of file diff --git a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/ByteUtils.java b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/ByteUtils.java new file mode 100644 index 000000000..7084b5123 --- /dev/null +++ b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/ByteUtils.java @@ -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 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 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(); + } +} diff --git a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/Client.java b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/Client.java new file mode 100644 index 000000000..07e20ac91 --- /dev/null +++ b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/Client.java @@ -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 ResourceRequest issueOptionsRequest(URI optionsUri, + String resourceFormat, + String message, + long timeout) throws IOException { + Request.Builder request = new Request.Builder() + .method("OPTIONS", null) + .url(optionsUri.toURL()); + + return executeFhirRequest(request, resourceFormat, new Headers.Builder().build(), message, retryCount, timeout); + } + + public ResourceRequest 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 ResourceRequest issuePutRequest(URI resourceUri, + byte[] payload, + String resourceFormat, + String message, + long timeout) throws IOException { + return issuePutRequest(resourceUri, payload, resourceFormat, new Headers.Builder().build(), message, timeout); + } + + public ResourceRequest 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 ResourceRequest issuePostRequest(URI resourceUri, + byte[] payload, + String resourceFormat, + String message, + long timeout) throws IOException { + return issuePostRequest(resourceUri, payload, resourceFormat, new Headers.Builder().build(), message, timeout); + } + + public ResourceRequest 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 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 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 ResourceRequest 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(); + } +} diff --git a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/ClientHeaders.java b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/ClientHeaders.java new file mode 100644 index 000000000..243270e65 --- /dev/null +++ b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/ClientHeaders.java @@ -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
headers; + + public ClientHeaders() { + this.headers = new ArrayList<>(); + } + + public ClientHeaders(ArrayList
headers) { + this.headers = headers; + } + + public ArrayList
headers() { + return headers; + } + + /** + * Add a header to the list of stored headers for network operations. + * + * @param header {@link Header} to add to the list. + * @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
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
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}")); + } +} diff --git a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/FhirRequestBuilder.java b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/FhirRequestBuilder.java new file mode 100644 index 000000000..0537c3211 --- /dev/null +++ b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/FhirRequestBuilder.java @@ -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. + *
  • User-Agent : hapi-fhir-tooling-client
  • + *
  • Accept-Charset : {@link FhirRequestBuilder#DEFAULT_CHARSET}
  • + * + * @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> headerMap = headers.toMultimap(); + if (headerMap.containsKey(LOCATION_HEADER)) { + return headerMap.get(LOCATION_HEADER).get(0); + } else if (headerMap.containsKey(CONTENT_LOCATION_HEADER)) { + return headerMap.get(CONTENT_LOCATION_HEADER).get(0); + } else { + return null; + } + } + + /** + * We only ever want to have one copy of the HttpClient kicking around at any given time. If we need to make changes + * to any configuration, such as proxy settings, timeout, caches, etc, we can do a per-call configuration through + * the {@link OkHttpClient#newBuilder()} method. That will return a builder that shares the same connection pool, + * dispatcher, and configuration with the original client. + *

    + * The {@link OkHttpClient} uses the proxy auth properties set in the current system properties. The reason we don't + * set the proxy address and authentication explicitly, is due to the fact that this class is often used in conjunction + * with other http client tools which rely on the system.properties settings to determine proxy settings. It's easier + * to keep the method consistent across the board. ...for now. + * + * @return {@link OkHttpClient} instance + */ + protected OkHttpClient getHttpClient() { + if (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 ResourceRequest execute() throws IOException { + formatHeaders(httpRequest, resourceFormat, null); + Response response = getHttpClient().newCall(httpRequest.build()).execute(); + T resource = unmarshalReference(response, resourceFormat); + return new ResourceRequest(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 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. + *

    + * 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 headerList = new ArrayList<>(Collections.emptyList()); + Map> 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?"); +// } + } +} diff --git a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/ResourceRequest.java b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/ResourceRequest.java new file mode 100644 index 000000000..27e909f0a --- /dev/null +++ b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/ResourceRequest.java @@ -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 { + 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; + } +} \ No newline at end of file diff --git a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/RetryInterceptor.java b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/RetryInterceptor.java new file mode 100644 index 000000000..70c40f0f0 --- /dev/null +++ b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/RetryInterceptor.java @@ -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); + } + +} \ No newline at end of file diff --git a/org.hl7.fhir.r4/pom.xml b/org.hl7.fhir.r4/pom.xml index fd74b503a..1468a6a5a 100644 --- a/org.hl7.fhir.r4/pom.xml +++ b/org.hl7.fhir.r4/pom.xml @@ -58,6 +58,19 @@ true + + + org.apache.httpcomponents + httpclient + true + + + com.squareup.okhttp3 + okhttp + 4.9.0 + true + + org.apache.poi diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/ClientUtils.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/ClientUtils.java deleted file mode 100644 index c9d9ac2ae..000000000 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/ClientUtils.java +++ /dev/null @@ -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 ResourceRequest issueOptionsRequest(URI optionsUri, String resourceFormat, String message, int timeout) { - HttpOptions options = new HttpOptions(optionsUri); - return issueResourceRequest(resourceFormat, options, message, timeout); - } - - public ResourceRequest issueGetResourceRequest(URI resourceUri, String resourceFormat, String message, int timeout) { - HttpGet httpget = new HttpGet(resourceUri); - return issueResourceRequest(resourceFormat, httpget, message, timeout); - } - - public ResourceRequest issuePutRequest(URI resourceUri, byte[] payload, String resourceFormat, List

    headers, String message, int timeout) { - HttpPut httpPut = new HttpPut(resourceUri); - return issueResourceRequest(resourceFormat, httpPut, payload, headers, message, timeout); - } - - public ResourceRequest 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 ResourceRequest issuePostRequest(URI resourceUri, byte[] payload, String resourceFormat, List
    headers, String message, int timeout) { - HttpPost httpPost = new HttpPost(resourceUri); - return issueResourceRequest(resourceFormat, httpPost, payload, headers, message, timeout); - } - - - public ResourceRequest 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 ResourceRequest issueResourceRequest(String resourceFormat, HttpUriRequest request, String message, int timeout) { - return issueResourceRequest(resourceFormat, request, null, message, timeout); - } - - /** - * @param resourceFormat - * @param options - * @return - */ - protected ResourceRequest 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 ResourceRequest issueResourceRequest(String resourceFormat, HttpUriRequest request, byte[] payload, List
    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(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
    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 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 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 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 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 headers = new ArrayList<>(); - for (Header h : request.getAllHeaders()) { - headers.add(h.toString()); - } - logger.logRequest(request.getMethod(), request.getURI().toString(), headers, null); - } - } - private void log(HttpEntityEnclosingRequestBase request) { - if (logger != null) { - List headers = new ArrayList<>(); - for (Header h : request.getAllHeaders()) { - headers.add(h.toString()); - } - byte[] cnt = null; - InputStream s; - try { - s = request.getEntity().getContent(); - cnt = IOUtils.toByteArray(s); - s.close(); - } catch (Exception e) { - } - logger.logRequest(request.getMethod(), request.getURI().toString(), headers, cnt); - } - } - - private byte[] log(HttpResponse response) { - byte[] cnt = null; - try { - InputStream s = response.getEntity().getContent(); - cnt = IOUtils.toByteArray(s); - s.close(); - } catch (Exception e) { - } - if (logger != null) { - List headers = new ArrayList<>(); - for (Header h : response.getAllHeaders()) { - headers.add(h.toString()); - } - logger.logResponse(response.getStatusLine().toString(), headers, cnt); - } - 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; - } - - -} \ No newline at end of file diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/EFhirClientException.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/EFhirClientException.java index f01dddb12..42fead47f 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/EFhirClientException.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/EFhirClientException.java @@ -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) { diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/FHIRToolingClient.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/FHIRToolingClient.java index b65924983..eff092b62 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/FHIRToolingClient.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/FHIRToolingClient.java @@ -1,768 +1,430 @@ 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.net.URI; -import java.net.URISyntaxException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.apache.http.Header; -import org.apache.http.HttpHost; -import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.CapabilityStatement; -import org.hl7.fhir.r4.model.CodeSystem; -import org.hl7.fhir.r4.model.Coding; -import org.hl7.fhir.r4.model.ConceptMap; -import org.hl7.fhir.r4.model.OperationOutcome; -import org.hl7.fhir.r4.model.Parameters; +import okhttp3.Headers; +import okhttp3.internal.http2.Header; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent; -import org.hl7.fhir.r4.model.PrimitiveType; -import org.hl7.fhir.r4.model.Resource; -import org.hl7.fhir.r4.model.StringType; -import org.hl7.fhir.r4.model.TerminologyCapabilities; -import org.hl7.fhir.r4.model.ValueSet; +import org.hl7.fhir.r4.utils.client.network.ByteUtils; +import org.hl7.fhir.r4.utils.client.network.Client; +import org.hl7.fhir.r4.utils.client.network.ResourceRequest; import org.hl7.fhir.utilities.ToolingClientLogger; import org.hl7.fhir.utilities.Utilities; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; + /** - * Very Simple RESTful client. This is purely for use in the standalone + * Very Simple RESTful client. This is purely for use in the standalone * tools jar packages. It doesn't support many features, only what the tools * need. - * + *

    * To use, initialize class and set base service URI as follows: - * + * *

    
      * FHIRSimpleClient fhirClient = new FHIRSimpleClient();
      * fhirClient.initialize("http://my.fhir.domain/myServiceRoot");
      * 
    - * + *

    * Default Accept and Content-Type headers are application/fhir+xml and application/fhir+json. - * + *

    * These can be changed by invoking the following setter functions: - * + * *

    
      * setPreferredResourceFormat()
      * setPreferredFeedFormat()
      * 
    - * - * TODO Review all sad paths. - * - * @author Claude Nanjo + *

    + * TODO Review all sad paths. * + * @author Claude Nanjo */ public class FHIRToolingClient { - - public static final String DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssK"; - public static final String DATE_FORMAT = "yyyy-MM-dd"; - public static final String hostKey = "http.proxyHost"; - public static final String portKey = "http.proxyPort"; - private static final int TIMEOUT_NORMAL = 15; - private static final int TIMEOUT_OPERATION = 30; - private static final int TIMEOUT_OPERATION_LONG = 60; - private static final int TIMEOUT_OPERATION_EXPAND = 120; - - private String base; - private ResourceAddress resourceAddress; - private ResourceFormat preferredResourceFormat; - private int maxResultSetSize = -1;//_count - private CapabilityStatement capabilities; - private ClientUtils utils = new ClientUtils(); - - //Pass enpoint for client - URI + public static final String DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssK"; + public static final String DATE_FORMAT = "yyyy-MM-dd"; + public static final String hostKey = "http.proxyHost"; + public static final String portKey = "http.proxyPort"; + + private static final int TIMEOUT_NORMAL = 1500; + private static final int TIMEOUT_OPERATION = 30000; + private static final int TIMEOUT_ENTRY = 500; + private static final int TIMEOUT_OPERATION_LONG = 60000; + private static final int TIMEOUT_OPERATION_EXPAND = 120000; + + private String base; + private ResourceAddress resourceAddress; + private ResourceFormat preferredResourceFormat; + private int maxResultSetSize = -1;//_count + private CapabilityStatement capabilities; + private Client client = new Client(); + private ArrayList

    headers = new ArrayList<>(); + private String username; + private String password; + + //Pass endpoint for client - URI public FHIRToolingClient(String baseServiceUrl) throws URISyntaxException { preferredResourceFormat = ResourceFormat.RESOURCE_XML; - detectProxy(); initialize(baseServiceUrl); } - - public FHIRToolingClient(String baseServiceUrl, String username, String password) throws URISyntaxException { - preferredResourceFormat = ResourceFormat.RESOURCE_XML; - utils.setUsername(username); - utils.setPassword(password); - detectProxy(); - initialize(baseServiceUrl); - } - - public void configureProxy(String proxyHost, int proxyPort) { - utils.setProxy(new HttpHost(proxyHost, proxyPort)); - } - - public void detectProxy() { - String host = System.getenv(hostKey); - String port = System.getenv(portKey); - if(host==null) { - host = System.getProperty(hostKey); - } - - if(port==null) { - port = System.getProperty(portKey); - } - - if(host!=null && port!=null) { - this.configureProxy(host, Integer.parseInt(port)); - } - } - - public void initialize(String baseServiceUrl) throws URISyntaxException { - base = baseServiceUrl; - resourceAddress = new ResourceAddress(baseServiceUrl); - this.maxResultSetSize = -1; - checkCapabilities(); - } - - private void checkCapabilities() { - try { + public void initialize(String baseServiceUrl) throws URISyntaxException { + base = baseServiceUrl; + resourceAddress = new ResourceAddress(baseServiceUrl); + this.maxResultSetSize = -1; + checkCapabilities(); + } + + public Client getClient() { + return client; + } + + public void setClient(Client client) { + this.client = client; + } + + private void checkCapabilities() { + try { capabilities = getCapabilitiesStatementQuick(); - } catch (Throwable e) { - } - } + } catch (Throwable e) { + } + } public String getPreferredResourceFormat() { return preferredResourceFormat.getHeader(); } - - public void setPreferredResourceFormat(ResourceFormat resourceFormat) { - preferredResourceFormat = resourceFormat; - } - - public int getMaximumRecordCount() { - return maxResultSetSize; - } - - public void setMaximumRecordCount(int maxResultSetSize) { - this.maxResultSetSize = maxResultSetSize; - } - - public TerminologyCapabilities getTerminologyCapabilities() { - return (TerminologyCapabilities) utils.issueGetResourceRequest(resourceAddress.resolveMetadataTxCaps(), getPreferredResourceFormat(), "TerminologyCapabilities", TIMEOUT_NORMAL).getReference(); - } - - public CapabilityStatement getCapabilitiesStatement() { - CapabilityStatement conformance = null; - try { - conformance = (CapabilityStatement)utils.issueGetResourceRequest(resourceAddress.resolveMetadataUri(false), getPreferredResourceFormat(), "CapabilitiesStatement", TIMEOUT_NORMAL).getReference(); - } catch(Exception e) { - handleException("An error has occurred while trying to fetch the server's conformance statement", e); - } - return conformance; - } - - public CapabilityStatement getCapabilitiesStatementQuick() throws EFhirClientException { - if (capabilities != null) - return capabilities; + + public void setPreferredResourceFormat(ResourceFormat resourceFormat) { + preferredResourceFormat = resourceFormat; + } + + public int getMaximumRecordCount() { + return maxResultSetSize; + } + + public void setMaximumRecordCount(int maxResultSetSize) { + this.maxResultSetSize = maxResultSetSize; + } + + public TerminologyCapabilities getTerminologyCapabilities() { + TerminologyCapabilities capabilities = null; try { - capabilities = (CapabilityStatement)utils.issueGetResourceRequest(resourceAddress.resolveMetadataUri(true), getPreferredResourceFormat(), "CapabilitiesStatement-Quick", TIMEOUT_NORMAL).getReference(); - } catch(Exception e) { - handleException("An error has occurred while trying to fetch the server's conformance statement", e); + capabilities = (TerminologyCapabilities) client.issueGetResourceRequest(resourceAddress.resolveMetadataTxCaps(), + getPreferredResourceFormat(), + generateHeaders(), + "TerminologyCapabilities", + TIMEOUT_NORMAL).getReference(); + } catch (Exception e) { + throw new FHIRException("Error fetching the server's terminology capabilities", e); } return capabilities; } - - public T read(Class resourceClass, String id) {//TODO Change this to AddressableResource - ResourceRequest result = null; - try { - result = utils.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id), getPreferredResourceFormat(), "Read "+resourceClass.getName()+"/"+id, TIMEOUT_NORMAL); - result.addErrorStatus(410);//gone - result.addErrorStatus(404);//unknown - result.addSuccessStatus(200);//Only one for now - if(result.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); - } - } catch (Exception e) { - handleException("An error has occurred while trying to read this resource", e); - } - return result.getPayload(); - } - public T vread(Class resourceClass, String id, String version) { - ResourceRequest result = null; - try { - result = utils.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndIdAndVersion(resourceClass, id, version), getPreferredResourceFormat(), "VRead "+resourceClass.getName()+"/"+id+"/?_history/"+version, TIMEOUT_NORMAL); - result.addErrorStatus(410);//gone - result.addErrorStatus(404);//unknown - result.addErrorStatus(405);//unknown - result.addSuccessStatus(200);//Only one for now - if(result.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); - } - } catch (Exception e) { - handleException("An error has occurred while trying to read this version of the resource", e); - } - return result.getPayload(); - } - - // GET fhir/ValueSet?url=http://hl7.org/fhir/ValueSet/clinical-findings&version=0.8 + public CapabilityStatement getCapabilitiesStatement() { + CapabilityStatement conformance = null; + try { + conformance = (CapabilityStatement) client.issueGetResourceRequest(resourceAddress.resolveMetadataUri(false), + getPreferredResourceFormat(), + generateHeaders(), + "CapabilitiesStatement", + TIMEOUT_NORMAL).getReference(); + } catch (Exception e) { + throw new FHIRException("Error fetching the server's conformance statement", e); + } + return conformance; + } + + public CapabilityStatement getCapabilitiesStatementQuick() throws EFhirClientException { + if (capabilities != null) return capabilities; + try { + capabilities = (CapabilityStatement) client.issueGetResourceRequest(resourceAddress.resolveMetadataUri(true), + getPreferredResourceFormat(), + generateHeaders(), + "CapabilitiesStatement-Quick", + TIMEOUT_NORMAL).getReference(); + } catch (Exception e) { + throw new FHIRException("Error fetching the server's capability statement: "+e.getMessage(), e); + } + return capabilities; + } + + public T read(Class resourceClass, String id) {//TODO Change this to AddressableResource + ResourceRequest result = null; + try { + result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id), + getPreferredResourceFormat(), + generateHeaders(), + "Read " + resourceClass.getName() + "/" + id, + TIMEOUT_NORMAL); + if (result.isUnsuccessfulRequest()) { + throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); + } + } catch (Exception e) { + throw new FHIRException(e); + } + return result.getPayload(); + } + + public T vread(Class resourceClass, String id, String version) { + ResourceRequest result = null; + try { + result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndIdAndVersion(resourceClass, id, version), + 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()); + } + } catch (Exception e) { + throw new FHIRException("Error trying to read this version of the resource", e); + } + return result.getPayload(); + } public T getCanonical(Class resourceClass, String canonicalURL) { ResourceRequest result = null; try { - result = utils.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndCanonical(resourceClass, canonicalURL), getPreferredResourceFormat(), "Read "+resourceClass.getName()+"?url="+canonicalURL, TIMEOUT_NORMAL); - result.addErrorStatus(410);//gone - result.addErrorStatus(404);//unknown - result.addErrorStatus(405);//unknown - result.addSuccessStatus(200);//Only one for now - if(result.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); + result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndCanonical(resourceClass, canonicalURL), + getPreferredResourceFormat(), + generateHeaders(), + "Read " + resourceClass.getName() + "?url=" + canonicalURL, + TIMEOUT_NORMAL); + if (result.isUnsuccessfulRequest()) { + throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); } } catch (Exception e) { handleException("An error has occurred while trying to read this version of the resource", e); } Bundle bnd = (Bundle) result.getPayload(); if (bnd.getEntry().size() == 0) - throw new EFhirClientException("No matching resource found for canonical URL '"+canonicalURL+"'"); + throw new EFhirClientException("No matching resource found for canonical URL '" + canonicalURL + "'"); if (bnd.getEntry().size() > 1) - throw new EFhirClientException("Multiple matching resources found for canonical URL '"+canonicalURL+"'"); + throw new EFhirClientException("Multiple matching resources found for canonical URL '" + canonicalURL + "'"); return (T) bnd.getEntry().get(0).getResource(); } - - + public Resource update(Resource resource) { - ResourceRequest result = null; + org.hl7.fhir.r4.utils.client.network.ResourceRequest result = null; try { - List
    headers = null; - result = utils.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resource.getClass(), resource.getId()),utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, - "Update "+resource.fhirType()+"/"+resource.getId(), TIMEOUT_OPERATION); - result.addErrorStatus(410);//gone - result.addErrorStatus(404);//unknown - result.addErrorStatus(405); - result.addErrorStatus(422);//Unprocessable Entity - result.addSuccessStatus(200); - result.addSuccessStatus(201); - if(result.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); + result = client.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resource.getClass(), resource.getId()), + 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()); } - } catch(Exception e) { + } catch (Exception e) { throw new EFhirClientException("An error has occurred while trying to update this resource", e); } // TODO oe 26.1.2015 could be made nicer if only OperationOutcome locationheader is returned with an operationOutcome would be returned (and not the resource also) we make another read try { - OperationOutcome operationOutcome = (OperationOutcome)result.getPayload(); + OperationOutcome operationOutcome = (OperationOutcome) result.getPayload(); ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = ResourceAddress.parseCreateLocation(result.getLocation()); - return this.vread(resource.getClass(), resVersionedIdentifier.getId(),resVersionedIdentifier.getVersionId()); - } catch(ClassCastException e) { + return this.vread(resource.getClass(), resVersionedIdentifier.getId(), resVersionedIdentifier.getVersionId()); + } catch (ClassCastException e) { // if we fall throught we have the correct type already in the create } return result.getPayload(); } - public T update(Class resourceClass, T resource, String id) { - ResourceRequest result = null; - try { - List
    headers = null; - result = utils.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, - "Update "+resource.fhirType()+"/"+id, TIMEOUT_OPERATION); - result.addErrorStatus(410);//gone - result.addErrorStatus(404);//unknown - result.addErrorStatus(405); - result.addErrorStatus(422);//Unprocessable Entity - result.addSuccessStatus(200); - result.addSuccessStatus(201); - if(result.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); - } - } catch(Exception e) { - throw new EFhirClientException("An error has occurred while trying to update this resource", e); - } - // TODO oe 26.1.2015 could be made nicer if only OperationOutcome locationheader is returned with an operationOutcome would be returned (and not the resource also) we make another read - try { - OperationOutcome operationOutcome = (OperationOutcome)result.getPayload(); - ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = ResourceAddress.parseCreateLocation(result.getLocation()); - return this.vread(resourceClass, resVersionedIdentifier.getId(),resVersionedIdentifier.getVersionId()); - } catch(ClassCastException e) { - // if we fall throught we have the correct type already in the create - } + public T update(Class resourceClass, T resource, String id) { + ResourceRequest result = null; + try { + result = client.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id), + ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat())), + getPreferredResourceFormat(), + generateHeaders(), + "Update " + resource.fhirType() + "/" + id, + TIMEOUT_OPERATION); + if (result.isUnsuccessfulRequest()) { + throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); + } + } catch (Exception e) { + throw new EFhirClientException("An error has occurred while trying to update this resource", e); + } + // TODO oe 26.1.2015 could be made nicer if only OperationOutcome locationheader is returned with an operationOutcome would be returned (and not the resource also) we make another read + try { + OperationOutcome operationOutcome = (OperationOutcome) result.getPayload(); + ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = ResourceAddress.parseCreateLocation(result.getLocation()); + return this.vread(resourceClass, resVersionedIdentifier.getId(), resVersionedIdentifier.getVersionId()); + } catch (ClassCastException e) { + // if we fall through we have the correct type already in the create + } - return result.getPayload(); - } + return result.getPayload(); + } -// -// public boolean delete(Class resourceClass, String id) { -// try { -// return utils.issueDeleteRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id), proxy); -// } catch(Exception e) { -// throw new EFhirClientException("An error has occurred while trying to delete this resource", e); -// } -// -// } - -// -// public OperationOutcome create(Class resourceClass, T resource) { -// ResourceRequest resourceRequest = null; -// try { -// List
    headers = null; -// resourceRequest = utils.issuePostRequest(resourceAddress.resolveGetUriFromResourceClass(resourceClass),utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, proxy); -// resourceRequest.addSuccessStatus(201); -// if(resourceRequest.isUnsuccessfulRequest()) { -// throw new EFhirClientException("Server responded with HTTP error code " + resourceRequest.getHttpStatus(), (OperationOutcome)resourceRequest.getPayload()); -// } -// } catch(Exception e) { -// handleException("An error has occurred while trying to create this resource", e); -// } -// OperationOutcome operationOutcome = null;; -// try { -// operationOutcome = (OperationOutcome)resourceRequest.getPayload(); -// ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = -// ResourceAddress.parseCreateLocation(resourceRequest.getLocation()); -// OperationOutcomeIssueComponent issue = operationOutcome.addIssue(); -// issue.setSeverity(IssueSeverity.INFORMATION); -// issue.setUserData(ResourceAddress.ResourceVersionedIdentifier.class.toString(), -// resVersionedIdentifier); -// return operationOutcome; -// } catch(ClassCastException e) { -// // some server (e.g. grahams) returns the resource directly -// operationOutcome = new OperationOutcome(); -// OperationOutcomeIssueComponent issue = operationOutcome.addIssue(); -// issue.setSeverity(IssueSeverity.INFORMATION); -// issue.setUserData(ResourceRequest.class.toString(), -// resourceRequest.getPayload()); -// return operationOutcome; -// } -// } - -// -// public Bundle history(Calendar lastUpdate, Class resourceClass, String id) { -// Bundle history = null; -// try { -// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceId(resourceClass, id, lastUpdate, maxResultSetSize), getPreferredResourceFormat(), proxy); -// } catch (Exception e) { -// handleException("An error has occurred while trying to retrieve history information for this resource", e); -// } -// return history; -// } - -// -// public Bundle history(Date lastUpdate, Class resourceClass, String id) { -// Bundle history = null; -// try { -// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceId(resourceClass, id, lastUpdate, maxResultSetSize), getPreferredResourceFormat(), proxy); -// } catch (Exception e) { -// handleException("An error has occurred while trying to retrieve history information for this resource", e); -// } -// return history; -// } -// -// -// public Bundle history(Calendar lastUpdate, Class resourceClass) { -// Bundle history = null; -// try { -// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceType(resourceClass, lastUpdate, maxResultSetSize), getPreferredResourceFormat(), proxy); -// } catch (Exception e) { -// handleException("An error has occurred while trying to retrieve history information for this resource type", e); -// } -// return history; -// } -// -// -// public Bundle history(Date lastUpdate, Class resourceClass) { -// Bundle history = null; -// try { -// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceType(resourceClass, lastUpdate, maxResultSetSize), getPreferredResourceFormat(), proxy); -// } catch (Exception e) { -// handleException("An error has occurred while trying to retrieve history information for this resource type", e); -// } -// return history; -// } -// -// -// public Bundle history(Class resourceClass) { -// Bundle history = null; -// try { -// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceType(resourceClass, maxResultSetSize), getPreferredResourceFormat(), proxy); -// } catch (Exception e) { -// handleException("An error has occurred while trying to retrieve history information for this resource type", e); -// } -// return history; -// } -// -// -// public Bundle history(Class resourceClass, String id) { -// Bundle history = null; -// try { -// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceId(resourceClass, id, maxResultSetSize), getPreferredResourceFormat(), proxy); -// } catch (Exception e) { -// handleException("An error has occurred while trying to retrieve history information for this resource", e); -// } -// return history; -// } -// -// -// public Bundle history(Date lastUpdate) { -// Bundle history = null; -// try { -// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForAllResources(lastUpdate, maxResultSetSize), getPreferredResourceFormat(), proxy); -// } catch (Exception e) { -// handleException("An error has occurred while trying to retrieve history since last update",e); -// } -// return history; -// } -// -// -// public Bundle history(Calendar lastUpdate) { -// Bundle history = null; -// try { -// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForAllResources(lastUpdate, maxResultSetSize), getPreferredResourceFormat(), proxy); -// } catch (Exception e) { -// handleException("An error has occurred while trying to retrieve history since last update",e); -// } -// return history; -// } -// -// -// public Bundle history() { -// Bundle history = null; -// try { -// history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForAllResources(maxResultSetSize), getPreferredResourceFormat(), proxy); -// } catch (Exception e) { -// handleException("An error has occurred while trying to retrieve history since last update",e); -// } -// return history; -// } -// -// -// public Bundle search(Class resourceClass, Map parameters) { -// Bundle searchResults = null; -// try { -// searchResults = utils.issueGetFeedRequest(resourceAddress.resolveSearchUri(resourceClass, parameters), getPreferredResourceFormat(), proxy); -// } catch (Exception e) { -// handleException("Error performing search with parameters " + parameters, e); -// } -// return searchResults; -// } -// -// -// public Bundle searchPost(Class resourceClass, T resource, Map parameters) { -// Bundle searchResults = null; -// try { -// searchResults = utils.issuePostFeedRequest(resourceAddress.resolveSearchUri(resourceClass, new HashMap()), parameters, "src", resource, getPreferredResourceFormat()); -// } catch (Exception e) { -// handleException("Error performing search with parameters " + parameters, e); -// } -// return searchResults; -// } - - public Parameters operateType(Class resourceClass, String name, Parameters params) { - boolean complex = false; - for (ParametersParameterComponent p : params.getParameter()) - complex = complex || !(p.getValue() instanceof PrimitiveType); - Parameters searchResults = null; - String ps = ""; - try { + boolean complex = false; + for (ParametersParameterComponent p : params.getParameter()) + complex = complex || !(p.getValue() instanceof PrimitiveType); + String ps = ""; + try { if (!complex) - for (ParametersParameterComponent p : params.getParameter()) - if (p.getValue() instanceof PrimitiveType) - ps += p.getName() + "=" + Utilities.encodeUri(((PrimitiveType) p.getValue()).asStringValue())+"&"; - ResourceRequest result; - if (complex) - result = utils.issuePostRequest(resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps), utils.getResourceAsByteArray(params, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), - "POST "+resourceClass.getName()+"/$"+name, TIMEOUT_OPERATION_LONG); - else - result = utils.issueGetResourceRequest(resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps), getPreferredResourceFormat(), "GET "+resourceClass.getName()+"/$"+name, TIMEOUT_OPERATION_LONG); - result.addErrorStatus(410);//gone - result.addErrorStatus(404);//unknown - result.addSuccessStatus(200);//Only one for now - if(result.isUnsuccessfulRequest()) - throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); - if (result.getPayload() instanceof Parameters) - return (Parameters) result.getPayload(); - else { - Parameters p_out = new Parameters(); - p_out.addParameter().setName("return").setResource(result.getPayload()); - return p_out; - } - } catch (Exception e) { - handleException("Error performing operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e); - } - return null; + for (ParametersParameterComponent p : params.getParameter()) + if (p.getValue() instanceof PrimitiveType) + ps += p.getName() + "=" + Utilities.encodeUri(((PrimitiveType) p.getValue()).asStringValue()) + "&"; + ResourceRequest result; + URI url = resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps); + if (complex) { + byte[] body = ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat())); + client.getLogger().logRequest("POST", url.toString(), null, body); + result = client.issuePostRequest(url, body, getPreferredResourceFormat(), + "POST " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG); + } else { + client.getLogger().logRequest("GET", url.toString(), null, null); + 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()); + } + if (result.getPayload() instanceof Parameters) { + return (Parameters) result.getPayload(); + } else { + Parameters p_out = new Parameters(); + p_out.addParameter().setName("return").setResource(result.getPayload()); + return p_out; + } + } catch (Exception e) { + handleException("Error performing operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e); + } + return null; } - - public Bundle transaction(Bundle batch) { - Bundle transactionResult = null; - try { - transactionResult = utils.postBatchRequest(resourceAddress.getBaseServiceUri(), utils.getFeedAsByteArray(batch, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), "transaction", TIMEOUT_NORMAL+batch.getEntry().size()); - } catch (Exception e) { - handleException("An error occurred trying to process this transaction request", e); - } - return transactionResult; - } - - @SuppressWarnings("unchecked") - public OperationOutcome validate(Class resourceClass, T resource, String id) { - ResourceRequest result = null; - try { - result = utils.issuePostRequest(resourceAddress.resolveValidateUri(resourceClass, id), utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), "POST "+resourceClass.getName()+(id != null ? "/"+id : "")+"/$validate", TIMEOUT_OPERATION_LONG); - result.addErrorStatus(400);//gone - result.addErrorStatus(422);//Unprocessable Entity - result.addSuccessStatus(200);//OK - if(result.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); - } - } catch(Exception e) { - handleException("An error has occurred while trying to validate this resource", e); - } - return (OperationOutcome)result.getPayload(); - } - - /* change to meta operations - - public List getAllTags() { - TagListRequest result = null; - try { - result = utils.issueGetRequestForTagList(resourceAddress.resolveGetAllTags(), getPreferredResourceFormat(), null, proxy); - } catch (Exception e) { - handleException("An error has occurred while trying to retrieve all tags", e); - } - return result.getPayload(); - } - - - public List getAllTagsForResourceType(Class resourceClass) { - TagListRequest result = null; - try { - result = utils.issueGetRequestForTagList(resourceAddress.resolveGetAllTagsForResourceType(resourceClass), getPreferredResourceFormat(), null, proxy); - } catch (Exception e) { - handleException("An error has occurred while trying to retrieve tags for this resource type", e); - } - return result.getPayload(); - } - - - public List getTagsForReference(Class resource, String id) { - TagListRequest result = null; - try { - result = utils.issueGetRequestForTagList(resourceAddress.resolveGetTagsForReference(resource, id), getPreferredResourceFormat(), null, proxy); - } catch (Exception e) { - handleException("An error has occurred while trying to retrieve tags for this resource", e); - } - return result.getPayload(); - } - - - public List getTagsForResourceVersion(Class resource, String id, String versionId) { - TagListRequest result = null; - try { - result = utils.issueGetRequestForTagList(resourceAddress.resolveGetTagsForResourceVersion(resource, id, versionId), getPreferredResourceFormat(), null, proxy); - } catch (Exception e) { - handleException("An error has occurred while trying to retrieve tags for this resource version", e); - } - return result.getPayload(); - } - -// -// public boolean deleteTagsForReference(Class resourceClass, String id) { -// try { -// return utils.issueDeleteRequest(resourceAddress.resolveGetTagsForReference(resourceClass, id), proxy); -// } catch(Exception e) { -// handleException("An error has occurred while trying to retrieve tags for this resource version", e); -// throw new EFhirClientException("An error has occurred while trying to delete this resource", e); -// } -// -// } -// -// -// public boolean deleteTagsForResourceVersion(Class resourceClass, String id, List tags, String version) { -// try { -// return utils.issueDeleteRequest(resourceAddress.resolveGetTagsForResourceVersion(resourceClass, id, version), proxy); -// } catch(Exception e) { -// handleException("An error has occurred while trying to retrieve tags for this resource version", e); -// throw new EFhirClientException("An error has occurred while trying to delete this resource", e); -// } -// } - - - public List createTags(List tags, Class resourceClass, String id) { - TagListRequest request = null; - try { - request = utils.issuePostRequestForTagList(resourceAddress.resolveGetTagsForReference(resourceClass, id),utils.getTagListAsByteArray(tags, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), null, proxy); - request.addSuccessStatus(201); - request.addSuccessStatus(200); - if(request.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server responded with HTTP error code " + request.getHttpStatus()); - } - } catch(Exception e) { - handleException("An error has occurred while trying to set tags for this resource", e); - } - return request.getPayload(); - } - - - public List createTags(List tags, Class resourceClass, String id, String version) { - TagListRequest request = null; - try { - request = utils.issuePostRequestForTagList(resourceAddress.resolveGetTagsForResourceVersion(resourceClass, id, version),utils.getTagListAsByteArray(tags, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), null, proxy); - request.addSuccessStatus(201); - request.addSuccessStatus(200); - if(request.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server responded with HTTP error code " + request.getHttpStatus()); - } - } catch(Exception e) { - handleException("An error has occurred while trying to set the tags for this resource version", e); - } - return request.getPayload(); - } - - public List deleteTags(List tags, Class resourceClass, String id, String version) { - TagListRequest request = null; - try { - request = utils.issuePostRequestForTagList(resourceAddress.resolveDeleteTagsForResourceVersion(resourceClass, id, version),utils.getTagListAsByteArray(tags, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), null, proxy); - request.addSuccessStatus(201); - request.addSuccessStatus(200); - if(request.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server responded with HTTP error code " + request.getHttpStatus()); - } - } catch(Exception e) { - handleException("An error has occurred while trying to delete the tags for this resource version", e); - } - return request.getPayload(); - } - */ + public Bundle transaction(Bundle batch) { + Bundle transactionResult = null; + try { + transactionResult = client.postBatchRequest(resourceAddress.getBaseServiceUri(), ByteUtils.resourceToByteArray(batch, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), "transaction", TIMEOUT_OPERATION + (TIMEOUT_ENTRY * batch.getEntry().size())); + } catch (Exception e) { + handleException("An error occurred trying to process this transaction request", e); + } + return transactionResult; + } + + @SuppressWarnings("unchecked") + public OperationOutcome validate(Class resourceClass, T resource, String id) { + ResourceRequest result = null; + try { + 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()); + } + } catch (Exception e) { + handleException("An error has occurred while trying to validate this resource", e); + } + return (OperationOutcome) result.getPayload(); + } + + /** + * Helper method to prevent nesting of previously thrown EFhirClientExceptions + * + * @param e + * @throws EFhirClientException + */ + protected void handleException(String message, Exception e) throws EFhirClientException { + if (e instanceof EFhirClientException) { + throw (EFhirClientException) e; + } else { + throw new EFhirClientException(message, e); + } + } + + /** + * Helper method to determine whether desired resource representation + * is Json or XML. + * + * @param format + * @return + */ + protected boolean isJson(String format) { + boolean isJson = false; + if (format.toLowerCase().contains("json")) { + isJson = true; + } + return isJson; + } - /** - * Helper method to prevent nesting of previously thrown EFhirClientExceptions - * - * @param e - * @throws EFhirClientException - */ - protected void handleException(String message, Exception e) throws EFhirClientException { - if(e instanceof EFhirClientException) { - throw (EFhirClientException)e; - } else { - throw new EFhirClientException(message, e); - } - } - - /** - * Helper method to determine whether desired resource representation - * is Json or XML. - * - * @param format - * @return - */ - protected boolean isJson(String format) { - boolean isJson = false; - if(format.toLowerCase().contains("json")) { - isJson = true; - } - return isJson; - } - public Bundle fetchFeed(String url) { - Bundle feed = null; - try { - feed = utils.issueGetFeedRequest(new URI(url), getPreferredResourceFormat()); - } catch (Exception e) { - handleException("An error has occurred while trying to retrieve history since last update",e); - } - return feed; + Bundle feed = null; + try { + feed = client.issueGetFeedRequest(new URI(url), getPreferredResourceFormat()); + } catch (Exception e) { + handleException("An error has occurred while trying to retrieve history since last update", e); + } + return feed; } - + public ValueSet expandValueset(ValueSet source, Parameters expParams) { - List
    headers = null; Parameters p = expParams == null ? new Parameters() : expParams.copy(); p.addParameter().setName("valueSet").setResource(source); - ResourceRequest result = utils.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand"), - utils.getResourceAsByteArray(p, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, "ValueSet/$expand?url="+source.getUrl(), TIMEOUT_OPERATION_EXPAND); - result.addErrorStatus(410);//gone - result.addErrorStatus(404);//unknown - result.addErrorStatus(405); - result.addErrorStatus(422);//Unprocessable Entity - result.addSuccessStatus(200); - result.addSuccessStatus(201); - if(result.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); + org.hl7.fhir.r4.utils.client.network.ResourceRequest result = null; + try { + result = client.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "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()); + } + } catch (IOException e) { + e.printStackTrace(); } - return (ValueSet) result.getPayload(); + return result == null ? null : (ValueSet) result.getPayload(); } - + public Parameters lookupCode(Map params) { - ResourceRequest result = utils.issueGetResourceRequest(resourceAddress.resolveOperationUri(CodeSystem.class, "lookup", params), getPreferredResourceFormat(), "CodeSystem/$lookup", TIMEOUT_NORMAL); - result.addErrorStatus(410);//gone - result.addErrorStatus(404);//unknown - result.addErrorStatus(405); - result.addErrorStatus(422);//Unprocessable Entity - result.addSuccessStatus(200); - result.addSuccessStatus(201); - if(result.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); + org.hl7.fhir.r4.utils.client.network.ResourceRequest result = null; + try { + result = client.issueGetResourceRequest(resourceAddress.resolveOperationUri(CodeSystem.class, "lookup", params), + getPreferredResourceFormat(), + generateHeaders(), + "CodeSystem/$lookup", + TIMEOUT_NORMAL); + } catch (IOException e) { + e.printStackTrace(); + } + if (result.isUnsuccessfulRequest()) { + throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); } return (Parameters) result.getPayload(); } + public ValueSet expandValueset(ValueSet source, Parameters expParams, Map params) { - List
    headers = null; Parameters p = expParams == null ? new Parameters() : expParams.copy(); p.addParameter().setName("valueSet").setResource(source); - for (String n : params.keySet()) + for (String n : params.keySet()) { p.addParameter().setName(n).setValue(new StringType(params.get(n))); - ResourceRequest result = utils.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand", params), - utils.getResourceAsByteArray(p, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, "ValueSet/$expand?url="+source.getUrl(), TIMEOUT_OPERATION_EXPAND); - result.addErrorStatus(410);//gone - result.addErrorStatus(404);//unknown - result.addErrorStatus(405); - result.addErrorStatus(422);//Unprocessable Entity - result.addSuccessStatus(200); - result.addSuccessStatus(201); - if(result.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); } - return (ValueSet) result.getPayload(); + org.hl7.fhir.r4.utils.client.network.ResourceRequest result = null; + try { + result = client.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand", params), + 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()); + } + } catch (IOException e) { + e.printStackTrace(); + } + return result == null ? null : (ValueSet) result.getPayload(); } - -// public ValueSet expandValueset(ValueSet source, ExpansionProfile profile, Map params) { -// List
    headers = null; -// ResourceRequest result = utils.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand", params), -// utils.getResourceAsByteArray(source, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, proxy); -// result.addErrorStatus(410);//gone -// result.addErrorStatus(404);//unknown -// result.addErrorStatus(405); -// result.addErrorStatus(422);//Unprocessable Entity -// result.addSuccessStatus(200); -// result.addSuccessStatus(201); -// if(result.isUnsuccessfulRequest()) { -// throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); -// } -// return (ValueSet) result.getPayload(); -// } - - + public String getAddress() { return base; } @@ -770,79 +432,109 @@ public class FHIRToolingClient { public ConceptMap initializeClosure(String name) { Parameters params = new Parameters(); params.addParameter().setName("name").setValue(new StringType(name)); - List
    headers = null; - ResourceRequest result = utils.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap()), - utils.getResourceAsByteArray(params, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, "Closure?name="+name, TIMEOUT_NORMAL); - result.addErrorStatus(410);//gone - result.addErrorStatus(404);//unknown - result.addErrorStatus(405); - result.addErrorStatus(422);//Unprocessable Entity - result.addSuccessStatus(200); - result.addSuccessStatus(201); - if(result.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); + ResourceRequest result = null; + try { + result = client.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap()), + 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()); + } + } catch (IOException e) { + e.printStackTrace(); } - return (ConceptMap) result.getPayload(); + return result == null ? null : (ConceptMap) result.getPayload(); } public ConceptMap updateClosure(String name, Coding coding) { Parameters params = new Parameters(); params.addParameter().setName("name").setValue(new StringType(name)); params.addParameter().setName("concept").setValue(coding); - List
    headers = null; - ResourceRequest result = utils.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap()), - utils.getResourceAsByteArray(params, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, "UpdateClosure?name="+name, TIMEOUT_OPERATION); - result.addErrorStatus(410);//gone - result.addErrorStatus(404);//unknown - result.addErrorStatus(405); - result.addErrorStatus(422);//Unprocessable Entity - result.addSuccessStatus(200); - result.addSuccessStatus(201); - if(result.isUnsuccessfulRequest()) { - throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload()); + org.hl7.fhir.r4.utils.client.network.ResourceRequest result = null; + try { + result = client.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap()), + 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()); + } + } catch (IOException e) { + e.printStackTrace(); } - return (ConceptMap) result.getPayload(); - } - - public int getTimeout() { - return utils.getTimeout(); - } - - public void setTimeout(int timeout) { - utils.setTimeout(timeout); + return result == null ? null : (ConceptMap) result.getPayload(); } public String getUsername() { - return utils.getUsername(); + return username; } public void setUsername(String username) { - utils.setUsername(username); + this.username = username; } public String getPassword() { - return utils.getPassword(); + return password; } public void setPassword(String password) { - utils.setPassword(password); + this.password = password; + } + + public long getTimeout() { + return client.getTimeout(); + } + + public void setTimeout(long timeout) { + client.setTimeout(timeout); } public ToolingClientLogger getLogger() { - return utils.getLogger(); + return client.getLogger(); } public void setLogger(ToolingClientLogger logger) { - utils.setLogger(logger); + client.setLogger(logger); } public int getRetryCount() { - return utils.getRetryCount(); + return client.getRetryCount(); } public void setRetryCount(int retryCount) { - utils.setRetryCount(retryCount); + client.setRetryCount(retryCount); } + public void setClientHeaders(ArrayList
    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); + } +} -} \ No newline at end of file diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/ResourceAddress.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/ResourceAddress.java index ad9891c85..86457a1f8 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/ResourceAddress.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/ResourceAddress.java @@ -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; diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/ResourceRequest.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/ResourceRequest.java deleted file mode 100644 index aa0ea688c..000000000 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/ResourceRequest.java +++ /dev/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 { - private T payload; - private int httpStatus = -1; - private String location; - private List successfulStatuses = new ArrayList(); - private List errorStatuses = new ArrayList(); - - public ResourceRequest(T payload, int httpStatus, List successfulStatuses, List 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; - } -} \ No newline at end of file diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/ByteUtils.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/ByteUtils.java new file mode 100644 index 000000000..7ac642402 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/ByteUtils.java @@ -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 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 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(); + } +} diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/Client.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/Client.java new file mode 100644 index 000000000..e8ffc8727 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/Client.java @@ -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 ResourceRequest issueOptionsRequest(URI optionsUri, + String resourceFormat, + String message, + long timeout) throws IOException { + Request.Builder request = new Request.Builder() + .method("OPTIONS", null) + .url(optionsUri.toURL()); + + return executeFhirRequest(request, resourceFormat, new Headers.Builder().build(), message, retryCount, timeout); + } + + public ResourceRequest 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 ResourceRequest issuePutRequest(URI resourceUri, + byte[] payload, + String resourceFormat, + String message, + long timeout) throws IOException { + return issuePutRequest(resourceUri, payload, resourceFormat, new Headers.Builder().build(), message, timeout); + } + + public ResourceRequest 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 ResourceRequest issuePostRequest(URI resourceUri, + byte[] payload, + String resourceFormat, + String message, + long timeout) throws IOException { + return issuePostRequest(resourceUri, payload, resourceFormat, new Headers.Builder().build(), message, timeout); + } + + public ResourceRequest 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 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 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 ResourceRequest 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(); + } +} diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/ClientHeaders.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/ClientHeaders.java new file mode 100644 index 000000000..f54a4977a --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/ClientHeaders.java @@ -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
    headers; + + public ClientHeaders() { + this.headers = new ArrayList<>(); + } + + public ClientHeaders(ArrayList
    headers) { + this.headers = headers; + } + + public ArrayList
    headers() { + return headers; + } + + /** + * Add a header to the list of stored headers for network operations. + * + * @param header {@link Header} to add to the list. + * @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
    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
    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}")); + } +} diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilder.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilder.java new file mode 100644 index 000000000..10235d77a --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilder.java @@ -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. + *
  • User-Agent : hapi-fhir-tooling-client
  • + *
  • Accept-Charset : {@link FhirRequestBuilder#DEFAULT_CHARSET}
  • + * + * @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> headerMap = headers.toMultimap(); + if (headerMap.containsKey(LOCATION_HEADER)) { + return headerMap.get(LOCATION_HEADER).get(0); + } else if (headerMap.containsKey(CONTENT_LOCATION_HEADER)) { + return headerMap.get(CONTENT_LOCATION_HEADER).get(0); + } else { + return null; + } + } + + /** + * We only ever want to have one copy of the HttpClient kicking around at any given time. If we need to make changes + * to any configuration, such as proxy settings, timeout, caches, etc, we can do a per-call configuration through + * the {@link OkHttpClient#newBuilder()} method. That will return a builder that shares the same connection pool, + * dispatcher, and configuration with the original client. + *

    + * The {@link OkHttpClient} uses the proxy auth properties set in the current system properties. The reason we don't + * set the proxy address and authentication explicitly, is due to the fact that this class is often used in conjunction + * with other http client tools which rely on the system.properties settings to determine proxy settings. It's easier + * to keep the method consistent across the board. ...for now. + * + * @return {@link OkHttpClient} instance + */ + protected OkHttpClient getHttpClient() { + if (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 ResourceRequest execute() throws IOException { + formatHeaders(httpRequest, resourceFormat, null); + Response response = getHttpClient().newCall(httpRequest.build()).execute(); + T resource = unmarshalReference(response, resourceFormat); + return new ResourceRequest(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 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. + *

    + * 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 headerList = new ArrayList<>(Collections.emptyList()); + Map> 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?"); +// } + } +} diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/ResourceRequest.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/ResourceRequest.java new file mode 100644 index 000000000..3c0cd69f2 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/ResourceRequest.java @@ -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 { + 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; + } +} \ No newline at end of file diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/RetryInterceptor.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/RetryInterceptor.java new file mode 100644 index 000000000..dd0ec0def --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/RetryInterceptor.java @@ -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); + } + +} \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/TerminologyClient.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/TerminologyClient.java index a0aaaee95..1582d3689 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/TerminologyClient.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/TerminologyClient.java @@ -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 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 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 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 params) throws FHIRException; + Bundle validateBatch(Bundle batch); + CanonicalResource read(String type, String id); + ClientHeaders getClientHeaders(); + TerminologyClient setClientHeaders(ClientHeaders clientHeaders); } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/FHIRToolingClient.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/FHIRToolingClient.java index e20c63b3d..b0cadeb71 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/FHIRToolingClient.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/FHIRToolingClient.java @@ -1,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

    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 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 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 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 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 OperationOutcome validate(Class resourceClass, T resource, String id) { ResourceRequest 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 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 params) { org.hl7.fhir.r5.utils.client.network.ResourceRequest 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 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 result = null; try { result = client.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap()), - 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 result = null; try { result = client.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap()), - 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
    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); + } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/Client.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/Client.java index 6d657aae9..485c9222e 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/Client.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/Client.java @@ -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 ResourceRequest 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 ResourceRequest 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 ResourceRequest 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 ResourceRequest 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 Bundle executeBundleRequest(Request.Builder request, + public Bundle executeBundleRequest(Request.Builder request, String resourceFormat, Headers headers, String message, @@ -197,7 +176,7 @@ public class Client { .executeAsBatch(); } - protected ResourceRequest executeFhirRequest(Request.Builder request, + public ResourceRequest 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); - } - } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/ClientHeaders.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/ClientHeaders.java new file mode 100644 index 000000000..efe62ea30 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/ClientHeaders.java @@ -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
    headers; + + public ClientHeaders() { + this.headers = new ArrayList<>(); + } + + public ClientHeaders(ArrayList
    headers) { + this.headers = headers; + } + + public ArrayList
    headers() { + return headers; + } + + /** + * Add a header to the list of stored headers for network operations. + * + * @param header {@link Header} to add to the list. + * @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
    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
    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}")); + } +} diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/FHIRToolingClientTest.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/FHIRToolingClientTest.java new file mode 100644 index 000000000..1ff215ace --- /dev/null +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/FHIRToolingClientTest.java @@ -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 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
    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 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 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 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 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 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 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 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 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); + } +} \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/network/ClientHeadersTest.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/network/ClientHeadersTest.java new file mode 100644 index 000000000..239c67069 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/network/ClientHeadersTest.java @@ -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
    headersList = Arrays.asList(h1, h2, h3); + clientHeaders.addHeaders(headersList); + Assertions.assertEquals(3, clientHeaders.headers().size()); + Assertions.assertEquals(headersList, clientHeaders.headers()); + } + + @Test + @DisplayName("Happy path add headers as list.") + void addHeadersDuplicateAdd() { + List
    headersList = Arrays.asList(h1, h2, h1); + Assertions.assertThrows(FHIRException.class, () -> clientHeaders.addHeaders(headersList)); + } + + @Test + @DisplayName("Happy path remove existing header.") + void removeHeader() { + clientHeaders.addHeader(h1); + clientHeaders.addHeader(h2); + clientHeaders.addHeader(h3); + clientHeaders.removeHeader(h2); + Assertions.assertEquals(2, clientHeaders.headers().size()); + clientHeaders.removeHeader(new 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
    headersToAddList = Arrays.asList(h1, h2, h3); + List
    headersToRemoveList = Arrays.asList(h2, h3); + clientHeaders.addHeaders(headersToAddList); + clientHeaders.removeHeaders(headersToRemoveList); + Assertions.assertEquals(1, clientHeaders.headers().size()); + } + + @Test + @DisplayName("Remove list containing unknown header.") + void removeHeadersUnknown() { + List
    headersToAddList = Arrays.asList(h1, h3); + List
    headersToRemoveList = Arrays.asList(h2, h3); + clientHeaders.addHeaders(headersToAddList); + Assertions.assertThrows(FHIRException.class, () -> clientHeaders.removeHeaders(headersToRemoveList)); + } + + @Test + void clearHeaders() { + List
    headersToAddList = Arrays.asList(h1, h3); + clientHeaders.addHeaders(headersToAddList); + Assertions.assertEquals(2, clientHeaders.headers().size()); + clientHeaders.clearHeaders(); + Assertions.assertEquals(0, clientHeaders.headers().size()); + } +} \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/network/ClientTest.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/network/ClientTest.java index 01a13a870..67a70457b 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/network/ClientTest.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/client/network/ClientTest.java @@ -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 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 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 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.");