This commit is contained in:
Grahame Grieve 2020-11-26 06:50:35 +11:00
commit 40b644adf1
38 changed files with 30436 additions and 34199 deletions

View File

@ -0,0 +1 @@
* fix for issue 383

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>5.2.1-SNAPSHOT</version>
<version>5.2.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -0,0 +1,27 @@
package org.hl7.fhir.convertors.conv10_30;
import org.hl7.fhir.convertors.VersionConvertorAdvisor30;
import org.hl7.fhir.convertors.VersionConvertor_10_30;
import org.hl7.fhir.convertors.loaders.R2ToR3Loader;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.io.IOException;
import java.io.InputStream;
import java.util.stream.Stream;
public class AdministrativeGender10_30Test {
@Test
@DisplayName("Test 10_30 extension present, value is not")
public void testMedicationRequestConversion() throws IOException {
InputStream dstu2_input = this.getClass().getResourceAsStream("/administrative_gender_null.json");
org.hl7.fhir.dstu2.model.Patient dstu2 = (org.hl7.fhir.dstu2.model.Patient) new org.hl7.fhir.dstu2.formats.JsonParser().parse(dstu2_input);
VersionConvertorAdvisor30 advisor = new R2ToR3Loader();
org.hl7.fhir.dstu3.model.Resource stu_actual = VersionConvertor_10_30.convertResource(dstu2, advisor);
}
}

View File

@ -0,0 +1,24 @@
package org.hl7.fhir.convertors.conv10_30;
import org.hl7.fhir.convertors.VersionConvertor_10_30;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class TimingRepeatComponent10_30Test {
@Test
@DisplayName("Issue #383 - Test 10_30 TimingRepeatComponent with Timing.when as null")
public void testMedicationRequestConversion() {
final int SET_COUNT = 11;
org.hl7.fhir.dstu2.model.Timing.TimingRepeatComponent src = new org.hl7.fhir.dstu2.model.Timing.TimingRepeatComponent();
src.setCount(SET_COUNT);
org.hl7.fhir.dstu3.model.Timing.TimingRepeatComponent tgt = VersionConvertor_10_30.convertTimingRepeatComponent(src);
Assertions.assertEquals(SET_COUNT, tgt.getCount(), "Count field not preserved through version conversion.");
Assertions.assertFalse(tgt.hasWhen(), "hasWhen() should return false for this conversion.");
Assertions.assertTrue(tgt.getWhen().isEmpty(), "When no _when time_ is provided, getWhen() should return an empty list.");
}
}

View File

@ -0,0 +1,94 @@
{
"resourceType": "Patient",
"id": "12743884",
"meta": {
"versionId": "0",
"lastUpdated": "2020-09-15T06:35:01.000Z"
},
"text": {
"status": "generated",
"div": "<div><p><b>Patient</b></p><p><b>Name</b>: Dawg, Joel</p><p><b>DOB</b>: Nov 11, 1991</p><p><b>Status</b>: Active</p></div>"
},
"identifier": [
{
"use": "usual",
"type": {
"coding": [
{
"system": "http://hl7.org/fhir/v2/0203",
"code": "MR",
"display": "Medical record number",
"userSelected": false
}
],
"text": "MRN"
},
"system": "urn:oid:2.16.840.1.113883.6.1000",
"value": "7690",
"_value": {
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/rendered-value",
"valueString": "00000007690"
}
]
},
"period": {
"start": "2020-09-15T06:35:01.000Z"
}
},
{
"use": "usual",
"type": {
"text": "Military Id"
},
"system": "urn:oid:2.16.840.1.113883.3.42.10001.100001.12",
"value": "10050007740",
"_value": {
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/rendered-value",
"valueString": "10050007740"
}
]
},
"period": {
"start": "2020-09-15T06:35:01.000Z"
}
}
],
"active": true,
"name": [
{
"use": "official",
"text": "Dawg, Joel",
"family": [
"Dawg"
],
"given": [
"Joel"
]
}
],
"telecom": [
{
"system": "phone",
"value": "3075557575",
"use": "home"
},
{
"system": "email",
"value": "amitabhp@mindfiresolutions.com",
"use": "work"
}
],
"_gender": {
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/data-absent-reason",
"valueCode": "unknown"
}
]
},
"birthDate": "1991-11-11"
}

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>5.2.1-SNAPSHOT</version>
<version>5.2.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>5.2.1-SNAPSHOT</version>
<version>5.2.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>5.2.1-SNAPSHOT</version>
<version>5.2.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>5.2.1-SNAPSHOT</version>
<version>5.2.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>5.2.1-SNAPSHOT</version>
<version>5.2.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@ -101,6 +101,11 @@
<artifactId>httpclient</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.0</version>
</dependency>
<!-- Test dependencies -->
<dependency>
@ -119,6 +124,13 @@
<version>${validator_test_case_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<version>4.9.0</version>
<scope>test</scope>
</dependency>
<!-- JUnit Jupiter -->
<dependency>

View File

@ -1,684 +0,0 @@
package org.hl7.fhir.r5.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.r5.formats.IParser;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.formats.XmlParser;
import org.hl7.fhir.r5.model.Bundle;
import org.hl7.fhir.r5.model.OperationOutcome;
import org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity;
import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.ResourceType;
import org.hl7.fhir.r5.utils.ResourceUtilities;
import org.hl7.fhir.utilities.ToolingClientLogger;
import org.hl7.fhir.utilities.Utilities;
/**
* Helper class handling lower level HTTP transport concerns.
* TODO Document methods.
* @author Claude Nanjo
*/
public class ClientUtils {
public static final String DEFAULT_CHARSET = "UTF-8";
public static final String HEADER_LOCATION = "location";
private static boolean debugging = false;
public static final int TIMEOUT_SOCKET = 5000;
public static final int TIMEOUT_CONNECT = 1000;
private HttpHost proxy;
private int timeout = TIMEOUT_SOCKET;
private String username;
private String password;
private ToolingClientLogger logger;
private int retryCount;
private HttpClient httpclient;
public HttpHost getProxy() {
return proxy;
}
public void setProxy(HttpHost proxy) {
this.proxy = proxy;
}
public int getTimeout() {
return timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public <T extends Resource> ResourceRequest<T> issueOptionsRequest(URI optionsUri, String resourceFormat, String message, int timeout) {
HttpOptions options = new HttpOptions(optionsUri);
return issueResourceRequest(resourceFormat, options, message, timeout);
}
public <T extends Resource> ResourceRequest<T> issueGetResourceRequest(URI resourceUri, String resourceFormat, String message, int timeout) {
HttpGet httpget = new HttpGet(resourceUri);
return issueResourceRequest(resourceFormat, httpget, message, timeout);
}
public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri, byte[] payload, String resourceFormat, List<Header> headers, String message, int timeout) {
HttpPut httpPut = new HttpPut(resourceUri);
return issueResourceRequest(resourceFormat, httpPut, payload, headers, message, timeout);
}
public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri, byte[] payload, String resourceFormat, String message, int timeout) {
HttpPut httpPut = new HttpPut(resourceUri);
return issueResourceRequest(resourceFormat, httpPut, payload, null, message, timeout);
}
public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri, byte[] payload, String resourceFormat, List<Header> headers, String message, int timeout) {
HttpPost httpPost = new HttpPost(resourceUri);
return issueResourceRequest(resourceFormat, httpPost, payload, headers, message, timeout);
}
public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri, byte[] payload, String resourceFormat, String message, int timeout) {
return issuePostRequest(resourceUri, payload, resourceFormat, null, message, timeout);
}
public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat) {
HttpGet httpget = new HttpGet(resourceUri);
configureFhirRequest(httpget, resourceFormat);
HttpResponse response = sendRequest(httpget);
return unmarshalReference(response, resourceFormat);
}
private void setAuth(HttpRequest httpget) {
if (password != null) {
try {
byte[] b = Base64.encodeBase64((username+":"+password).getBytes("ASCII"));
String b64 = new String(b, StandardCharsets.US_ASCII);
httpget.setHeader("Authorization", "Basic " + b64);
} catch (UnsupportedEncodingException e) {
}
}
}
public Bundle postBatchRequest(URI resourceUri, byte[] payload, String resourceFormat, String message, int timeout) {
HttpPost httpPost = new HttpPost(resourceUri);
configureFhirRequest(httpPost, resourceFormat);
HttpResponse response = sendPayload(httpPost, payload, proxy, message, timeout);
return unmarshalFeed(response, resourceFormat);
}
public boolean issueDeleteRequest(URI resourceUri) {
HttpDelete deleteRequest = new HttpDelete(resourceUri);
HttpResponse response = sendRequest(deleteRequest);
int responseStatusCode = response.getStatusLine().getStatusCode();
boolean deletionSuccessful = false;
if(responseStatusCode == 204) {
deletionSuccessful = true;
}
return deletionSuccessful;
}
/***********************************************************
* Request/Response Helper methods
***********************************************************/
protected <T extends Resource> ResourceRequest<T> issueResourceRequest(String resourceFormat, HttpUriRequest request, String message, int timeout) {
return issueResourceRequest(resourceFormat, request, null, message, timeout);
}
/**
* @param resourceFormat
* @param options
* @return
*/
protected <T extends Resource> ResourceRequest<T> issueResourceRequest(String resourceFormat, HttpUriRequest request, byte[] payload, String message, int timeout) {
return issueResourceRequest(resourceFormat, request, payload, null, message, timeout);
}
/**
* @param resourceFormat
* @param options
* @return
*/
protected <T extends Resource> ResourceRequest<T> issueResourceRequest(String resourceFormat, HttpUriRequest request, byte[] payload, List<Header> headers, String message, int timeout) {
configureFhirRequest(request, resourceFormat, headers);
HttpResponse response = null;
if(request instanceof HttpEntityEnclosingRequest && payload != null) {
response = sendPayload((HttpEntityEnclosingRequestBase)request, payload, proxy, message, timeout);
} else if (request instanceof HttpEntityEnclosingRequest && payload == null){
throw new EFhirClientException("PUT and POST requests require a non-null payload");
} else {
response = sendRequest(request);
}
T resource = unmarshalReference(response, resourceFormat);
return new ResourceRequest<T>(resource, response.getStatusLine().getStatusCode(), getLocationHeader(response));
}
/**
* Method adds required request headers.
* TODO handle JSON request as well.
*
* @param request
*/
protected void configureFhirRequest(HttpRequest request, String format) {
configureFhirRequest(request, format, null);
}
/**
* Method adds required request headers.
* TODO handle JSON request as well.
*
* @param request
*/
protected void configureFhirRequest(HttpRequest request, String format, List<Header> headers) {
request.addHeader("User-Agent", "hapi-fhir-tooling-client");
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 > 4) {
System.out.println("Giving up: "+ioe.getMessage()+" (R5 / "+(System.currentTimeMillis()-t)+"ms / "+Utilities.describeSize(payload.length)+" for "+message+")");
}
throw new EFhirClientException("Error sending HTTP Post/Put Payload: "+ioe.getMessage(), ioe);
}
}
}
return response;
}
@SuppressWarnings("deprecation")
public void makeClient(HttpHost proxy) {
httpclient = new DefaultHttpClient();
HttpParams params = httpclient.getParams();
HttpConnectionParams.setConnectionTimeout(params, TIMEOUT_CONNECT);
HttpConnectionParams.setSoTimeout(params, timeout);
HttpConnectionParams.setSoKeepalive(params, true);
if(proxy != null) {
httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
}
}
/**
*
* @param request
* @param payload
* @return
*/
protected HttpResponse sendRequest(HttpUriRequest request) {
HttpResponse response = null;
try {
if (httpclient == null) {
makeClient(proxy);
}
response = httpclient.execute(request);
} catch(IOException ioe) {
if (ClientUtils.debugging ) {
ioe.printStackTrace();
}
throw new EFhirClientException("Error sending Http Request: "+ioe.getMessage(), ioe);
}
return response;
}
/**
* Unmarshals a resource from the response stream.
*
* @param response
* @return
*/
@SuppressWarnings("unchecked")
protected <T extends Resource> T unmarshalReference(HttpResponse response, String format) {
T resource = null;
OperationOutcome error = null;
byte[] cnt = log(response);
if (cnt != null) {
try {
resource = (T)getParser(format).parse(cnt);
if (resource instanceof OperationOutcome && hasError((OperationOutcome)resource)) {
error = (OperationOutcome) resource;
}
} catch(IOException ioe) {
throw new EFhirClientException("Error reading Http Response: "+ioe.getMessage(), ioe);
} catch(Exception e) {
throw new EFhirClientException("Error parsing response message: "+e.getMessage(), e);
}
}
if(error != null) {
throw new EFhirClientException("Error from server: "+ResourceUtilities.getErrorDescription(error), error);
}
return resource;
}
/**
* Unmarshals Bundle from response stream.
*
* @param response
* @return
*/
protected Bundle unmarshalFeed(HttpResponse response, String format) {
Bundle feed = null;
byte[] cnt = log(response);
String contentType = response.getHeaders("Content-Type")[0].getValue();
OperationOutcome error = null;
try {
if (cnt != null) {
if(contentType.contains(ResourceFormat.RESOURCE_XML.getHeader()) || contentType.contains("text/xml+fhir")) {
Resource rf = getParser(format).parse(cnt);
if (rf instanceof Bundle)
feed = (Bundle) rf;
else if (rf instanceof OperationOutcome && hasError((OperationOutcome) rf)) {
error = (OperationOutcome) rf;
} else {
throw new EFhirClientException("Error reading server response: a resource was returned instead");
}
}
}
} catch(IOException ioe) {
throw new EFhirClientException("Error reading Http Response", ioe);
} catch(Exception e) {
throw new EFhirClientException("Error parsing response message", e);
}
if(error != null) {
throw new EFhirClientException("Error from server: "+ResourceUtilities.getErrorDescription(error), error);
}
return feed;
}
private boolean hasError(OperationOutcome oo) {
for (OperationOutcomeIssueComponent t : oo.getIssue())
if (t.getSeverity() == IssueSeverity.ERROR || t.getSeverity() == IssueSeverity.FATAL)
return true;
return false;
}
protected String getLocationHeader(HttpResponse response) {
String location = null;
if(response.getHeaders("location").length > 0) {//TODO Distinguish between both cases if necessary
location = response.getHeaders("location")[0].getValue();
} else if(response.getHeaders("content-location").length > 0) {
location = response.getHeaders("content-location")[0].getValue();
}
return location;
}
/*****************************************************************
* Client connection methods
* ***************************************************************/
public HttpURLConnection buildConnection(URI baseServiceUri, String tail) {
try {
HttpURLConnection client = (HttpURLConnection) baseServiceUri.resolve(tail).toURL().openConnection();
return client;
} catch(MalformedURLException mue) {
throw new EFhirClientException("Invalid Service URL", mue);
} catch(IOException ioe) {
throw new EFhirClientException("Unable to establish connection to server: " + baseServiceUri.toString() + tail, ioe);
}
}
public HttpURLConnection buildConnection(URI baseServiceUri, ResourceType resourceType, String id) {
return buildConnection(baseServiceUri, ResourceAddress.buildRelativePathFromResourceType(resourceType, id));
}
/******************************************************************
* Other general helper methods
* ****************************************************************/
public <T extends Resource> byte[] getResourceAsByteArray(T resource, boolean pretty, boolean isJson) {
ByteArrayOutputStream baos = null;
byte[] byteArray = null;
try {
baos = new ByteArrayOutputStream();
IParser parser = null;
if(isJson) {
parser = new JsonParser();
} else {
parser = new XmlParser();
}
parser.setOutputStyle(pretty ? OutputStyle.PRETTY : OutputStyle.NORMAL);
parser.compose(baos, resource);
baos.close();
byteArray = baos.toByteArray();
baos.close();
} catch (Exception e) {
try{
baos.close();
}catch(Exception ex) {
throw new EFhirClientException("Error closing output stream", ex);
}
throw new EFhirClientException("Error converting output stream to byte array", e);
}
return byteArray;
}
public byte[] getFeedAsByteArray(Bundle feed, boolean pretty, boolean isJson) {
ByteArrayOutputStream baos = null;
byte[] byteArray = null;
try {
baos = new ByteArrayOutputStream();
IParser parser = null;
if(isJson) {
parser = new JsonParser();
} else {
parser = new XmlParser();
}
parser.setOutputStyle(pretty ? OutputStyle.PRETTY : OutputStyle.NORMAL);
parser.compose(baos, feed);
baos.close();
byteArray = baos.toByteArray();
baos.close();
} catch (Exception e) {
try{
baos.close();
}catch(Exception ex) {
throw new EFhirClientException("Error closing output stream", ex);
}
throw new EFhirClientException("Error converting output stream to byte array", e);
}
return byteArray;
}
public Calendar getLastModifiedResponseHeaderAsCalendarObject(URLConnection serverConnection) {
String dateTime = null;
try {
dateTime = serverConnection.getHeaderField("Last-Modified");
SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", new Locale("en", "US"));
Date lastModifiedTimestamp = format.parse(dateTime);
Calendar calendar=Calendar.getInstance();
calendar.setTime(lastModifiedTimestamp);
return calendar;
} catch(ParseException pe) {
throw new EFhirClientException("Error parsing Last-Modified response header " + dateTime, pe);
}
}
protected IParser getParser(String format) {
if(StringUtils.isBlank(format)) {
format = ResourceFormat.RESOURCE_XML.getHeader();
}
if(format.equalsIgnoreCase("json") || format.equalsIgnoreCase(ResourceFormat.RESOURCE_JSON.getHeader()) || format.equalsIgnoreCase(ResourceFormat.RESOURCE_JSON.getHeader())) {
return new JsonParser();
} else if(format.equalsIgnoreCase("xml") || format.equalsIgnoreCase(ResourceFormat.RESOURCE_XML.getHeader()) || format.equalsIgnoreCase(ResourceFormat.RESOURCE_XML.getHeader())) {
return new XmlParser();
} else {
throw new EFhirClientException("Invalid format: " + format);
}
}
public Bundle issuePostFeedRequest(URI resourceUri, Map<String, String> parameters, String resourceName, Resource resource, String resourceFormat) throws IOException {
HttpPost httppost = new HttpPost(resourceUri);
String boundary = "----WebKitFormBoundarykbMUo6H8QaUnYtRy";
httppost.addHeader("Content-Type", "multipart/form-data; boundary="+boundary);
httppost.addHeader("Accept", resourceFormat);
configureFhirRequest(httppost, null);
HttpResponse response = sendPayload(httppost, encodeFormSubmission(parameters, resourceName, resource, boundary));
return unmarshalFeed(response, resourceFormat);
}
private byte[] encodeFormSubmission(Map<String, String> parameters, String resourceName, Resource resource, String boundary) throws IOException {
ByteArrayOutputStream b = new ByteArrayOutputStream();
OutputStreamWriter w = new OutputStreamWriter(b, "UTF-8");
for (String name : parameters.keySet()) {
w.write("--");
w.write(boundary);
w.write("\r\nContent-Disposition: form-data; name=\""+name+"\"\r\n\r\n");
w.write(parameters.get(name)+"\r\n");
}
w.write("--");
w.write(boundary);
w.write("\r\nContent-Disposition: form-data; name=\""+resourceName+"\"\r\n\r\n");
w.close();
JsonParser json = new JsonParser();
json.setOutputStyle(OutputStyle.NORMAL);
json.compose(b, resource);
b.close();
w = new OutputStreamWriter(b, "UTF-8");
w.write("\r\n--");
w.write(boundary);
w.write("--");
w.close();
return b.toByteArray();
}
/**
* Method posts request payload
*
* @param request
* @param payload
* @return
*/
protected HttpResponse sendPayload(HttpEntityEnclosingRequestBase request, byte[] payload) {
HttpResponse response = null;
try {
log(request);
if (httpclient == null) {
makeClient(proxy);
}
request.setEntity(new ByteArrayEntity(payload));
response = httpclient.execute(request);
log(response);
} catch(IOException ioe) {
throw new EFhirClientException("Error sending HTTP Post/Put Payload: "+ioe.getMessage(), ioe);
}
return response;
}
private void log(HttpUriRequest request) {
if (logger != null) {
List<String> headers = new ArrayList<>();
for (Header h : request.getAllHeaders()) {
headers.add(h.toString());
}
logger.logRequest(request.getMethod(), request.getURI().toString(), headers, null);
}
}
private void log(HttpEntityEnclosingRequestBase request) {
if (logger != null) {
List<String> headers = new ArrayList<>();
for (Header h : request.getAllHeaders()) {
headers.add(h.toString());
}
byte[] cnt = null;
InputStream s;
try {
s = request.getEntity().getContent();
cnt = IOUtils.toByteArray(s);
s.close();
} catch (Exception e) {
}
logger.logRequest(request.getMethod(), request.getURI().toString(), headers, cnt);
}
}
private byte[] log(HttpResponse response) {
byte[] cnt = null;
try {
InputStream s = response.getEntity().getContent();
cnt = IOUtils.toByteArray(s);
s.close();
} catch (Exception e) {
}
if (logger != null) {
List<String> headers = new ArrayList<>();
for (Header h : response.getAllHeaders()) {
headers.add(h.toString());
}
logger.logResponse(response.getStatusLine().toString(), headers, cnt);
}
return cnt;
}
public ToolingClientLogger getLogger() {
return logger;
}
public void setLogger(ToolingClientLogger logger) {
this.logger = logger;
}
/**
* Used for debugging
*
* @param instream
* @return
*/
protected String writeInputStreamAsString(InputStream instream) {
String value = null;
try {
value = IOUtils.toString(instream, "UTF-8");
System.out.println(value);
} catch(IOException ioe) {
//Do nothing
}
return value;
}
public int getRetryCount() {
return retryCount;
}
public void setRetryCount(int retryCount) {
this.retryCount = retryCount;
}
}

View File

@ -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) {

View File

@ -29,55 +29,44 @@ package org.hl7.fhir.r5.utils.client;
*/
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.ResourceRequest;
import org.hl7.fhir.utilities.ToolingClientLogger;
import org.hl7.fhir.utilities.Utilities;
import java.net.MalformedURLException;
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.r5.model.Bundle;
import org.hl7.fhir.r5.model.CapabilityStatement;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.ConceptMap;
import org.hl7.fhir.r5.model.OperationOutcome;
import org.hl7.fhir.r5.model.Parameters;
import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent;
import org.hl7.fhir.r5.model.PrimitiveType;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.model.TerminologyCapabilities;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.utilities.ToolingClientLogger;
import org.hl7.fhir.utilities.Utilities;
/**
* 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.
*
* <p>
* To use, initialize class and set base service URI as follows:
*
* <pre><code>
* FHIRSimpleClient fhirClient = new FHIRSimpleClient();
* fhirClient.initialize("http://my.fhir.domain/myServiceRoot");
* </code></pre>
*
* <p>
* Default Accept and Content-Type headers are application/fhir+xml and application/fhir+json.
*
* <p>
* These can be changed by invoking the following setter functions:
*
* <pre><code>
* setPreferredResourceFormat()
* setPreferredFeedFormat()
* </code></pre>
*
* <p>
* TODO Review all sad paths.
*
* @author Claude Nanjo
*
*/
public class FHIRToolingClient {
@ -85,55 +74,25 @@ public class FHIRToolingClient {
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 static final int TIMEOUT_NORMAL = 1500;
private static final int TIMEOUT_OPERATION = 30000;
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 ClientUtils utils = new ClientUtils();
private Client client = new Client();
//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 {
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);
@ -165,25 +124,33 @@ public class FHIRToolingClient {
}
public TerminologyCapabilities getTerminologyCapabilities() {
return (TerminologyCapabilities) utils.issueGetResourceRequest(resourceAddress.resolveMetadataTxCaps(), getPreferredResourceFormat(), "TerminologyCapabilities", TIMEOUT_NORMAL).getReference();
TerminologyCapabilities capabilities = null;
try {
capabilities = (TerminologyCapabilities) client.issueGetResourceRequest(resourceAddress.resolveMetadataTxCaps(),
getPreferredResourceFormat(), "TerminologyCapabilities", TIMEOUT_NORMAL).getReference();
} catch (Exception e) {
handleException("An error has occurred while trying to fetch the server's terminology capabilities", e);
}
return capabilities;
}
public CapabilityStatement getCapabilitiesStatement() {
CapabilityStatement conformance = null;
try {
conformance = (CapabilityStatement)utils.issueGetResourceRequest(resourceAddress.resolveMetadataUri(false), getPreferredResourceFormat(), "CapabilitiesStatement", TIMEOUT_NORMAL).getReference();
} catch(Exception e) {
conformance = (CapabilityStatement) client.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;
if (capabilities != null) return capabilities;
try {
capabilities = (CapabilityStatement)utils.issueGetResourceRequest(resourceAddress.resolveMetadataUri(true), getPreferredResourceFormat(), "CapabilitiesStatement-Quick", TIMEOUT_NORMAL).getReference();
} catch(Exception e) {
capabilities = (CapabilityStatement) client.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);
}
return capabilities;
@ -192,12 +159,10 @@ public class FHIRToolingClient {
public <T extends Resource> T read(Class<T> resourceClass, String id) {//TODO Change this to AddressableResource
ResourceRequest<T> 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());
result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),
getPreferredResourceFormat(), "Read " + resourceClass.getName() + "/" + id, 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 resource", e);
@ -208,13 +173,10 @@ public class FHIRToolingClient {
public <T extends Resource> T vread(Class<T> resourceClass, String id, String version) {
ResourceRequest<T> 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());
result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndIdAndVersion(resourceClass, id, version),
getPreferredResourceFormat(), "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) {
handleException("An error has occurred while trying to read this version of the resource", e);
@ -222,55 +184,43 @@ public class FHIRToolingClient {
return result.getPayload();
}
// GET fhir/ValueSet?url=http://hl7.org/fhir/ValueSet/clinical-findings&version=0.8
public <T extends Resource> T getCanonical(Class<T> resourceClass, String canonicalURL) {
ResourceRequest<T> 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(), "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<Resource> result = null;
org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null;
try {
List<Header> 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(),
"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
}
@ -280,222 +230,46 @@ public class FHIRToolingClient {
public <T extends Resource> T update(Class<T> resourceClass, T resource, String id) {
ResourceRequest<T> result = null;
try {
List<Header> 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());
result = client.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),
ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat())),
getPreferredResourceFormat(),"Update " + resource.fhirType() + "/" + id, 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(resourceClass, resVersionedIdentifier.getId(),resVersionedIdentifier.getVersionId());
} catch(ClassCastException e) {
// if we fall throught we have the correct type already in the create
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 <T extends Resource> boolean delete(Class<T> 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 <T extends Resource> OperationOutcome create(Class<T> resourceClass, T resource) {
// ResourceRequest<T> resourceRequest = null;
// try {
// List<Header> 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 <T extends Resource> Bundle history(Calendar lastUpdate, Class<T> 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 <T extends Resource> Bundle history(Date lastUpdate, Class<T> 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 <T extends Resource> Bundle history(Calendar lastUpdate, Class<T> 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 <T extends Resource> Bundle history(Date lastUpdate, Class<T> 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 <T extends Resource> Bundle history(Class<T> 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 <T extends Resource> Bundle history(Class<T> 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 <T extends Resource> 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 <T extends Resource> 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 <T extends Resource> 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 <T extends Resource> Bundle search(Class<T> resourceClass, Map<String, String> 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 <T extends Resource> Bundle searchPost(Class<T> resourceClass, T resource, Map<String, String> parameters) {
// Bundle searchResults = null;
// try {
// searchResults = utils.issuePostFeedRequest(resourceAddress.resolveSearchUri(resourceClass, new HashMap<String, String>()), parameters, "src", resource, getPreferredResourceFormat());
// } catch (Exception e) {
// handleException("Error performing search with parameters " + parameters, e);
// }
// return searchResults;
// }
public <T extends Resource> Parameters operateType(Class<T> 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())+"&";
ps += p.getName() + "=" + Utilities.encodeUri(((PrimitiveType) p.getValue()).asStringValue()) + "&";
ResourceRequest<T> 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);
result = client.issuePostRequest(resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps), ByteUtils.resourceToByteArray(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 = client.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.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
if (result.getPayload() instanceof Parameters) {
return (Parameters) result.getPayload();
@ -505,7 +279,7 @@ public class FHIRToolingClient {
return p_out;
}
} catch (Exception e) {
handleException("Error performing operation '"+name+"' with parameters " + ps, e);
handleException("Error performing operation '" + name + "' with parameters " + ps, e);
}
return null;
}
@ -514,7 +288,7 @@ public class FHIRToolingClient {
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());
transactionResult = client.postBatchRequest(resourceAddress.getBaseServiceUri(), ByteUtils.resourceToByteArray(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);
}
@ -525,134 +299,16 @@ public class FHIRToolingClient {
public <T extends Resource> OperationOutcome validate(Class<T> resourceClass, T resource, String id) {
ResourceRequest<T> 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());
result = client.issuePostRequest(resourceAddress.resolveValidateUri(resourceClass, id), ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), "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) {
} catch (Exception e) {
handleException("An error has occurred while trying to validate this resource", e);
}
return (OperationOutcome)result.getPayload();
return (OperationOutcome) result.getPayload();
}
/* change to meta operations
public List<Coding> 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 <T extends Resource> List<Coding> getAllTagsForResourceType(Class<T> 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 <T extends Resource> List<Coding> getTagsForReference(Class<T> 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 <T extends Resource> List<Coding> getTagsForResourceVersion(Class<T> 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 <T extends Resource> boolean deleteTagsForReference(Class<T> 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 <T extends Resource> boolean deleteTagsForResourceVersion(Class<T> resourceClass, String id, List<Coding> 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 <T extends Resource> List<Coding> createTags(List<Coding> tags, Class<T> 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 <T extends Resource> List<Coding> createTags(List<Coding> tags, Class<T> 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 <T extends Resource> List<Coding> deleteTags(List<Coding> tags, Class<T> 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
*
@ -660,8 +316,8 @@ public class FHIRToolingClient {
* @throws EFhirClientException
*/
protected void handleException(String message, Exception e) throws EFhirClientException {
if(e instanceof EFhirClientException) {
throw (EFhirClientException)e;
if (e instanceof EFhirClientException) {
throw (EFhirClientException) e;
} else {
throw new EFhirClientException(message, e);
}
@ -676,7 +332,7 @@ public class FHIRToolingClient {
*/
protected boolean isJson(String format) {
boolean isJson = false;
if(format.toLowerCase().contains("json")) {
if (format.toLowerCase().contains("json")) {
isJson = true;
}
return isJson;
@ -685,82 +341,61 @@ public class FHIRToolingClient {
public Bundle fetchFeed(String url) {
Bundle feed = null;
try {
feed = utils.issueGetFeedRequest(new URI(url), getPreferredResourceFormat());
feed = client.issueGetFeedRequest(new URI(url), getPreferredResourceFormat());
} catch (Exception e) {
handleException("An error has occurred while trying to retrieve history since last update",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<Header> headers = null;
Parameters p = expParams == null ? new Parameters() : expParams.copy();
p.addParameter().setName("valueSet").setResource(source);
ResourceRequest<Resource> 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.r5.utils.client.network.ResourceRequest<Resource> result = null;
try {
result = client.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand"),
ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), null, "ValueSet/$expand?url=" + source.getUrl(), TIMEOUT_OPERATION_EXPAND);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
return (ValueSet) result.getPayload();
} catch (MalformedURLException e) {
e.printStackTrace();
}
return result == null ? null : (ValueSet) result.getPayload();
}
public Parameters lookupCode(Map<String, String> params) {
ResourceRequest<Resource> 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.r5.utils.client.network.ResourceRequest<Resource> result = null;
try {
result = client.issueGetResourceRequest(resourceAddress.resolveOperationUri(CodeSystem.class, "lookup", params), getPreferredResourceFormat(), "CodeSystem/$lookup", TIMEOUT_NORMAL);
} catch (MalformedURLException 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<String, String> params) {
List<Header> headers = null;
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)));
}
ResourceRequest<Resource> 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.r5.utils.client.network.ResourceRequest<Resource> result = null;
try {
result = client.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand", params),
ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), null, "ValueSet/$expand?url=" + source.getUrl(), TIMEOUT_OPERATION_EXPAND);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
return (ValueSet) result.getPayload();
} catch (MalformedURLException e) {
e.printStackTrace();
}
return result == null ? null : (ValueSet) result.getPayload();
}
// public ValueSet expandValueset(ValueSet source, ExpansionProfile profile, Map<String, String> params) {
// List<Header> headers = null;
// ResourceRequest<Resource> 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;
@ -769,79 +404,60 @@ public class FHIRToolingClient {
public ConceptMap initializeClosure(String name) {
Parameters params = new Parameters();
params.addParameter().setName("name").setValue(new StringType(name));
List<Header> headers = null;
ResourceRequest<Resource> result = utils.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
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<Resource> result = null;
try {
result = client.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), null, "Closure?name=" + name, TIMEOUT_NORMAL);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
return (ConceptMap) result.getPayload();
} catch (MalformedURLException e) {
e.printStackTrace();
}
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<Header> headers = null;
ResourceRequest<Resource> result = utils.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
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.r5.utils.client.network.ResourceRequest<Resource> result = null;
try {
result = client.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), null, "UpdateClosure?name=" + name, TIMEOUT_OPERATION);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
return (ConceptMap) result.getPayload();
} catch (MalformedURLException e) {
e.printStackTrace();
}
return result == null ? null : (ConceptMap) result.getPayload();
}
public int getTimeout() {
return utils.getTimeout();
public long getTimeout() {
return client.getTimeout();
}
public void setTimeout(int timeout) {
utils.setTimeout(timeout);
}
public String getUsername() {
return utils.getUsername();
}
public void setUsername(String username) {
utils.setUsername(username);
}
public String getPassword() {
return utils.getPassword();
}
public void setPassword(String password) {
utils.setPassword(password);
public 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);
}
}

View File

@ -228,9 +228,6 @@ 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) {
Pattern pattern = Pattern.compile(REGEX_ID_WITH_HISTORY);

View File

@ -1,109 +0,0 @@
package org.hl7.fhir.r5.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.r5.model.Resource;
public class ResourceRequest<T extends Resource> {
private T payload;
private int httpStatus = -1;
private String location;
private List<Integer> successfulStatuses = new ArrayList<Integer>();
private List<Integer> errorStatuses = new ArrayList<Integer>();
public ResourceRequest(T payload, int httpStatus, List<Integer> successfulStatuses, List<Integer> errorStatuses, String location) {
this.payload = payload;
this.httpStatus = httpStatus;
if(successfulStatuses != null) {
this.successfulStatuses.addAll(successfulStatuses);
}
if(errorStatuses != null) {
this.errorStatuses.addAll(errorStatuses);
}
this.location = location;
}
public ResourceRequest(T payload, int httpStatus, String location) {
this.payload = payload;
this.httpStatus = httpStatus;
this.location = location;
}
public ResourceRequest(T payload, int httpStatus, int successfulStatus, String location) {
this.payload = payload;
this.httpStatus = httpStatus;
this.successfulStatuses.add(successfulStatus);
this.location = location;
}
public int getHttpStatus() {
return httpStatus;
}
public T getPayload() {
return payload;
}
public T getReference() {
T payloadResource = null;
if(payload != null) {
payloadResource = payload;
}
return payloadResource;
}
public boolean isSuccessfulRequest() {
return successfulStatuses.contains(httpStatus) && !errorStatuses.contains(httpStatus) && httpStatus > 0;
}
public boolean isUnsuccessfulRequest() {
return !isSuccessfulRequest();
}
public void addSuccessStatus(int status) {
this.successfulStatuses.add(status);
}
public void addErrorStatus(int status) {
this.errorStatuses.add(status);
}
public String getLocation() {
return location;
}
}

View File

@ -0,0 +1,69 @@
package org.hl7.fhir.r5.utils.client.network;
import org.hl7.fhir.r5.formats.IParser;
import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.formats.XmlParser;
import org.hl7.fhir.r5.model.Bundle;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.utils.client.EFhirClientException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.Map;
public class ByteUtils {
public static <T extends Resource> byte[] resourceToByteArray(T resource, boolean pretty, boolean isJson) {
ByteArrayOutputStream baos = null;
byte[] byteArray = null;
try {
baos = new ByteArrayOutputStream();
IParser parser = null;
if (isJson) {
parser = new JsonParser();
} else {
parser = new XmlParser();
}
parser.setOutputStyle(pretty ? IParser.OutputStyle.PRETTY : IParser.OutputStyle.NORMAL);
parser.compose(baos, resource);
baos.close();
byteArray = baos.toByteArray();
baos.close();
} catch (Exception e) {
try {
baos.close();
} catch (Exception ex) {
throw new EFhirClientException("Error closing output stream", ex);
}
throw new EFhirClientException("Error converting output stream to byte array", e);
}
return byteArray;
}
public static byte[] encodeFormSubmission(Map<String, String> parameters, String resourceName, Resource resource, String boundary) throws IOException {
ByteArrayOutputStream b = new ByteArrayOutputStream();
OutputStreamWriter w = new OutputStreamWriter(b, StandardCharsets.UTF_8);
for (String name : parameters.keySet()) {
w.write("--");
w.write(boundary);
w.write("\r\nContent-Disposition: form-data; name=\"" + name + "\"\r\n\r\n");
w.write(parameters.get(name) + "\r\n");
}
w.write("--");
w.write(boundary);
w.write("\r\nContent-Disposition: form-data; name=\"" + resourceName + "\"\r\n\r\n");
w.close();
JsonParser json = new JsonParser();
json.setOutputStyle(IParser.OutputStyle.NORMAL);
json.compose(b, resource);
b.close();
w = new OutputStreamWriter(b, StandardCharsets.UTF_8);
w.write("\r\n--");
w.write(boundary);
w.write("--");
w.close();
return b.toByteArray();
}
}

View File

@ -0,0 +1,234 @@
package org.hl7.fhir.r5.utils.client.network;
import okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import org.hl7.fhir.r5.model.Bundle;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.utils.client.EFhirClientException;
import org.hl7.fhir.utilities.ToolingClientLogger;
import 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;
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;
}
public void setLogger(ToolingClientLogger logger) {
this.logger = logger;
}
public int getRetryCount() {
return retryCount;
}
public void setRetryCount(int retryCount) {
this.retryCount = retryCount;
}
public long getTimeout() {
return timeout;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
public <T extends Resource> ResourceRequest<T> issueOptionsRequest(URI optionsUri,
String resourceFormat,
String message,
long timeout) throws MalformedURLException {
Request.Builder request = new Request.Builder()
.method("OPTIONS", null)
.url(optionsUri.toURL());
return executeFhirRequest(request, resourceFormat, null, message, retryCount, timeout);
}
public <T extends Resource> ResourceRequest<T> issueGetResourceRequest(URI resourceUri,
String resourceFormat,
String message,
long timeout) throws MalformedURLException {
Request.Builder request = new Request.Builder()
.url(resourceUri.toURL());
return executeFhirRequest(request, resourceFormat, null, message, retryCount, timeout);
}
public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri,
byte[] payload,
String resourceFormat,
String message,
long timeout) throws MalformedURLException {
return issuePutRequest(resourceUri, payload, resourceFormat, null, message, timeout);
}
public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri,
byte[] payload,
String resourceFormat,
Headers headers,
String message,
long timeout) throws MalformedURLException {
if (payload == null) throw new EFhirClientException("PUT requests require a non-null payload");
RequestBody body = RequestBody.create(payload);
Request.Builder request = new Request.Builder()
.url(resourceUri.toURL())
.put(body);
return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout);
}
public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri,
byte[] payload,
String resourceFormat,
String message,
long timeout) throws MalformedURLException {
return issuePostRequest(resourceUri, payload, resourceFormat, null, message, timeout);
}
public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri,
byte[] payload,
String resourceFormat,
Headers headers,
String message,
long timeout) throws MalformedURLException {
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 MalformedURLException {
Request.Builder request = new Request.Builder()
.url(resourceUri.toURL())
.delete();
return executeFhirRequest(request, null, null, null, retryCount, timeout).isSuccessfulRequest();
}
public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat) throws MalformedURLException {
Request.Builder request = new Request.Builder()
.url(resourceUri.toURL());
return executeBundleRequest(request, resourceFormat, null, null, retryCount, timeout);
}
public Bundle issuePostFeedRequest(URI resourceUri,
Map<String, String> parameters,
String resourceName,
Resource resource,
String resourceFormat) throws IOException {
String boundary = "----WebKitFormBoundarykbMUo6H8QaUnYtRy";
byte[] payload = ByteUtils.encodeFormSubmission(parameters, resourceName, resource, boundary);
RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
Request.Builder request = new Request.Builder()
.url(resourceUri.toURL())
.post(body);
return executeBundleRequest(request, resourceFormat, null, null, retryCount, timeout);
}
public Bundle postBatchRequest(URI resourceUri,
byte[] payload,
String resourceFormat,
String message,
int timeout) throws MalformedURLException {
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, null, message, retryCount, timeout);
}
protected <T extends Resource> Bundle executeBundleRequest(Request.Builder request,
String resourceFormat,
Headers headers,
String message,
int retryCount,
long timeout) {
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();
}
protected <T extends Resource> ResourceRequest<T> executeFhirRequest(Request.Builder request,
String resourceFormat,
Headers headers,
String message,
int retryCount,
long timeout) {
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();
}
/**
* @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);
}
}
}

View File

@ -0,0 +1,339 @@
package org.hl7.fhir.r5.utils.client.network;
import kotlin.Pair;
import okhttp3.*;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.r5.formats.IParser;
import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.formats.XmlParser;
import org.hl7.fhir.r5.model.Bundle;
import org.hl7.fhir.r5.model.OperationOutcome;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.utils.ResourceUtilities;
import org.hl7.fhir.r5.utils.client.EFhirClientException;
import org.hl7.fhir.r5.utils.client.ResourceFormat;
import org.hl7.fhir.utilities.ToolingClientLogger;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
public class FhirRequestBuilder {
protected static final String HTTP_PROXY_USER = "http.proxyUser";
protected static final String HTTP_PROXY_PASS = "http.proxyPassword";
protected static final String HEADER_PROXY_AUTH = "Proxy-Authorization";
protected static final String LOCATION_HEADER = "location";
protected static final String CONTENT_LOCATION_HEADER = "content-location";
protected static final String DEFAULT_CHARSET = "UTF-8";
/**
* The singleton instance of the HttpClient, used for all requests.
*/
private static OkHttpClient okHttpClient;
private final Request.Builder httpRequest;
private String resourceFormat = null;
private Headers headers = null;
private String message = null;
private int retryCount = 1;
/**
* The timeout quantity. Used in combination with {@link FhirRequestBuilder#timeoutUnit}.
*/
private long timeout = 5000;
/**
* Time unit for {@link FhirRequestBuilder#timeout}.
*/
private TimeUnit timeoutUnit = TimeUnit.MILLISECONDS;
/**
* {@link ToolingClientLogger} for log output.
*/
private ToolingClientLogger logger = null;
public FhirRequestBuilder(Request.Builder httpRequest) {
this.httpRequest = httpRequest;
}
/**
* Adds necessary default headers, formatting headers, and any passed in {@link Headers} to the passed in
* {@link okhttp3.Request.Builder}
*
* @param request {@link okhttp3.Request.Builder} to add headers to.
* @param format Expected {@link Resource} format.
* @param headers Any additional {@link Headers} to add to the request.
*/
protected static void formatHeaders(Request.Builder request, String format, Headers headers) {
addDefaultHeaders(request);
if (format != null) addResourceFormatHeaders(request, format);
if (headers != null) addHeaders(request, headers);
}
/**
* Adds necessary headers for all REST requests.
* <li>User-Agent : hapi-fhir-tooling-client</li>
* <li>Accept-Charset : {@link FhirRequestBuilder#DEFAULT_CHARSET}</li>
*
* @param request {@link Request.Builder} to add default headers to.
*/
protected static void addDefaultHeaders(Request.Builder request) {
request.addHeader("User-Agent", "hapi-fhir-tooling-client");
request.addHeader("Accept-Charset", DEFAULT_CHARSET);
}
/**
* Adds necessary headers for the given resource format provided.
*
* @param request {@link Request.Builder} to add default headers to.
*/
protected static void addResourceFormatHeaders(Request.Builder request, String format) {
request.addHeader("Accept", format);
request.addHeader("Content-Type", format + ";charset=" + DEFAULT_CHARSET);
}
/**
* Iterates through the passed in {@link Headers} and adds them to the provided {@link Request.Builder}.
*
* @param request {@link Request.Builder} to add headers to.
* @param headers {@link Headers} to add to request.
*/
protected static void addHeaders(Request.Builder request, Headers headers) {
headers.forEach(header -> request.addHeader(header.getFirst(), header.getSecond()));
}
/**
* Returns true if any of the {@link org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent} within the
* provided {@link OperationOutcome} have an {@link org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity} of
* {@link org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity#ERROR} or
* {@link org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity#FATAL}
*
* @param oo {@link OperationOutcome} to evaluate.
* @return {@link Boolean#TRUE} if an error exists.
*/
protected static boolean hasError(OperationOutcome oo) {
return (oo.getIssue().stream()
.anyMatch(issue -> issue.getSeverity() == OperationOutcome.IssueSeverity.ERROR
|| issue.getSeverity() == OperationOutcome.IssueSeverity.FATAL));
}
/**
* Extracts the 'location' header from the passes in {@link Headers}. If no value for 'location' exists, the
* value for 'content-location' is returned. If neither header exists, we return null.
*
* @param headers {@link Headers} to evaluate
* @return {@link String} header value, or null if no location headers are set.
*/
protected static String getLocationHeader(Headers headers) {
Map<String, List<String>> headerMap = headers.toMultimap();
if (headerMap.containsKey(LOCATION_HEADER)) {
return headerMap.get(LOCATION_HEADER).get(0);
} else if (headerMap.containsKey(CONTENT_LOCATION_HEADER)) {
return headerMap.get(CONTENT_LOCATION_HEADER).get(0);
} else {
return null;
}
}
/**
* We only ever want to have one copy of the HttpClient kicking around at any given time. If we need to make changes
* to any configuration, such as proxy settings, timeout, caches, etc, we can do a per-call configuration through
* the {@link OkHttpClient#newBuilder()} method. That will return a builder that shares the same connection pool,
* dispatcher, and configuration with the original client.
* </p>
* The {@link OkHttpClient} uses the proxy auth properties set in the current system properties. The reason we don't
* set the proxy address and authentication explicitly, is due to the fact that this class is often used in conjunction
* with other http client tools which rely on the system.properties settings to determine proxy settings. It's easier
* to keep the method consistent across the board. ...for now.
*
* @return {@link OkHttpClient} instance
*/
protected OkHttpClient getHttpClient() {
if (okHttpClient == null) {
okHttpClient = new OkHttpClient();
}
Authenticator proxyAuthenticator = (route, response) -> {
String credential = Credentials.basic(System.getProperty(HTTP_PROXY_USER), System.getProperty(HTTP_PROXY_PASS));
return response.request().newBuilder()
.header(HEADER_PROXY_AUTH, credential)
.build();
};
return okHttpClient.newBuilder()
.addInterceptor(new RetryInterceptor(retryCount))
.connectTimeout(timeout, timeoutUnit)
.writeTimeout(timeout, timeoutUnit)
.readTimeout(timeout, timeoutUnit)
.proxyAuthenticator(proxyAuthenticator)
.build();
}
public FhirRequestBuilder withResourceFormat(String resourceFormat) {
this.resourceFormat = resourceFormat;
return this;
}
public FhirRequestBuilder withHeaders(Headers headers) {
this.headers = headers;
return this;
}
public FhirRequestBuilder withMessage(String message) {
this.message = message;
return this;
}
public FhirRequestBuilder withRetryCount(int retryCount) {
this.retryCount = retryCount;
return this;
}
public FhirRequestBuilder withLogger(ToolingClientLogger logger) {
this.logger = logger;
return this;
}
public FhirRequestBuilder withTimeout(long timeout, TimeUnit unit) {
this.timeout = timeout;
this.timeoutUnit = unit;
return this;
}
protected Request buildRequest() {
return httpRequest.build();
}
public <T extends Resource> ResourceRequest<T> execute() {
formatHeaders(httpRequest, resourceFormat, null);
try {
Response response = getHttpClient().newCall(httpRequest.build()).execute();
T resource = unmarshalReference(response, resourceFormat);
return new ResourceRequest<T>(resource, response.code(), getLocationHeader(response.headers()));
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public Bundle executeAsBatch() {
formatHeaders(httpRequest, resourceFormat, null);
try {
Response response = getHttpClient().newCall(httpRequest.build()).execute();
return unmarshalFeed(response, resourceFormat);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* Unmarshalls a resource from the response stream.
*/
@SuppressWarnings("unchecked")
protected <T extends Resource> T unmarshalReference(Response response, String format) {
T resource = null;
OperationOutcome error = null;
if (response.body() != null) {
try {
byte[] body = response.body().bytes();
log(response.code(), response.headers(), body);
resource = (T) getParser(format).parse(body);
if (resource instanceof OperationOutcome && hasError((OperationOutcome) resource)) {
error = (OperationOutcome) resource;
}
} catch (IOException ioe) {
throw new EFhirClientException("Error reading Http Response: " + ioe.getMessage(), ioe);
} catch (Exception e) {
throw new EFhirClientException("Error parsing response message: " + e.getMessage(), e);
}
}
if (error != null) {
throw new EFhirClientException("Error from server: " + ResourceUtilities.getErrorDescription(error), error);
}
return resource;
}
/**
* Unmarshalls Bundle from response stream.
*/
protected Bundle unmarshalFeed(Response response, String format) {
Bundle feed = null;
OperationOutcome error = null;
try {
byte[] body = response.body().bytes();
log(response.code(), response.headers(), body);
String contentType = response.header("Content-Type");
if (body != null) {
if (contentType.contains(ResourceFormat.RESOURCE_XML.getHeader()) || contentType.contains("text/xml+fhir")) {
Resource rf = getParser(format).parse(body);
if (rf instanceof Bundle)
feed = (Bundle) rf;
else if (rf instanceof OperationOutcome && hasError((OperationOutcome) rf)) {
error = (OperationOutcome) rf;
} else {
throw new EFhirClientException("Error reading server response: a resource was returned instead");
}
}
}
} catch (IOException ioe) {
throw new EFhirClientException("Error reading Http Response", ioe);
} catch (Exception e) {
throw new EFhirClientException("Error parsing response message", e);
}
if (error != null) {
throw new EFhirClientException("Error from server: " + ResourceUtilities.getErrorDescription(error), error);
}
return feed;
}
/**
* Returns the appropriate parser based on the format type passed in. Defaults to XML parser if a blank format is
* provided...because reasons.
* <p>
* Currently supports only "json" and "xml" formats.
*
* @param format One of "json" or "xml".
* @return {@link JsonParser} or {@link XmlParser}
*/
protected IParser getParser(String format) {
if (StringUtils.isBlank(format)) {
format = ResourceFormat.RESOURCE_XML.getHeader();
}
if (format.equalsIgnoreCase("json") || format.equalsIgnoreCase(ResourceFormat.RESOURCE_JSON.getHeader())) {
return new JsonParser();
} else if (format.equalsIgnoreCase("xml") || format.equalsIgnoreCase(ResourceFormat.RESOURCE_XML.getHeader())) {
return new XmlParser();
} else {
throw new EFhirClientException("Invalid format: " + format);
}
}
/**
* Logs the given {@link Response}, using the current {@link ToolingClientLogger}. If the current
* {@link FhirRequestBuilder#logger} is null, no action is taken.
*
* @param responseCode HTTP response code
* @param responseHeaders {@link Headers} from response
* @param responseBody Byte array response
*/
protected void log(int responseCode, Headers responseHeaders, byte[] responseBody) {
if (logger != null) {
List<String> headerList = new ArrayList<>(Collections.emptyList());
Map<String, List<String>> headerMap = responseHeaders.toMultimap();
headerMap.keySet().forEach(key -> headerMap.get(key).forEach(value -> headerList.add(key + ":" + value)));
try {
logger.logResponse(Integer.toString(responseCode), headerList, responseBody);
} catch (Exception e) {
System.out.println("Error parsing response body passed in to logger ->\n" + e.getLocalizedMessage());
}
} else {
System.out.println("Call to log HTTP response with null ToolingClientLogger set... are you forgetting to " +
"initialize your logger?");
}
}
}

View File

@ -0,0 +1,71 @@
package org.hl7.fhir.r5.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.r5.model.Resource;
public class ResourceRequest<T extends Resource> {
private T payload;
private int httpStatus = -1;
private String location;
public ResourceRequest(T payload, int httpStatus, String location) {
this.payload = payload;
this.httpStatus = httpStatus;
this.location = location;
}
public int getHttpStatus() {
return httpStatus;
}
public T getPayload() {
return payload;
}
public T getReference() {
T payloadResource = null;
if (payload != null) {
payloadResource = payload;
}
return payloadResource;
}
public boolean isSuccessfulRequest() {
return this.httpStatus / 100 == 2;
}
public boolean isUnsuccessfulRequest() {
return !isSuccessfulRequest();
}
public String getLocation() {
return location;
}
}

View File

@ -0,0 +1,63 @@
package org.hl7.fhir.r5.utils.client.network;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import org.jetbrains.annotations.NotNull;
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 @NotNull 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);
}
}

View File

@ -0,0 +1,146 @@
package org.hl7.fhir.r5.utils.client.network;
import okhttp3.HttpUrl;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.model.*;
import org.junit.jupiter.api.*;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Random;
import java.util.concurrent.TimeUnit;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ClientTest {
private static final long TIMEOUT = 5000;
private MockWebServer server;
private HttpUrl serverUrl;
private Client client;
private Address address = new Address()
.setCity("Toronto")
.setState("Ontario")
.setCountry("Canada");
private HumanName humanName = new HumanName()
.addGiven("Mark")
.setFamily("Iantorno");
private Patient patient = new Patient()
.addName(humanName)
.addAddress(address)
.setGender(Enumerations.AdministrativeGender.MALE);
@BeforeEach
void setup() {
setupMockServer();
client = new Client();
}
void setupMockServer() {
server = new MockWebServer();
serverUrl = server.url("/v1/endpoint");
}
byte[] generateResourceBytes(Resource resource) throws IOException {
return new JsonParser().composeBytes(resource);
}
@Test
@DisplayName("GET request, happy path.")
void test_get_happy_path() throws IOException, URISyntaxException {
server.enqueue(
new MockResponse()
.setBody(new String(generateResourceBytes(patient)))
);
ResourceRequest<Resource> resourceRequest = client.issueGetResourceRequest(new URI(serverUrl.toString()),
"json", null, TIMEOUT);
Assertions.assertTrue(resourceRequest.isSuccessfulRequest());
Assertions.assertTrue(patient.equalsDeep(resourceRequest.getPayload()),
"GET request returned resource does not match expected.");
}
@Test
@DisplayName("GET request, test client retries after timeout failure.")
void test_get_retries_with_timeout() throws IOException, URISyntaxException {
int failedAttempts = new Random().nextInt(5) + 1;
System.out.println("Simulating <" + failedAttempts + "> failed connections (timeouts) before success.");
for (int i = 0; i < failedAttempts; i++) {
server.enqueue(
new MockResponse()
.setHeadersDelay(TIMEOUT * 10, TimeUnit.MILLISECONDS)
.setBody(new String(generateResourceBytes(patient)))
);
}
server.enqueue(new MockResponse().setBody(new String(generateResourceBytes(patient))));
client.setRetryCount(failedAttempts + 1);
ResourceRequest<Resource> resourceRequest = client.issueGetResourceRequest(new URI(serverUrl.toString()),
"json", null, TIMEOUT);
Assertions.assertTrue(resourceRequest.isSuccessfulRequest());
Assertions.assertTrue(patient.equalsDeep(resourceRequest.getPayload()),
"GET request returned resource does not match expected.");
}
@Test
@DisplayName("GET request, test client retries after bad response.")
void test_get_retries_with_unsuccessful_response() throws IOException, URISyntaxException {
int failedAttempts = new Random().nextInt(5) + 1;
System.out.println("Simulating <" + failedAttempts + "> failed connections (bad response codes) before success.");
for (int i = 0; i < failedAttempts; i++) {
server.enqueue(
new MockResponse()
.setResponseCode(400 + i)
.setBody(new String(generateResourceBytes(patient)))
);
}
server.enqueue(new MockResponse().setBody(new String(generateResourceBytes(patient))));
client.setRetryCount(failedAttempts + 1);
ResourceRequest<Resource> resourceRequest = client.issueGetResourceRequest(new URI(serverUrl.toString()),
"json", null, TIMEOUT);
Assertions.assertTrue(resourceRequest.isSuccessfulRequest());
Assertions.assertTrue(patient.equalsDeep(resourceRequest.getPayload()),
"GET request returned resource does not match expected.");
}
@Test
@DisplayName("PUT request, test payload received by server matches sent.")
void test_put() throws IOException, URISyntaxException, InterruptedException {
byte[] payload = ByteUtils.resourceToByteArray(patient, true, false);
// Mock server response of 200, with the same resource payload returned that we included in the PUT request
server.enqueue(
new MockResponse()
.setResponseCode(200)
.setBody(new String(payload))
);
ResourceRequest<Resource> request = client.issuePutRequest(new URI(serverUrl.toString()), payload,
"xml", null, TIMEOUT);
RecordedRequest recordedRequest = server.takeRequest();
Assertions.assertArrayEquals(payload, recordedRequest.getBody().readByteArray(),
"PUT request payload does not match send data.");
}
@Test
@DisplayName("POST request, test payload received by server matches sent.")
void test_post() throws IOException, URISyntaxException, InterruptedException {
byte[] payload = ByteUtils.resourceToByteArray(patient, true, false);
// Mock server response of 200, with the same resource payload returned that we included in the PUT request
server.enqueue(
new MockResponse()
.setResponseCode(200)
.setBody(new String(payload))
);
ResourceRequest<Resource> request = client.issuePostRequest(new URI(serverUrl.toString()), payload,
"xml", null, TIMEOUT);
RecordedRequest recordedRequest = server.takeRequest();
Assertions.assertArrayEquals(payload, recordedRequest.getBody().readByteArray(),
"POST request payload does not match send data.");
}
}

View File

@ -0,0 +1,147 @@
package org.hl7.fhir.r5.utils.client.network;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import org.hl7.fhir.r5.model.OperationOutcome;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class FhirRequestBuilderTest {
@Test
@DisplayName("Test default headers are added correctly.")
void addDefaultHeaders() {
Request.Builder request = new Request.Builder().url("http://www.google.com");
FhirRequestBuilder.addDefaultHeaders(request);
Map<String, List<String>> headersMap = request.build().headers().toMultimap();
Assertions.assertNotNull(headersMap.get("User-Agent"), "User-Agent header null.");
Assertions.assertEquals("hapi-fhir-tooling-client", headersMap.get("User-Agent").get(0),
"User-Agent header not populated with expected value \"hapi-fhir-tooling-client\".");
Assertions.assertNotNull(headersMap.get("Accept-Charset"), "Accept-Charset header null.");
Assertions.assertEquals(FhirRequestBuilder.DEFAULT_CHARSET, headersMap.get("Accept-Charset").get(0),
"Accept-Charset header not populated with expected value " + FhirRequestBuilder.DEFAULT_CHARSET);
}
@Test
@DisplayName("Test resource format headers are added correctly.")
void addResourceFormatHeaders() {
String testFormat = "yaml";
Request.Builder request = new Request.Builder().url("http://www.google.com");
FhirRequestBuilder.addResourceFormatHeaders(request, testFormat);
Map<String, List<String>> headersMap = request.build().headers().toMultimap();
Assertions.assertNotNull(headersMap.get("Accept"), "Accept header null.");
Assertions.assertEquals(testFormat, headersMap.get("Accept").get(0),
"Accept header not populated with expected value " + testFormat + ".");
Assertions.assertNotNull(headersMap.get("Content-Type"), "Content-Type header null.");
Assertions.assertEquals(testFormat + ";charset=" + FhirRequestBuilder.DEFAULT_CHARSET, headersMap.get("Content-Type").get(0),
"Content-Type header not populated with expected value \"" + testFormat + ";charset=" + FhirRequestBuilder.DEFAULT_CHARSET + "\".");
}
@Test
@DisplayName("Test a list of provided headers are added correctly.")
void addHeaders() {
String headerName1 = "headerName1";
String headerValue1 = "headerValue1";
String headerName2 = "headerName2";
String headerValue2 = "headerValue2";
Headers headers = new Headers.Builder()
.add(headerName1, headerValue1)
.add(headerName2, headerValue2)
.build();
Request.Builder request = new Request.Builder().url("http://www.google.com");
FhirRequestBuilder.addHeaders(request, headers);
Map<String, List<String>> headersMap = request.build().headers().toMultimap();
Assertions.assertNotNull(headersMap.get(headerName1), headerName1 + " header null.");
Assertions.assertEquals(headerValue1, headersMap.get(headerName1).get(0),
headerName1 + " header not populated with expected value " + headerValue1 + ".");
Assertions.assertNotNull(headersMap.get(headerName2), headerName2 + " header null.");
Assertions.assertEquals(headerValue2, headersMap.get(headerName2).get(0),
headerName2 + " header not populated with expected value " + headerValue2 + ".");
}
@Test
@DisplayName("Test that FATAL issue severity triggers error.")
void hasErrorTestFatal() {
OperationOutcome outcome = new OperationOutcome();
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.INFORMATION));
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.NULL));
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.WARNING));
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.FATAL));
Assertions.assertTrue(FhirRequestBuilder.hasError(outcome), "Error check not triggered for FATAL issue severity.");
}
@Test
@DisplayName("Test that ERROR issue severity triggers error.")
void hasErrorTestError() {
OperationOutcome outcome = new OperationOutcome();
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.INFORMATION));
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.NULL));
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.WARNING));
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.ERROR));
Assertions.assertTrue(FhirRequestBuilder.hasError(outcome), "Error check not triggered for ERROR issue severity.");
}
@Test
@DisplayName("Test that no FATAL or ERROR issue severity does not trigger error.")
void hasErrorTestNoErrors() {
OperationOutcome outcome = new OperationOutcome();
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.INFORMATION));
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.NULL));
outcome.addIssue(new OperationOutcome.OperationOutcomeIssueComponent().setSeverity(OperationOutcome.IssueSeverity.WARNING));
Assertions.assertFalse(FhirRequestBuilder.hasError(outcome), "Error check triggered unexpectedly.");
}
@Test
@DisplayName("Test that getLocationHeader returns header for 'location'.")
void getLocationHeaderWhenOnlyLocationIsSet() {
final String expectedLocationHeader = "location_header_value";
Headers headers = new Headers.Builder()
.add(FhirRequestBuilder.LOCATION_HEADER, expectedLocationHeader)
.build();
Assertions.assertEquals(expectedLocationHeader, FhirRequestBuilder.getLocationHeader(headers));
}
@Test
@DisplayName("Test that getLocationHeader returns header for 'content-location'.")
void getLocationHeaderWhenOnlyContentLocationIsSet() {
final String expectedContentLocationHeader = "content_location_header_value";
Headers headers = new Headers.Builder()
.add(FhirRequestBuilder.CONTENT_LOCATION_HEADER, expectedContentLocationHeader)
.build();
Assertions.assertEquals(expectedContentLocationHeader, FhirRequestBuilder.getLocationHeader(headers));
}
@Test
@DisplayName("Test that getLocationHeader returns 'location' header when both 'location' and 'content-location' are set.")
void getLocationHeaderWhenLocationAndContentLocationAreSet() {
final String expectedLocationHeader = "location_header_value";
final String expectedContentLocationHeader = "content_location_header_value";
Headers headers = new Headers.Builder()
.add(FhirRequestBuilder.LOCATION_HEADER, expectedLocationHeader)
.add(FhirRequestBuilder.CONTENT_LOCATION_HEADER, expectedContentLocationHeader)
.build();
Assertions.assertEquals(expectedLocationHeader, FhirRequestBuilder.getLocationHeader(headers));
}
@Test
@DisplayName("Test that getLocationHeader returns null when no location available.")
void getLocationHeaderWhenNoLocationSet() {
Headers headers = new Headers.Builder()
.build();
Assertions.assertNull(FhirRequestBuilder.getLocationHeader(headers));
}
}

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>5.2.1-SNAPSHOT</version>
<version>5.2.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>5.2.1-SNAPSHOT</version>
<version>5.2.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>5.2.1-SNAPSHOT</version>
<version>5.2.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>5.2.1-SNAPSHOT</version>
<version>5.2.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -68,6 +68,11 @@ import org.hl7.fhir.validation.cli.services.ValidationService;
import org.hl7.fhir.validation.cli.utils.*;
import java.io.File;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.URL;
import java.net.URLConnection;
import java.util.Base64;
/**
* A executable class that will validate one or more FHIR resources against
@ -86,6 +91,11 @@ public class ValidatorCli {
public static final String HTTP_PROXY_HOST = "http.proxyHost";
public static final String HTTP_PROXY_PORT = "http.proxyPort";
public static final String HTTP_PROXY_USER = "http.proxyUser";
public static final String HTTP_PROXY_PASS = "http.proxyPassword";
public static final String JAVA_DISABLED_TUNNELING_SCHEMES = "jdk.http.auth.tunneling.disabledSchemes";
public static final String JAVA_DISABLED_PROXY_SCHEMES = "jdk.http.auth.proxying.disabledSchemes";
public static final String JAVA_USE_SYSTEM_PROXIES = "java.net.useSystemProxies";
public static void main(String[] args) throws Exception {
TimeTracker tt = new TimeTracker();
@ -95,11 +105,45 @@ public class ValidatorCli {
Display.displaySystemInfo();
if (Params.hasParam(args, Params.PROXY)) {
String[] p = Params.getParam(args, Params.PROXY).split("\\:");
assert Params.getParam(args, Params.PROXY) != null : "PROXY arg passed in was NULL";
String[] p = Params.getParam(args, Params.PROXY).split(":");
System.setProperty(HTTP_PROXY_HOST, p[0]);
System.setProperty(HTTP_PROXY_PORT, p[1]);
}
if (Params.hasParam(args, Params.PROXY_AUTH)) {
assert Params.getParam(args, Params.PROXY) != null : "Cannot set PROXY_AUTH without setting PROXY...";
assert Params.getParam(args, Params.PROXY_AUTH) != null : "PROXY_AUTH arg passed in was NULL...";
String[] p = Params.getParam(args, Params.PROXY_AUTH).split(":");
String authUser = p[0];
String authPass = p[1];
/*
* For authentication, use java.net.Authenticator to set proxy's configuration and set the system properties
* http.proxyUser and http.proxyPassword
*/
Authenticator.setDefault(
new Authenticator() {
@Override
public PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(authUser, authPass.toCharArray());
}
}
);
System.setProperty(HTTP_PROXY_USER, authUser);
System.setProperty(HTTP_PROXY_PASS, authPass);
System.setProperty(JAVA_USE_SYSTEM_PROXIES, "true");
/*
* For Java 1.8 and higher you must set
* -Djdk.http.auth.tunneling.disabledSchemes=
* to make proxies with Basic Authorization working with https along with Authenticator
*/
System.setProperty(JAVA_DISABLED_TUNNELING_SCHEMES, "");
System.setProperty(JAVA_DISABLED_PROXY_SCHEMES, "");
}
CliContext cliContext = Params.loadCliContext(args);
if (Params.hasParam(args, Params.TEST)) {

View File

@ -1,5 +1,6 @@
package org.hl7.fhir.validation.cli.utils;
import org.apache.http.auth.AUTH;
import org.hl7.fhir.r5.utils.IResourceValidator.BundleValidationRule;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.validation.cli.model.CliContext;
@ -14,6 +15,7 @@ public class Params {
public static final String OUTPUT = "-output";
public static final String HTML_OUTPUT = "-html-output";
public static final String PROXY = "-proxy";
public static final String PROXY_AUTH = "-auth";
public static final String PROFILE = "-profile";
public static final String BUNDLE = "-bundle";
public static final String QUESTIONNAIRE = "-questionnaire";
@ -101,6 +103,8 @@ public class Params {
cliContext.setHtmlOutput(args[++i]);
} else if (args[i].equals(PROXY)) {
i++; // ignore next parameter
} else if (args[i].equals(PROXY_AUTH)) {
i++;
} else if (args[i].equals(PROFILE)) {
String p = null;
if (i + 1 == args.length) {

View File

@ -14,7 +14,7 @@
HAPI FHIR.
-->
<artifactId>org.hl7.fhir.core</artifactId>
<version>5.2.1-SNAPSHOT</version>
<version>5.2.2-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>