Merge branch 'master' of https://github.com/hapifhir/org.hl7.fhir.core
This commit is contained in:
commit
d8df660c33
|
@ -6,7 +6,7 @@
|
|||
|
||||
### CI/CD
|
||||
|
||||
All intergration and delivery done on Azure pipelines. Azure project can be viewed [here][Link-AzureProject].
|
||||
All integration and delivery done on Azure pipelines. Azure project can be viewed [here][Link-AzureProject].
|
||||
|
||||
### Current Versions
|
||||
| Project | Current Release | Latest SNAPSHOT |
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>org.hl7.fhir.core</artifactId>
|
||||
<version>5.1.23-SNAPSHOT</version>
|
||||
<version>5.2.8-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -3,6 +3,7 @@ package org.hl7.fhir.convertors.conv40_50;
|
|||
|
||||
import org.hl7.fhir.convertors.VersionConvertor_40_50;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupTypeMode;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -220,8 +221,11 @@ public class StructureMap40_50 extends VersionConvertor_40_50 {
|
|||
tgt.setNameElement(convertId(src.getNameElement()));
|
||||
if (src.hasExtends())
|
||||
tgt.setExtendsElement(convertId(src.getExtendsElement()));
|
||||
if (src.hasTypeMode())
|
||||
if (src.hasTypeMode()) {
|
||||
tgt.setTypeModeElement(convertStructureMapGroupTypeMode(src.getTypeModeElement()));
|
||||
} else {
|
||||
tgt.setTypeMode(StructureMapGroupTypeMode.NONE);
|
||||
}
|
||||
if (src.hasDocumentation())
|
||||
tgt.setDocumentationElement(convertString(src.getDocumentationElement()));
|
||||
for (org.hl7.fhir.r5.model.StructureMap.StructureMapGroupInputComponent t : src.getInput()) tgt.addInput(convertStructureMapGroupInputComponent(t));
|
||||
|
|
|
@ -36,6 +36,7 @@ import java.net.URISyntaxException;
|
|||
import org.hl7.fhir.r5.model.FhirPublication;
|
||||
import org.hl7.fhir.r5.terminologies.TerminologyClient;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.VersionUtilities;
|
||||
|
||||
public class TerminologyClientFactory {
|
||||
|
||||
|
@ -53,6 +54,20 @@ public class TerminologyClientFactory {
|
|||
}
|
||||
}
|
||||
|
||||
public static TerminologyClient makeClient(String url, String v) throws URISyntaxException {
|
||||
if (v == null)
|
||||
return new TerminologyClientR5(checkEndsWith("/r4", url));
|
||||
v = VersionUtilities.getMajMin(v);
|
||||
switch (v) {
|
||||
case "1.0": return new TerminologyClientR2(checkEndsWith("/r2", url));
|
||||
case "1.4": return new TerminologyClientR3(checkEndsWith("/r3", url)); // r3 is the least worst match
|
||||
case "3.0": return new TerminologyClientR3(checkEndsWith("/r3", url));
|
||||
case "4.0": return new TerminologyClientR4(checkEndsWith("/r4", url));
|
||||
case "4.5": return new TerminologyClientR5(checkEndsWith("/r4", url)); // r4 for now, since the terminology is currently the same
|
||||
default: throw new Error("The version "+v.toString()+" is not currently supported");
|
||||
}
|
||||
}
|
||||
|
||||
private static String checkEndsWith(String term, String url) {
|
||||
if (url.endsWith(term))
|
||||
return url;
|
||||
|
|
|
@ -36,15 +36,19 @@ import java.util.Map;
|
|||
|
||||
import org.hl7.fhir.convertors.VersionConvertor_10_50;
|
||||
import org.hl7.fhir.convertors.VersionConvertor_40_50;
|
||||
import org.hl7.fhir.dstu2.model.Resource;
|
||||
import org.hl7.fhir.dstu2.utils.client.FHIRToolingClient;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.exceptions.TerminologyServiceException;
|
||||
import org.hl7.fhir.r5.model.Bundle;
|
||||
import org.hl7.fhir.r5.model.CanonicalResource;
|
||||
import org.hl7.fhir.r5.model.CapabilityStatement;
|
||||
import org.hl7.fhir.r5.model.Parameters;
|
||||
import org.hl7.fhir.r5.model.TerminologyCapabilities;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
import org.hl7.fhir.r5.terminologies.TerminologyClient;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
public class TerminologyClientR2 implements TerminologyClient {
|
||||
|
||||
|
@ -124,5 +128,27 @@ public class TerminologyClientR2 implements TerminologyClient {
|
|||
return (Bundle) VersionConvertor_10_50.convertResource(client.transaction((org.hl7.fhir.dstu2.model.Bundle) VersionConvertor_10_50.convertResource(batch)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CanonicalResource read(String type, String id) {
|
||||
Class<Resource> t;
|
||||
try {
|
||||
t = (Class<Resource>) Class.forName("org.hl7.fhir.dstu2.model."+type);// todo: do we have to deal with any resource renaming? Use cases are limited...
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new FHIRException("Unable to fetch resources of type "+type+" in R2");
|
||||
}
|
||||
org.hl7.fhir.dstu2.model.Resource r2 = client.read(t, id);
|
||||
if (r2 == null) {
|
||||
throw new FHIRException("Unable to fetch resource "+Utilities.pathURL(getAddress(), type, id));
|
||||
}
|
||||
org.hl7.fhir.r5.model.Resource r5 = VersionConvertor_10_50.convertResource(r2);
|
||||
if (r5 != null) {
|
||||
throw new FHIRException("Unable to convert resource "+Utilities.pathURL(getAddress(), type, id)+" to R5 (internal representation)");
|
||||
}
|
||||
if (!(r5 instanceof CanonicalResource)) {
|
||||
throw new FHIRException("Unable to convert resource "+Utilities.pathURL(getAddress(), type, id)+" to R5 canonical resource (internal representation)");
|
||||
}
|
||||
return (CanonicalResource) r5;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -34,17 +34,21 @@ package org.hl7.fhir.convertors.txClient;
|
|||
import java.net.URISyntaxException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hl7.fhir.convertors.VersionConvertor_10_50;
|
||||
import org.hl7.fhir.convertors.VersionConvertor_30_50;
|
||||
import org.hl7.fhir.convertors.VersionConvertor_40_50;
|
||||
import org.hl7.fhir.dstu3.model.Resource;
|
||||
import org.hl7.fhir.dstu3.utils.client.FHIRToolingClient;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.model.Bundle;
|
||||
import org.hl7.fhir.r5.model.CanonicalResource;
|
||||
import org.hl7.fhir.r5.model.CapabilityStatement;
|
||||
import org.hl7.fhir.r5.model.Parameters;
|
||||
import org.hl7.fhir.r5.model.TerminologyCapabilities;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
import org.hl7.fhir.r5.terminologies.TerminologyClient;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
public class TerminologyClientR3 implements TerminologyClient {
|
||||
|
||||
|
@ -124,5 +128,27 @@ public class TerminologyClientR3 implements TerminologyClient {
|
|||
return (Bundle) VersionConvertor_30_50.convertResource(client.transaction((org.hl7.fhir.dstu3.model.Bundle) VersionConvertor_30_50.convertResource(batch, false)), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CanonicalResource read(String type, String id) {
|
||||
Class<Resource> t;
|
||||
try {
|
||||
t = (Class<Resource>) Class.forName("org.hl7.fhir.dstu3.model."+type);// todo: do we have to deal with any resource renaming? Use cases are limited...
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new FHIRException("Unable to fetch resources of type "+type+" in R2");
|
||||
}
|
||||
org.hl7.fhir.dstu3.model.Resource r3 = client.read(t, id);
|
||||
if (r3 == null) {
|
||||
throw new FHIRException("Unable to fetch resource "+Utilities.pathURL(getAddress(), type, id));
|
||||
}
|
||||
org.hl7.fhir.r5.model.Resource r5 = VersionConvertor_30_50.convertResource(r3, false);
|
||||
if (r5 != null) {
|
||||
throw new FHIRException("Unable to convert resource "+Utilities.pathURL(getAddress(), type, id)+" to R5 (internal representation)");
|
||||
}
|
||||
if (!(r5 instanceof CanonicalResource)) {
|
||||
throw new FHIRException("Unable to convert resource "+Utilities.pathURL(getAddress(), type, id)+" to R5 canonical resource (internal representation)");
|
||||
}
|
||||
return (CanonicalResource) r5;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -34,17 +34,21 @@ package org.hl7.fhir.convertors.txClient;
|
|||
import java.net.URISyntaxException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hl7.fhir.convertors.VersionConvertor_30_50;
|
||||
import org.hl7.fhir.convertors.VersionConvertor_40_50;
|
||||
import org.hl7.fhir.convertors.conv40_50.TerminologyCapabilities40_50;
|
||||
import org.hl7.fhir.r4.model.Resource;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r4.utils.client.FHIRToolingClient;
|
||||
import org.hl7.fhir.r5.model.Bundle;
|
||||
import org.hl7.fhir.r5.model.CanonicalResource;
|
||||
import org.hl7.fhir.r5.model.CapabilityStatement;
|
||||
import org.hl7.fhir.r5.model.Parameters;
|
||||
import org.hl7.fhir.r5.model.TerminologyCapabilities;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
import org.hl7.fhir.r5.terminologies.TerminologyClient;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
public class TerminologyClientR4 implements TerminologyClient {
|
||||
|
||||
|
@ -124,4 +128,26 @@ public class TerminologyClientR4 implements TerminologyClient {
|
|||
return (Bundle) VersionConvertor_40_50.convertResource(client.transaction((org.hl7.fhir.r4.model.Bundle) VersionConvertor_40_50.convertResource(batch)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CanonicalResource read(String type, String id) {
|
||||
Class<Resource> t;
|
||||
try {
|
||||
t = (Class<Resource>) Class.forName("org.hl7.fhir.r4.model."+type);// todo: do we have to deal with any resource renaming? Use cases are limited...
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new FHIRException("Unable to fetch resources of type "+type+" in R2");
|
||||
}
|
||||
org.hl7.fhir.r4.model.Resource r4 = client.read(t, id);
|
||||
if (r4 == null) {
|
||||
throw new FHIRException("Unable to fetch resource "+Utilities.pathURL(getAddress(), type, id));
|
||||
}
|
||||
org.hl7.fhir.r5.model.Resource r5 = VersionConvertor_40_50.convertResource(r4);
|
||||
if (r5 != null) {
|
||||
throw new FHIRException("Unable to convert resource "+Utilities.pathURL(getAddress(), type, id)+" to R5 (internal representation)");
|
||||
}
|
||||
if (!(r5 instanceof CanonicalResource)) {
|
||||
throw new FHIRException("Unable to convert resource "+Utilities.pathURL(getAddress(), type, id)+" to R5 canonical resource (internal representation)");
|
||||
}
|
||||
return (CanonicalResource) r5;
|
||||
}
|
||||
|
||||
}
|
|
@ -34,8 +34,11 @@ package org.hl7.fhir.convertors.txClient;
|
|||
import java.net.URISyntaxException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hl7.fhir.convertors.VersionConvertor_40_50;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.model.Resource;
|
||||
import org.hl7.fhir.r5.model.Bundle;
|
||||
import org.hl7.fhir.r5.model.CanonicalResource;
|
||||
import org.hl7.fhir.r5.model.CapabilityStatement;
|
||||
import org.hl7.fhir.r5.model.CodeSystem;
|
||||
import org.hl7.fhir.r5.model.Parameters;
|
||||
|
@ -44,6 +47,7 @@ import org.hl7.fhir.r5.model.ValueSet;
|
|||
import org.hl7.fhir.r5.terminologies.TerminologyClient;
|
||||
import org.hl7.fhir.r5.utils.client.FHIRToolingClient;
|
||||
import org.hl7.fhir.utilities.ToolingClientLogger;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
public class TerminologyClientR5 implements TerminologyClient {
|
||||
|
||||
|
@ -116,4 +120,22 @@ public class TerminologyClientR5 implements TerminologyClient {
|
|||
return client.transaction(batch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CanonicalResource read(String type, String id) {
|
||||
Class<Resource> t;
|
||||
try {
|
||||
t = (Class<Resource>) Class.forName("org.hl7.fhir.r5.model."+type);// todo: do we have to deal with any resource renaming? Use cases are limited...
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new FHIRException("Unable to fetch resources of type "+type+" in R5");
|
||||
}
|
||||
org.hl7.fhir.r5.model.Resource r5 = client.read(t, id);
|
||||
if (r5 != null) {
|
||||
throw new FHIRException("Unable to convert resource "+Utilities.pathURL(getAddress(), type, id)+" to R5 (internal representation)");
|
||||
}
|
||||
if (!(r5 instanceof CanonicalResource)) {
|
||||
throw new FHIRException("Unable to convert resource "+Utilities.pathURL(getAddress(), type, id)+" to R5 canonical resource (internal representation)");
|
||||
}
|
||||
return (CanonicalResource) r5;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>org.hl7.fhir.core</artifactId>
|
||||
<version>5.1.23-SNAPSHOT</version>
|
||||
<version>5.2.8-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -516,7 +516,7 @@ public class FHIRToolingClient {
|
|||
return p_out;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
handleException("Error performing operation '"+name+"' with parameters " + ps, e);
|
||||
handleException("Error performing operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>org.hl7.fhir.core</artifactId>
|
||||
<version>5.1.23-SNAPSHOT</version>
|
||||
<version>5.2.8-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -517,7 +517,7 @@ public class FHIRToolingClient {
|
|||
return p_out;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
handleException("Error performing operation '"+name+"' with parameters " + ps, e);
|
||||
handleException("Error performing operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>org.hl7.fhir.core</artifactId>
|
||||
<version>5.1.23-SNAPSHOT</version>
|
||||
<version>5.2.8-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -504,7 +504,7 @@ public class FHIRToolingClient {
|
|||
return p_out;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
handleException("Error performing operation '"+name+"' with parameters " + ps, e);
|
||||
handleException("Error performing operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>org.hl7.fhir.core</artifactId>
|
||||
<version>5.1.23-SNAPSHOT</version>
|
||||
<version>5.2.8-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -507,7 +507,7 @@ public class FHIRToolingClient {
|
|||
return p_out;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
handleException("Error performing operation '"+name+"' with parameters " + ps, e);
|
||||
handleException("Error performing operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>org.hl7.fhir.core</artifactId>
|
||||
<version>5.1.23-SNAPSHOT</version>
|
||||
<version>5.2.8-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>
|
||||
|
|
|
@ -4141,11 +4141,13 @@ public class ProfileUtilities extends TranslatingUtilities {
|
|||
first = false;
|
||||
}
|
||||
}
|
||||
if (first)
|
||||
if (first) {
|
||||
c.getPieces().add(gen.new Piece(null, "Any", null));
|
||||
}
|
||||
|
||||
if (ADD_REFERENCE_TO_TABLE)
|
||||
if (ADD_REFERENCE_TO_TABLE) {
|
||||
c.getPieces().add(gen.new Piece(null, ")", null));
|
||||
}
|
||||
|
||||
} else {
|
||||
StructureDefinition sd = context.fetchTypeDefinition(t);
|
||||
|
@ -5617,6 +5619,7 @@ public class ProfileUtilities extends TranslatingUtilities {
|
|||
return;
|
||||
|
||||
Map<String, String> idList = new HashMap<String, String>();
|
||||
Map<String, String> replacedIds = new HashMap<String, String>();
|
||||
|
||||
SliceList sliceInfo = new SliceList();
|
||||
// first pass, update the element ids
|
||||
|
@ -5643,6 +5646,9 @@ public class ProfileUtilities extends TranslatingUtilities {
|
|||
}
|
||||
}
|
||||
String bs = b.toString();
|
||||
if (ed.hasId()) {
|
||||
replacedIds.put(ed.getId(), ed.getPath());
|
||||
}
|
||||
ed.setId(bs);
|
||||
if (idList.containsKey(bs)) {
|
||||
if (exception || messages == null) {
|
||||
|
@ -5653,7 +5659,11 @@ public class ProfileUtilities extends TranslatingUtilities {
|
|||
idList.put(bs, ed.getPath());
|
||||
if (ed.hasContentReference() && ed.getContentReference().startsWith("#")) {
|
||||
String s = ed.getContentReference();
|
||||
ed.setContentReference("http://hl7.org/fhir/StructureDefinition/"+type+s);
|
||||
if (replacedIds.containsKey(s.substring(1))) {
|
||||
ed.setContentReference("http://hl7.org/fhir/StructureDefinition/"+type+"#"+replacedIds.get(s.substring(1)));
|
||||
} else {
|
||||
ed.setContentReference("http://hl7.org/fhir/StructureDefinition/"+type+s);
|
||||
}
|
||||
}
|
||||
}
|
||||
// second path - fix up any broken path based id references
|
||||
|
|
|
@ -193,6 +193,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
private boolean allowLoadingDuplicates;
|
||||
|
||||
protected TerminologyClient txClient;
|
||||
private Set<String> codeSystemsUsed = new HashSet<>();
|
||||
protected ToolingClientLogger txLog;
|
||||
private TerminologyCapabilities txcaps;
|
||||
private boolean canRunWithoutTerminology;
|
||||
|
@ -597,10 +598,17 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
Parameters p = expParameters.copy();
|
||||
p.setParameter("includeDefinition", false);
|
||||
p.setParameter("excludeNested", !hierarchical);
|
||||
if (isTxCaching && cacheId != null) {
|
||||
|
||||
boolean cached = addDependentResources(p, vs);
|
||||
if (cached) {
|
||||
p.addParameter().setName("cache-id").setValue(new StringType(cacheId));
|
||||
}
|
||||
addDependentResources(p, vs);
|
||||
for (ConceptSetComponent incl : vs.getCompose().getInclude()) {
|
||||
codeSystemsUsed.add(incl.getSystem());
|
||||
}
|
||||
for (ConceptSetComponent incl : vs.getCompose().getExclude()) {
|
||||
codeSystemsUsed.add(incl.getSystem());
|
||||
}
|
||||
|
||||
if (noTerminologyServer) {
|
||||
return new ValueSetExpansionOutcome(formatMessage(I18nConstants.ERROR_EXPANDING_VALUESET_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE);
|
||||
|
@ -640,6 +648,12 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
if (!vs.hasUrl()) {
|
||||
throw new Error(formatMessage(I18nConstants.NO_VALUE_SET_IN_URL));
|
||||
}
|
||||
for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
|
||||
codeSystemsUsed.add(inc.getSystem());
|
||||
}
|
||||
for (ConceptSetComponent inc : vs.getCompose().getExclude()) {
|
||||
codeSystemsUsed.add(inc.getSystem());
|
||||
}
|
||||
|
||||
CacheToken cacheToken = txCache.generateExpandToken(vs, heirarchical);
|
||||
ValueSetExpansionOutcome res;
|
||||
|
@ -672,10 +686,10 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
}
|
||||
|
||||
// if that failed, we try to expand on the server
|
||||
if (isTxCaching && cacheId != null) {
|
||||
if (addDependentResources(p, vs)) {
|
||||
p.addParameter().setName("cache-id").setValue(new StringType(cacheId));
|
||||
}
|
||||
addDependentResources(p, vs);
|
||||
|
||||
if (noTerminologyServer) {
|
||||
return new ValueSetExpansionOutcome(formatMessage(I18nConstants.ERROR_EXPANDING_VALUESET_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE, allErrors);
|
||||
}
|
||||
|
@ -736,6 +750,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
// 3rd pass: hit the server
|
||||
for (CodingValidationRequest t : codes) {
|
||||
t.setCacheToken(txCache != null ? txCache.generateValidationToken(options, t.getCoding(), vs) : null);
|
||||
codeSystemsUsed.add(t.getCoding().getSystem());
|
||||
if (txCache != null) {
|
||||
t.setResult(txCache.getValidation(t.getCacheToken()));
|
||||
}
|
||||
|
@ -808,6 +823,9 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
BundleEntryComponent r = resp.getEntry().get(i);
|
||||
if (r.getResource() instanceof Parameters) {
|
||||
t.setResult(processValidationResult((Parameters) r.getResource()));
|
||||
if (txCache != null) {
|
||||
txCache.cacheValidation(t.getCacheToken(), t.getResult(), TerminologyCache.PERMANENT);
|
||||
}
|
||||
} else {
|
||||
t.setResult(new ValidationResult(IssueSeverity.ERROR, getResponseText(r.getResource())).setTxLink(txLog == null ? null : txLog.getLastId()));
|
||||
}
|
||||
|
@ -828,6 +846,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
options = ValidationOptions.defaults();
|
||||
}
|
||||
|
||||
codeSystemsUsed.add(code.getSystem());
|
||||
CacheToken cacheToken = txCache != null ? txCache.generateValidationToken(options, code, vs) : null;
|
||||
ValidationResult res = null;
|
||||
if (txCache != null) {
|
||||
|
@ -902,6 +921,9 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
if (res != null) {
|
||||
return res;
|
||||
}
|
||||
for (Coding c : code.getCoding()) {
|
||||
codeSystemsUsed.add(c.getSystem());
|
||||
}
|
||||
|
||||
if (options.isUseClient()) {
|
||||
// ok, first we try to validate locally
|
||||
|
@ -939,8 +961,14 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
}
|
||||
|
||||
private ValidationResult validateOnServer(ValueSet vs, Parameters pin) throws FHIRException {
|
||||
if (isTxCaching && cacheId != null) {
|
||||
pin.addParameter().setName("cache-id").setValue(new StringType(cacheId));
|
||||
boolean cache = false;
|
||||
if (vs != null) {
|
||||
for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
|
||||
codeSystemsUsed.add(inc.getSystem());
|
||||
}
|
||||
for (ConceptSetComponent inc : vs.getCompose().getExclude()) {
|
||||
codeSystemsUsed.add(inc.getSystem());
|
||||
}
|
||||
}
|
||||
if (vs != null) {
|
||||
if (isTxCaching && cacheId != null && cached.contains(vs.getUrl()+"|"+vs.getVersion())) {
|
||||
|
@ -949,8 +977,12 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
pin.addParameter().setName("valueSet").setResource(vs);
|
||||
cached.add(vs.getUrl()+"|"+vs.getVersion());
|
||||
}
|
||||
cache = true;
|
||||
addDependentResources(pin, vs);
|
||||
}
|
||||
if (cache) {
|
||||
pin.addParameter().setName("cache-id").setValue(new StringType(cacheId));
|
||||
}
|
||||
for (ParametersParameterComponent pp : pin.getParameter()) {
|
||||
if (pp.getName().equals("profile")) {
|
||||
throw new Error(formatMessage(I18nConstants.CAN_ONLY_SPECIFY_PROFILE_IN_THE_CONTEXT));
|
||||
|
@ -975,22 +1007,26 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
return processValidationResult(pOut);
|
||||
}
|
||||
|
||||
private void addDependentResources(Parameters pin, ValueSet vs) {
|
||||
private boolean addDependentResources(Parameters pin, ValueSet vs) {
|
||||
boolean cache = false;
|
||||
for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
|
||||
addDependentResources(pin, inc);
|
||||
cache = addDependentResources(pin, inc) || cache;
|
||||
}
|
||||
for (ConceptSetComponent inc : vs.getCompose().getExclude()) {
|
||||
addDependentResources(pin, inc);
|
||||
cache = addDependentResources(pin, inc) || cache;
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
private void addDependentResources(Parameters pin, ConceptSetComponent inc) {
|
||||
private boolean addDependentResources(Parameters pin, ConceptSetComponent inc) {
|
||||
boolean cache = false;
|
||||
for (CanonicalType c : inc.getValueSet()) {
|
||||
ValueSet vs = fetchResource(ValueSet.class, c.getValue());
|
||||
if (vs != null) {
|
||||
if (isTxCaching && cacheId == null || !cached.contains(vs.getVUrl())) {
|
||||
pin.addParameter().setName("tx-resource").setResource(vs);
|
||||
cached.add(vs.getVUrl());
|
||||
cache = true;
|
||||
}
|
||||
addDependentResources(pin, vs);
|
||||
}
|
||||
|
@ -1000,9 +1036,11 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
if (isTxCaching && cacheId == null || !cached.contains(cs.getVUrl())) {
|
||||
pin.addParameter().setName("tx-resource").setResource(cs);
|
||||
cached.add(cs.getVUrl());
|
||||
cache = true;
|
||||
}
|
||||
// todo: supplements
|
||||
}
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
public ValidationResult processValidationResult(Parameters pOut) {
|
||||
|
@ -1932,5 +1970,22 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
guides.size() + capstmts.size() + searchParameters.size() + questionnaires.size() + operations.size() + plans.size() + systems.size();
|
||||
}
|
||||
|
||||
public Set<String> getCodeSystemsUsed() {
|
||||
return codeSystemsUsed ;
|
||||
}
|
||||
|
||||
public String getSpecUrl() {
|
||||
String v = getVersion();
|
||||
switch (VersionUtilities.getMajMin(v)) {
|
||||
case "1.0" : return "http://hl7.org/fhir/DSTU1";
|
||||
case "1.4" : return "http://hl7.org/fhir/DSTU2";
|
||||
case "3.0" : return "http://hl7.org/fhir/STU3";
|
||||
case "4.0" : return "http://hl7.org/fhir/R4";
|
||||
case "4.5" : return "http://build.fhir.org";
|
||||
case "5.0" : return "http://build.fhir.org";
|
||||
default:
|
||||
return "http://hl7.org/fhir";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -222,7 +222,12 @@ public interface IWorkerContext {
|
|||
* Get the versions of the definitions loaded in context
|
||||
* @return
|
||||
*/
|
||||
public String getVersion();
|
||||
public String getVersion();
|
||||
|
||||
/**
|
||||
* return the link to the base of the specification for the loaded version e.g. http://hl7.org/fhir/R4
|
||||
*/
|
||||
public String getSpecUrl();
|
||||
|
||||
// get the UCUM service (might not be available)
|
||||
public UcumService getUcumService();
|
||||
|
@ -710,7 +715,7 @@ public interface IWorkerContext {
|
|||
public ILoggingService getLogger();
|
||||
|
||||
public boolean isNoTerminologyServer();
|
||||
|
||||
public Set<String> getCodeSystemsUsed();
|
||||
public TranslationServices translator();
|
||||
public List<StructureMap> listTransforms();
|
||||
public StructureMap getTransform(String url);
|
||||
|
|
|
@ -76,6 +76,7 @@ import org.hl7.fhir.r5.model.StructureMap.StructureMapModelMode;
|
|||
import org.hl7.fhir.r5.model.StructureMap.StructureMapStructureComponent;
|
||||
import org.hl7.fhir.r5.terminologies.TerminologyClient;
|
||||
import org.hl7.fhir.r5.utils.IResourceValidator;
|
||||
import org.hl7.fhir.r5.utils.XVerExtensionManager;
|
||||
import org.hl7.fhir.utilities.CSFileInputStream;
|
||||
import org.hl7.fhir.utilities.TextFile;
|
||||
import org.hl7.fhir.utilities.TimeTracker;
|
||||
|
@ -138,7 +139,8 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon
|
|||
}
|
||||
|
||||
public interface IValidatorFactory {
|
||||
IResourceValidator makeValidator(IWorkerContext ctxts) throws FHIRException;
|
||||
IResourceValidator makeValidator(IWorkerContext ctxt) throws FHIRException;
|
||||
IResourceValidator makeValidator(IWorkerContext ctxts, XVerExtensionManager xverManager) throws FHIRException;
|
||||
}
|
||||
|
||||
private Questionnaire questionnaire;
|
||||
|
@ -149,6 +151,7 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon
|
|||
private boolean progress;
|
||||
private List<String> loadedPackages = new ArrayList<String>();
|
||||
private boolean canNoTS;
|
||||
private XVerExtensionManager xverManager;
|
||||
|
||||
public SimpleWorkerContext() throws FileNotFoundException, IOException, FHIRException {
|
||||
super();
|
||||
|
@ -243,19 +246,15 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon
|
|||
}
|
||||
|
||||
public static SimpleWorkerContext fromClassPath(String name) throws IOException, FHIRException {
|
||||
InputStream s = SimpleWorkerContext.class.getResourceAsStream("/"+name);
|
||||
SimpleWorkerContext res = new SimpleWorkerContext();
|
||||
res.loadFromStream(s, null);
|
||||
return res;
|
||||
return fromClassPath(name, false);
|
||||
}
|
||||
public static SimpleWorkerContext fromClassPath(String name, boolean allowDuplicates) throws IOException, FHIRException {
|
||||
InputStream s = SimpleWorkerContext.class.getResourceAsStream("/" + name);
|
||||
SimpleWorkerContext res = new SimpleWorkerContext();
|
||||
res.setAllowLoadingDuplicates(allowDuplicates);
|
||||
res.loadFromStream(s, null);
|
||||
return res;
|
||||
}
|
||||
|
||||
// public static SimpleWorkerContext fromDefinitions(Map<String, byte[]> source) throws IOException, FHIRException {
|
||||
// SimpleWorkerContext res = new SimpleWorkerContext();
|
||||
// for (String name : source.keySet()) {
|
||||
// res.loadDefinitionItem(name, new ByteArrayInputStream(source.get(name)), null, null);
|
||||
// }
|
||||
// return res;
|
||||
// }
|
||||
|
||||
public static SimpleWorkerContext fromDefinitions(Map<String, byte[]> source, IContextResourceLoader loader, PackageVersion pi) throws FileNotFoundException, IOException, FHIRException {
|
||||
SimpleWorkerContext res = new SimpleWorkerContext();
|
||||
|
@ -539,7 +538,7 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon
|
|||
public IResourceValidator newValidator() throws FHIRException {
|
||||
if (validatorFactory == null)
|
||||
throw new Error(formatMessage(I18nConstants.NO_VALIDATOR_CONFIGURED));
|
||||
return validatorFactory.makeValidator(this);
|
||||
return validatorFactory.makeValidator(this, xverManager);
|
||||
}
|
||||
|
||||
|
||||
|
@ -770,6 +769,10 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon
|
|||
ProfileUtilities pu = new ProfileUtilities(this, msgs, this);
|
||||
pu.setAutoFixSliceNames(true);
|
||||
pu.setThrowException(false);
|
||||
if (xverManager == null) {
|
||||
xverManager = new XVerExtensionManager(this);
|
||||
}
|
||||
pu.setXver(xverManager);
|
||||
if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) {
|
||||
pu.sortDifferential(sd, p, p.getUrl(), errors, true);
|
||||
}
|
||||
|
@ -812,6 +815,10 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon
|
|||
return loadedPackages.contains(id+"#"+ver);
|
||||
}
|
||||
|
||||
public boolean hasPackage(String idAndver) {
|
||||
return loadedPackages.contains(idAndver);
|
||||
}
|
||||
|
||||
public void setClock(TimeTracker tt) {
|
||||
clock = tt;
|
||||
}
|
||||
|
@ -823,6 +830,13 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon
|
|||
public void setCanNoTS(boolean canNoTS) {
|
||||
this.canNoTS = canNoTS;
|
||||
}
|
||||
|
||||
public XVerExtensionManager getXVer() {
|
||||
if (xverManager == null) {
|
||||
xverManager = new XVerExtensionManager(this);
|
||||
}
|
||||
return xverManager;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -109,17 +109,31 @@ public class Property {
|
|||
return definition.getPath();
|
||||
ElementDefinition ed = definition;
|
||||
if (definition.hasContentReference()) {
|
||||
if (!definition.getContentReference().startsWith("#"))
|
||||
throw new Error("not handled yet");
|
||||
String url = null;
|
||||
String path = definition.getContentReference();
|
||||
if (!path.startsWith("#")) {
|
||||
if (path.contains("#")) {
|
||||
url = path.substring(0, path.indexOf("#"));
|
||||
path = path.substring(path.indexOf("#")+1);
|
||||
} else {
|
||||
throw new Error("Illegal content reference '"+path+"'");
|
||||
}
|
||||
} else {
|
||||
path = path.substring(1);
|
||||
}
|
||||
StructureDefinition sd = (url == null || url.equals(structure.getUrl())) ? structure : context.fetchResource(StructureDefinition.class, url, structure);
|
||||
if (sd == null) {
|
||||
throw new Error("Unknown Type in content reference '"+path+"'");
|
||||
}
|
||||
boolean found = false;
|
||||
for (ElementDefinition d : structure.getSnapshot().getElement()) {
|
||||
if (d.hasId() && d.getId().equals(definition.getContentReference().substring(1))) {
|
||||
for (ElementDefinition d : sd.getSnapshot().getElement()) {
|
||||
if (d.hasId() && d.getId().equals(path)) {
|
||||
found = true;
|
||||
ed = d;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
throw new Error("Unable to resolve "+definition.getContentReference()+" at "+definition.getPath()+" on "+structure.getUrl());
|
||||
throw new Error("Unable to resolve "+definition.getContentReference()+" at "+definition.getPath()+" on "+sd.getUrl());
|
||||
}
|
||||
if (ed.getType().size() == 0)
|
||||
return null;
|
||||
|
|
|
@ -899,16 +899,12 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
|
|||
lowRight = lowRight - (14 * DateUtils.MILLIS_PER_HOUR);
|
||||
highRight = highRight + (14 * DateUtils.MILLIS_PER_HOUR);
|
||||
}
|
||||
System.out.print("["+((lowLeft / 1000) - 130000000)+"-"+((highLeft / 1000) - 130000000)+"] vs ["+((lowRight / 1000) - 130000000)+"-"+((highRight / 1000) - 130000000)+"] = ");
|
||||
if (highRight < lowLeft) {
|
||||
System.out.println("false");
|
||||
return false;
|
||||
}
|
||||
if (highLeft < lowRight) {
|
||||
System.out.println("false");
|
||||
return false;
|
||||
}
|
||||
System.out.println("true");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1000,6 +996,14 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
|
|||
return def;
|
||||
}
|
||||
|
||||
if (left.getSecond() < right.getSecond()) {
|
||||
return -1;
|
||||
} else if (left.getSecond() > right.getSecond()) {
|
||||
return 1;
|
||||
} else if (left.getPrecision() == TemporalPrecisionEnum.SECOND && right.getPrecision() == TemporalPrecisionEnum.SECOND) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (left.getSecondsMilli() < right.getSecondsMilli()) {
|
||||
return -1;
|
||||
} else if (left.getSecondsMilli() > right.getSecondsMilli()) {
|
||||
|
|
|
@ -63,7 +63,7 @@ public class BundleRenderer extends ResourceRenderer {
|
|||
List<BaseWrapper> entries = b.children("entry");
|
||||
if ("document".equals(b.get("type").primitiveValue())) {
|
||||
if (entries.isEmpty() || (entries.get(0).has("resource") && "Composition".equals(entries.get(0).get("resource").fhirType())))
|
||||
throw new FHIRException("Invalid document - first entry is not a Composition");
|
||||
throw new FHIRException("Invalid document '"+b.getId()+"' - first entry is not a Composition ('"+entries.get(0).get("resource").fhirType()+"')");
|
||||
return renderDocument(x, b, entries);
|
||||
} else if ("collection".equals(b.get("type").primitiveValue()) && allEntriesAreHistoryProvenance(entries)) {
|
||||
// nothing
|
||||
|
@ -399,4 +399,16 @@ public class BundleRenderer extends ResourceRenderer {
|
|||
return "??";
|
||||
}
|
||||
|
||||
public boolean canRender(Bundle b) {
|
||||
for (BundleEntryComponent be : b.getEntry()) {
|
||||
if (be.hasResource()) {
|
||||
ResourceRenderer rr = RendererFactory.factory(be.getResource(), context);
|
||||
if (!rr.canRender(be.getResource())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ public class CapabilityStatementRenderer extends ResourceRenderer {
|
|||
CapabilityStatementRestComponent rest = conf.getRest().get(0);
|
||||
XhtmlNode t = x.table(null);
|
||||
addTableRow(t, "Mode", rest.getMode().toString());
|
||||
addTableRow(t, "Description", rest.getDocumentation());
|
||||
addMarkdown(addTableRow(t, "Description"), rest.getDocumentation());
|
||||
|
||||
addTableRow(t, "Transaction", showOp(rest, SystemRestfulInteraction.TRANSACTION));
|
||||
addTableRow(t, "System History", showOp(rest, SystemRestfulInteraction.HISTORYSYSTEM));
|
||||
|
@ -143,7 +143,12 @@ public class CapabilityStatementRenderer extends ResourceRenderer {
|
|||
return "";
|
||||
}
|
||||
|
||||
|
||||
private XhtmlNode addTableRow(XhtmlNode t, String name) {
|
||||
XhtmlNode tr = t.tr();
|
||||
tr.td().addText(name);
|
||||
return tr.td();
|
||||
}
|
||||
|
||||
private void addTableRow(XhtmlNode t, String name, String value) {
|
||||
XhtmlNode tr = t.tr();
|
||||
tr.td().addText(name);
|
||||
|
|
|
@ -57,6 +57,7 @@ import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
|
|||
import org.hl7.fhir.utilities.MarkDownProcessor;
|
||||
import org.hl7.fhir.utilities.MarkDownProcessor.Dialect;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.VersionUtilities;
|
||||
import org.hl7.fhir.utilities.validation.ValidationOptions;
|
||||
import org.hl7.fhir.utilities.xhtml.NodeType;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
|
||||
|
@ -219,11 +220,18 @@ public class DataRenderer extends Renderer {
|
|||
private boolean isCanonical(String path) {
|
||||
if (!path.endsWith(".url"))
|
||||
return false;
|
||||
StructureDefinition sd = getContext().getWorker().fetchTypeDefinition(path.substring(0, path.length()-4));
|
||||
String t = path.substring(0, path.length()-4);
|
||||
StructureDefinition sd = getContext().getWorker().fetchTypeDefinition(t);
|
||||
if (sd == null)
|
||||
return false;
|
||||
if (Utilities.existsInList(path.substring(0, path.length()-4), "CapabilityStatement", "StructureDefinition", "ImplementationGuide", "SearchParameter", "MessageDefinition", "OperationDefinition", "CompartmentDefinition", "StructureMap", "GraphDefinition",
|
||||
"ExampleScenario", "CodeSystem", "ValueSet", "ConceptMap", "NamingSystem", "TerminologyCapabilities"))
|
||||
if (Utilities.existsInList(t, VersionUtilities.getCanonicalResourceNames(getContext().getWorker().getVersion()))) {
|
||||
return true;
|
||||
}
|
||||
if (Utilities.existsInList(t,
|
||||
"ActivityDefinition", "CapabilityStatement", "CapabilityStatement2", "ChargeItemDefinition", "Citation", "CodeSystem",
|
||||
"CompartmentDefinition", "ConceptMap", "ConditionDefinition", "EventDefinition", "Evidence", "EvidenceReport", "EvidenceVariable",
|
||||
"ExampleScenario", "GraphDefinition", "ImplementationGuide", "Library", "Measure", "MessageDefinition", "NamingSystem", "PlanDefinition"
|
||||
))
|
||||
return true;
|
||||
return sd.getBaseDefinitionElement().hasExtension("http://hl7.org/fhir/StructureDefinition/structuredefinition-codegen-super");
|
||||
}
|
||||
|
@ -381,26 +389,20 @@ public class DataRenderer extends Renderer {
|
|||
}
|
||||
|
||||
protected void renderUri(XhtmlNode x, UriType uri, String path, String id) {
|
||||
String url = uri.getValue();
|
||||
if (isCanonical(path)) {
|
||||
CanonicalResource mr = getContext().getWorker().fetchResource(null, url);
|
||||
if (mr != null) {
|
||||
if (path.startsWith(mr.fhirType()+".") && mr.getId().equals(id)) {
|
||||
url = null; // don't link to self whatever
|
||||
} else if (mr.hasUserData("path"))
|
||||
url = mr.getUserString("path");
|
||||
} else if (!getContext().isCanonicalUrlsAsLinks())
|
||||
url = null;
|
||||
}
|
||||
if (url == null) {
|
||||
x.b().tx(uri.getValue());
|
||||
} else if (uri.getValue().startsWith("mailto:")) {
|
||||
x.ah(uri.getValue()).addText(uri.getValue().substring(7));
|
||||
x.code().tx(uri.getValue());
|
||||
} else {
|
||||
if (uri.getValue().contains("|")) {
|
||||
x.ah(uri.getValue().substring(0, uri.getValue().indexOf("|"))).addText(uri.getValue());
|
||||
String url = uri.getValue();
|
||||
if (url == null) {
|
||||
x.b().tx(uri.getValue());
|
||||
} else if (uri.getValue().startsWith("mailto:")) {
|
||||
x.ah(uri.getValue()).addText(uri.getValue().substring(7));
|
||||
} else {
|
||||
x.ah(uri.getValue()).addText(uri.getValue());
|
||||
if (uri.getValue().contains("|")) {
|
||||
x.ah(uri.getValue().substring(0, uri.getValue().indexOf("|"))).addText(uri.getValue());
|
||||
} else {
|
||||
x.ah(uri.getValue()).addText(uri.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -939,5 +939,8 @@ public class ProfileDrivenRenderer extends ResourceRenderer {
|
|||
return path.substring(path.lastIndexOf(".")+1);
|
||||
}
|
||||
|
||||
public boolean canRender(Resource resource) {
|
||||
return context.getWorker().getResourceNames().contains(resource.fhirType());
|
||||
}
|
||||
|
||||
}
|
|
@ -366,4 +366,8 @@ public abstract class ResourceRenderer extends DataRenderer {
|
|||
return fullUrl.replace(":", "-");
|
||||
}
|
||||
|
||||
public boolean canRender(Resource resource) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -35,6 +35,7 @@ import java.util.Map;
|
|||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.model.Bundle;
|
||||
import org.hl7.fhir.r5.model.CanonicalResource;
|
||||
import org.hl7.fhir.r5.model.CapabilityStatement;
|
||||
import org.hl7.fhir.r5.model.Parameters;
|
||||
import org.hl7.fhir.r5.model.TerminologyCapabilities;
|
||||
|
@ -54,4 +55,5 @@ public interface TerminologyClient {
|
|||
public CapabilityStatement getCapabilitiesStatementQuick() throws FHIRException;
|
||||
public Parameters lookupCode(Map<String, String> params) throws FHIRException;
|
||||
public Bundle validateBatch(Bundle batch);
|
||||
public CanonicalResource read(String type, String id);
|
||||
}
|
|
@ -34,6 +34,7 @@ package org.hl7.fhir.r5.utils;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
|
@ -42,6 +43,7 @@ import org.hl7.fhir.exceptions.FHIRException;
|
|||
import org.hl7.fhir.exceptions.FHIRFormatError;
|
||||
import org.hl7.fhir.r5.elementmodel.Element;
|
||||
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
|
||||
import org.hl7.fhir.r5.model.CanonicalResource;
|
||||
import org.hl7.fhir.r5.model.StructureDefinition;
|
||||
import org.hl7.fhir.r5.utils.IResourceValidator.BundleValidationRule;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
@ -102,6 +104,7 @@ public interface IResourceValidator {
|
|||
void recordProfileUsage(StructureDefinition profile, Object appContext, Element element);
|
||||
}
|
||||
|
||||
|
||||
public interface IValidatorResourceFetcher {
|
||||
|
||||
Element fetch(Object appContext, String url) throws FHIRFormatError, DefinitionException, FHIRException, IOException;
|
||||
|
@ -111,6 +114,29 @@ public interface IResourceValidator {
|
|||
byte[] fetchRaw(String url) throws MalformedURLException, IOException; // for attachment checking
|
||||
|
||||
void setLocale(Locale locale);
|
||||
|
||||
|
||||
/**
|
||||
* this is used when the validator encounters a reference to a structure definition, value set or code system at some random URL reference
|
||||
* while validating.
|
||||
*
|
||||
* Added in v5.2.2. return null to leave functionality as it was before then.
|
||||
*
|
||||
* @param primitiveValue
|
||||
* @return an R5 version of the resource
|
||||
* @throws URISyntaxException
|
||||
*/
|
||||
CanonicalResource fetchCanonicalResource(String url) throws URISyntaxException;
|
||||
|
||||
/**
|
||||
* Whether to try calling fetchCanonicalResource for this reference (not whether it will succeed - just throw an exception from fetchCanonicalResource if it doesn't resolve. This is a policy thing.
|
||||
*
|
||||
* Added in v5.2.2. return false to leave functionality as it was before then.
|
||||
*
|
||||
* @param url
|
||||
* @return
|
||||
*/
|
||||
boolean fetchesCanonicalResource(String url);
|
||||
}
|
||||
|
||||
public enum BestPracticeWarningLevel {
|
||||
|
|
|
@ -84,6 +84,7 @@ public class XVerExtensionManager {
|
|||
|
||||
StructureDefinition sd = new StructureDefinition();
|
||||
sd.setUserData(XVER_EXT_MARKER, "true");
|
||||
sd.setUserData("path", "http://hl7.org/fhir/versions.html#extensions");
|
||||
sd.setUrl(url);
|
||||
sd.setVersion(context.getVersion());
|
||||
sd.setFhirVersion(FHIRVersion.fromCode(context.getVersion()));
|
||||
|
@ -135,7 +136,11 @@ public class XVerExtensionManager {
|
|||
if (hasTargets(tr.getCode()) ) {
|
||||
s = s.substring(t.length()+1);
|
||||
for (String p : s.substring(0, s.length()-1).split("\\|")) {
|
||||
tr.addTargetProfile("http://hl7.org/fhir/StructureDefinition/"+p);
|
||||
if ("Any".equals(p)) {
|
||||
tr.addTargetProfile("http://hl7.org/fhir/StructureDefinition/Resource");
|
||||
} else {
|
||||
tr.addTargetProfile("http://hl7.org/fhir/StructureDefinition/"+p);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -99,7 +99,6 @@ public class EFhirClientException extends RuntimeException {
|
|||
* A default message of "One or more server side errors have occurred during this operation. Refer to e.getServerErrors() for additional details."
|
||||
* will be returned to users.
|
||||
*
|
||||
* @param message
|
||||
* @param serverError
|
||||
*/
|
||||
public EFhirClientException(OperationOutcome serverError) {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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 IOException {
|
||||
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 IOException {
|
||||
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 IOException {
|
||||
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 IOException {
|
||||
if (payload == null) throw new EFhirClientException("PUT requests require a non-null payload");
|
||||
RequestBody body = RequestBody.create(payload);
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL())
|
||||
.put(body);
|
||||
|
||||
return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri,
|
||||
byte[] payload,
|
||||
String resourceFormat,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
return issuePostRequest(resourceUri, payload, resourceFormat, null, message, timeout);
|
||||
}
|
||||
|
||||
public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri,
|
||||
byte[] payload,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
String message,
|
||||
long timeout) throws IOException {
|
||||
if (payload == null) throw new EFhirClientException("POST requests require a non-null payload");
|
||||
RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL())
|
||||
.post(body);
|
||||
|
||||
return executeFhirRequest(request, resourceFormat, headers, message, retryCount, timeout);
|
||||
}
|
||||
|
||||
public boolean issueDeleteRequest(URI resourceUri) throws IOException {
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL())
|
||||
.delete();
|
||||
return executeFhirRequest(request, null, null, null, retryCount, timeout).isSuccessfulRequest();
|
||||
}
|
||||
|
||||
public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat) throws IOException {
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL());
|
||||
|
||||
return executeBundleRequest(request, resourceFormat, null, null, retryCount, timeout);
|
||||
}
|
||||
|
||||
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 IOException {
|
||||
if (payload == null) throw new EFhirClientException("POST requests require a non-null payload");
|
||||
RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
|
||||
Request.Builder request = new Request.Builder()
|
||||
.url(resourceUri.toURL())
|
||||
.post(body);
|
||||
|
||||
return executeBundleRequest(request, resourceFormat, null, message, retryCount, timeout);
|
||||
}
|
||||
|
||||
protected <T extends Resource> Bundle executeBundleRequest(Request.Builder request,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
String message,
|
||||
int retryCount,
|
||||
long timeout) throws IOException {
|
||||
return new FhirRequestBuilder(request)
|
||||
.withLogger(logger)
|
||||
.withResourceFormat(resourceFormat)
|
||||
.withRetryCount(retryCount)
|
||||
.withMessage(message)
|
||||
.withHeaders(headers == null ? new Headers.Builder().build() : headers)
|
||||
.withTimeout(timeout, TimeUnit.MILLISECONDS)
|
||||
.executeAsBatch();
|
||||
}
|
||||
|
||||
protected <T extends Resource> ResourceRequest<T> executeFhirRequest(Request.Builder request,
|
||||
String resourceFormat,
|
||||
Headers headers,
|
||||
String message,
|
||||
int retryCount,
|
||||
long timeout) throws IOException {
|
||||
return new FhirRequestBuilder(request)
|
||||
.withLogger(logger)
|
||||
.withResourceFormat(resourceFormat)
|
||||
.withRetryCount(retryCount)
|
||||
.withMessage(message)
|
||||
.withHeaders(headers == null ? new Headers.Builder().build() : headers)
|
||||
.withTimeout(timeout, TimeUnit.MILLISECONDS)
|
||||
.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated It does not appear as though this method is actually being used. Will be removed in a future release
|
||||
* unless a case is made to keep it.
|
||||
*/
|
||||
@Deprecated
|
||||
public Calendar getLastModifiedResponseHeaderAsCalendarObject(URLConnection serverConnection) {
|
||||
String dateTime = null;
|
||||
try {
|
||||
dateTime = serverConnection.getHeaderField("Last-Modified");
|
||||
SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", new Locale("en", "US"));
|
||||
Date lastModifiedTimestamp = format.parse(dateTime);
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(lastModifiedTimestamp);
|
||||
return calendar;
|
||||
} catch (ParseException pe) {
|
||||
throw new EFhirClientException("Error parsing Last-Modified response header " + dateTime, pe);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,328 @@
|
|||
package org.hl7.fhir.r5.utils.client.network;
|
||||
|
||||
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.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class FhirRequestBuilder {
|
||||
|
||||
protected static final String HTTP_PROXY_USER = "http.proxyUser";
|
||||
protected static final String HTTP_PROXY_PASS = "http.proxyPassword";
|
||||
protected static final String HEADER_PROXY_AUTH = "Proxy-Authorization";
|
||||
protected static final String LOCATION_HEADER = "location";
|
||||
protected static final String CONTENT_LOCATION_HEADER = "content-location";
|
||||
protected static final String DEFAULT_CHARSET = "UTF-8";
|
||||
/**
|
||||
* The singleton instance of the HttpClient, used for all requests.
|
||||
*/
|
||||
private static OkHttpClient okHttpClient;
|
||||
private final Request.Builder httpRequest;
|
||||
private String resourceFormat = null;
|
||||
private Headers headers = null;
|
||||
private String message = null;
|
||||
private int retryCount = 1;
|
||||
/**
|
||||
* The timeout quantity. Used in combination with {@link FhirRequestBuilder#timeoutUnit}.
|
||||
*/
|
||||
private long timeout = 5000;
|
||||
/**
|
||||
* Time unit for {@link FhirRequestBuilder#timeout}.
|
||||
*/
|
||||
private TimeUnit timeoutUnit = TimeUnit.MILLISECONDS;
|
||||
/**
|
||||
* {@link ToolingClientLogger} for log output.
|
||||
*/
|
||||
private ToolingClientLogger logger = null;
|
||||
|
||||
public FhirRequestBuilder(Request.Builder httpRequest) {
|
||||
this.httpRequest = httpRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds necessary default headers, formatting headers, and any passed in {@link Headers} to the passed in
|
||||
* {@link okhttp3.Request.Builder}
|
||||
*
|
||||
* @param request {@link okhttp3.Request.Builder} to add headers to.
|
||||
* @param format Expected {@link Resource} format.
|
||||
* @param headers Any additional {@link Headers} to add to the request.
|
||||
*/
|
||||
protected static void formatHeaders(Request.Builder request, String format, Headers headers) {
|
||||
addDefaultHeaders(request);
|
||||
if (format != null) addResourceFormatHeaders(request, format);
|
||||
if (headers != null) addHeaders(request, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds necessary headers for all REST requests.
|
||||
* <li>User-Agent : hapi-fhir-tooling-client</li>
|
||||
* <li>Accept-Charset : {@link FhirRequestBuilder#DEFAULT_CHARSET}</li>
|
||||
*
|
||||
* @param request {@link Request.Builder} to add default headers to.
|
||||
*/
|
||||
protected static void addDefaultHeaders(Request.Builder request) {
|
||||
request.addHeader("User-Agent", "hapi-fhir-tooling-client");
|
||||
request.addHeader("Accept-Charset", DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds necessary headers for the given resource format provided.
|
||||
*
|
||||
* @param request {@link Request.Builder} to add default headers to.
|
||||
*/
|
||||
protected static void addResourceFormatHeaders(Request.Builder request, String format) {
|
||||
request.addHeader("Accept", format);
|
||||
request.addHeader("Content-Type", format + ";charset=" + DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through the passed in {@link Headers} and adds them to the provided {@link Request.Builder}.
|
||||
*
|
||||
* @param request {@link Request.Builder} to add headers to.
|
||||
* @param headers {@link Headers} to add to request.
|
||||
*/
|
||||
protected static void addHeaders(Request.Builder request, Headers headers) {
|
||||
headers.forEach(header -> request.addHeader(header.getFirst(), header.getSecond()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if any of the {@link org.hl7.fhir.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() throws IOException {
|
||||
formatHeaders(httpRequest, resourceFormat, null);
|
||||
Response response = getHttpClient().newCall(httpRequest.build()).execute();
|
||||
T resource = unmarshalReference(response, resourceFormat);
|
||||
return new ResourceRequest<T>(resource, response.code(), getLocationHeader(response.headers()));
|
||||
}
|
||||
|
||||
public Bundle executeAsBatch() throws IOException {
|
||||
formatHeaders(httpRequest, resourceFormat, null);
|
||||
Response response = getHttpClient().newCall(httpRequest.build()).execute();
|
||||
return unmarshalFeed(response, resourceFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmarshalls a resource from the response stream.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T extends Resource> T unmarshalReference(Response response, String format) {
|
||||
T resource = null;
|
||||
OperationOutcome error = null;
|
||||
|
||||
if (response.body() != null) {
|
||||
try {
|
||||
byte[] body = response.body().bytes();
|
||||
log(response.code(), response.headers(), body);
|
||||
resource = (T) getParser(format).parse(body);
|
||||
if (resource instanceof OperationOutcome && hasError((OperationOutcome) resource)) {
|
||||
error = (OperationOutcome) resource;
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new EFhirClientException("Error reading Http Response: " + ioe.getMessage(), ioe);
|
||||
} catch (Exception e) {
|
||||
throw new EFhirClientException("Error parsing response message: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (error != null) {
|
||||
throw new EFhirClientException("Error from server: " + ResourceUtilities.getErrorDescription(error), error);
|
||||
}
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmarshalls Bundle from response stream.
|
||||
*/
|
||||
protected Bundle unmarshalFeed(Response response, String format) {
|
||||
Bundle feed = null;
|
||||
OperationOutcome error = null;
|
||||
try {
|
||||
byte[] body = response.body().bytes();
|
||||
log(response.code(), response.headers(), body);
|
||||
String contentType = response.header("Content-Type");
|
||||
if (body != null) {
|
||||
if (contentType.contains(ResourceFormat.RESOURCE_XML.getHeader()) || contentType.contains("text/xml+fhir")) {
|
||||
Resource rf = getParser(format).parse(body);
|
||||
if (rf instanceof Bundle)
|
||||
feed = (Bundle) rf;
|
||||
else if (rf instanceof OperationOutcome && hasError((OperationOutcome) rf)) {
|
||||
error = (OperationOutcome) rf;
|
||||
} else {
|
||||
throw new EFhirClientException("Error reading server response: a resource was returned instead");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new EFhirClientException("Error reading Http Response", ioe);
|
||||
} catch (Exception e) {
|
||||
throw new EFhirClientException("Error parsing response message", e);
|
||||
}
|
||||
if (error != null) {
|
||||
throw new EFhirClientException("Error from server: " + ResourceUtilities.getErrorDescription(error), error);
|
||||
}
|
||||
return feed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the appropriate parser based on the format type passed in. Defaults to XML parser if a blank format is
|
||||
* provided...because reasons.
|
||||
* <p>
|
||||
* Currently supports only "json" and "xml" formats.
|
||||
*
|
||||
* @param format One of "json" or "xml".
|
||||
* @return {@link JsonParser} or {@link XmlParser}
|
||||
*/
|
||||
protected IParser getParser(String format) {
|
||||
if (StringUtils.isBlank(format)) {
|
||||
format = ResourceFormat.RESOURCE_XML.getHeader();
|
||||
}
|
||||
if (format.equalsIgnoreCase("json") || format.equalsIgnoreCase(ResourceFormat.RESOURCE_JSON.getHeader())) {
|
||||
return new JsonParser();
|
||||
} else if (format.equalsIgnoreCase("xml") || format.equalsIgnoreCase(ResourceFormat.RESOURCE_XML.getHeader())) {
|
||||
return new XmlParser();
|
||||
} else {
|
||||
throw new EFhirClientException("Invalid format: " + format);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the given {@link Response}, using the current {@link ToolingClientLogger}. If the current
|
||||
* {@link FhirRequestBuilder#logger} is null, no action is taken.
|
||||
*
|
||||
* @param responseCode HTTP response code
|
||||
* @param responseHeaders {@link Headers} from response
|
||||
* @param responseBody Byte array response
|
||||
*/
|
||||
protected void log(int responseCode, Headers responseHeaders, byte[] responseBody) {
|
||||
if (logger != null) {
|
||||
List<String> headerList = new ArrayList<>(Collections.emptyList());
|
||||
Map<String, List<String>> headerMap = responseHeaders.toMultimap();
|
||||
headerMap.keySet().forEach(key -> headerMap.get(key).forEach(value -> headerList.add(key + ":" + value)));
|
||||
|
||||
try {
|
||||
logger.logResponse(Integer.toString(responseCode), headerList, responseBody);
|
||||
} catch (Exception e) {
|
||||
System.out.println("Error parsing response body passed in to logger ->\n" + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
// else { // TODO fix logs
|
||||
// System.out.println("Call to log HTTP response with null ToolingClientLogger set... are you forgetting to " +
|
||||
// "initialize your logger?");
|
||||
// }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package org.hl7.fhir.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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>org.hl7.fhir.core</artifactId>
|
||||
<version>5.1.23-SNAPSHOT</version>
|
||||
<version>5.2.8-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
@ -83,14 +83,25 @@
|
|||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.sonatype.plugins</groupId>
|
||||
<artifactId>nexus-staging-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>org.hl7.fhir.core</artifactId>
|
||||
<version>5.1.23-SNAPSHOT</version>
|
||||
<version>5.2.8-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -778,7 +778,7 @@ public class Utilities {
|
|||
|
||||
|
||||
public static String encodeUri(String v) {
|
||||
return v.replace(" ", "%20").replace("?", "%3F").replace("=", "%3D");
|
||||
return v.replace(" ", "%20").replace("?", "%3F").replace("=", "%3D").replace("|", "%7C");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -455,4 +455,12 @@ public class VersionUtilities {
|
|||
return res;
|
||||
}
|
||||
|
||||
public static String getVersionForPackage(String pid) {
|
||||
if (pid.startsWith("hl7.fhir.r")) {
|
||||
String[] p = pid.split("\\.");
|
||||
return versionFromCode(p[2]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -27,6 +27,7 @@ public class I18nConstants {
|
|||
public static final String BUNDLE_BUNDLE_ENTRY_NOFIRST = "Bundle_BUNDLE_Entry_NoFirst";
|
||||
public static final String BUNDLE_BUNDLE_ENTRY_NOFIRSTRESOURCE = "Bundle_BUNDLE_Entry_NoFirstResource";
|
||||
public static final String BUNDLE_BUNDLE_ENTRY_NOFULLURL = "Bundle_BUNDLE_Entry_NoFullUrl";
|
||||
public static final String BUNDLE_BUNDLE_ENTRY_FULLURL_REQUIRED = "BUNDLE_BUNDLE_ENTRY_FULLURL_REQUIRED";
|
||||
public static final String BUNDLE_BUNDLE_ENTRY_NOPROFILE = "Bundle_BUNDLE_Entry_NoProfile";
|
||||
public static final String BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES = "BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES";
|
||||
public static final String BUNDLE_BUNDLE_ENTRY_NOTFOUND = "Bundle_BUNDLE_Entry_NotFound";
|
||||
|
@ -349,6 +350,7 @@ public class I18nConstants {
|
|||
public static final String SD_ED_TYPE_PROFILE_UNKNOWN = "SD_ED_TYPE_PROFILE_UNKNOWN";
|
||||
public static final String SD_ED_TYPE_PROFILE_NOTYPE = "SD_ED_TYPE_PROFILE_NOTYPE";
|
||||
public static final String SD_ED_TYPE_PROFILE_WRONG = "SD_ED_TYPE_PROFILE_WRONG";
|
||||
public static final String SD_ED_TYPE_NO_TARGET_PROFILE = "SD_ED_TYPE_NO_TARGET_PROFILE";
|
||||
public static final String SEARCHPARAMETER_BASE_WRONG = "SEARCHPARAMETER_BASE_WRONG";
|
||||
public static final String SEARCHPARAMETER_EXP_WRONG = "SEARCHPARAMETER_EXP_WRONG";
|
||||
public static final String SEARCHPARAMETER_NOTFOUND = "SEARCHPARAMETER_NOTFOUND";
|
||||
|
@ -572,6 +574,8 @@ public class I18nConstants {
|
|||
public static final String VALIDATION_VAL_PROFILE_THIS_VERSION_OK = "VALIDATION_VAL_PROFILE_THIS_VERSION_OK";
|
||||
public static final String VALIDATION_VAL_PROFILE_THIS_VERSION_OTHER = "VALIDATION_VAL_PROFILE_THIS_VERSION_OTHER";
|
||||
public static final String VALIDATION_VAL_PROFILE_UNKNOWN = "Validation_VAL_Profile_Unknown";
|
||||
public static final String VALIDATION_VAL_PROFILE_UNKNOWN_NOT_POLICY = "VALIDATION_VAL_PROFILE_UNKNOWN_NOT_POLICY";
|
||||
public static final String VALIDATION_VAL_PROFILE_UNKNOWN_ERROR = "VALIDATION_VAL_PROFILE_UNKNOWN_ERROR";
|
||||
public static final String VALIDATION_VAL_PROFILE_WRONGTYPE = "Validation_VAL_Profile_WrongType";
|
||||
public static final String VALIDATION_VAL_PROFILE_WRONGTYPE2 = "Validation_VAL_Profile_WrongType2";
|
||||
public static final String VALIDATION_VAL_UNKNOWN_PROFILE = "Validation_VAL_Unknown_Profile";
|
||||
|
|
|
@ -46,6 +46,7 @@ import org.hl7.fhir.utilities.npm.NpmPackage.NpmPackageFolder;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
|
@ -57,6 +58,7 @@ import java.net.URL;
|
|||
import java.net.URLConnection;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.FileLock;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.sql.Timestamp;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
@ -219,6 +221,11 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple
|
|||
return retVal;
|
||||
}
|
||||
|
||||
retVal = super.loadFromPackageServer(id, VersionUtilities.getMajMin(version)+".x");
|
||||
if (retVal != null) {
|
||||
return retVal;
|
||||
}
|
||||
|
||||
// ok, well, we'll try the old way
|
||||
return fetchTheOldWay(id, version);
|
||||
}
|
||||
|
@ -523,7 +530,7 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple
|
|||
InputStream stream = fetchFromUrlSpecific(Utilities.pathURL("http://build.fhir.org", id + ".tgz"), false);
|
||||
return new InputStreamWithSrc(stream, Utilities.pathURL("http://build.fhir.org", id + ".tgz"), "current");
|
||||
} else {
|
||||
throw new FHIRException("The package '" + id + "' has not entry on the current build server");
|
||||
throw new FHIRException("The package '" + id + "' has no entry on the current build server");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -624,13 +631,16 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple
|
|||
loadFromBuildServer();
|
||||
} catch (Exception e) {
|
||||
System.out.println("Error connecting to build server - running without build (" + e.getMessage() + ")");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void loadFromBuildServer() throws IOException {
|
||||
URL url = new URL("https://build.fhir.org/ig/qas.json?nocache=" + System.currentTimeMillis());
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
SSLCertTruster.trustAllHosts();
|
||||
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
||||
connection.setHostnameVerifier(SSLCertTruster.DO_NOT_VERIFY);
|
||||
connection.setRequestMethod("GET");
|
||||
InputStream json = connection.getInputStream();
|
||||
buildInfo = (JsonArray) new com.google.gson.JsonParser().parse(TextFile.streamToString(json));
|
||||
|
@ -655,16 +665,6 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple
|
|||
buildLoaded = true; // whether it succeeds or not
|
||||
}
|
||||
|
||||
// private String buildPath(String url) {
|
||||
// for (JsonElement e : buildInfo) {
|
||||
// JsonObject j = (JsonObject) e;
|
||||
// if (j.has("url") && (url.equals(j.get("url").getAsString()) || j.get("url").getAsString().startsWith(url+"/ImplementationGuide"))) {
|
||||
// return "https://build.fhir.org/ig/"+j.get("repo").getAsString();
|
||||
// }
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
private String getRepo(String path) {
|
||||
String[] p = path.split("\\/");
|
||||
return p[0] + "/" + p[1];
|
||||
|
|
|
@ -1087,6 +1087,12 @@ public class NpmPackage {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
if (Utilities.existsInList(name(), "fhir.test.data.r2", "fhir.test.data.r3", "fhir.test.data.r4", "fhir.tx.support.r2", "fhir.tx.support.r3", "fhir.tx.support.r4", "us.nlm.vsac")) {
|
||||
return true;
|
||||
}
|
||||
if (JSONUtil.bool(npm, "lazy-load")) {
|
||||
return true;
|
||||
}
|
||||
if (!hasFile("other", "spec.internals")) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package org.hl7.fhir.utilities.npm;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
/**
|
||||
* This is a _temporary_ fix to get around the fact that the build server's SSL certs have expired and people cannot
|
||||
* publish IGs or run tests that rely on that box. The intention is to overhaul much of the current networking code
|
||||
* to a more central, unified, HttpClient module.
|
||||
* <p>
|
||||
* If this is still in the code in 2021, contact markiantorno on github and yell at him.
|
||||
*/
|
||||
public class SSLCertTruster {
|
||||
|
||||
// always verify the host - dont check for certificate
|
||||
final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
|
||||
public boolean verify(String hostname, SSLSession session) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Trust every server - don't check for any certificate
|
||||
*/
|
||||
public static void trustAllHosts() {
|
||||
// Create a trust manager that does not validate certificate chains
|
||||
TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() {
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {}
|
||||
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[]{};
|
||||
}
|
||||
}};
|
||||
|
||||
// Install the all-trusting trust manager
|
||||
try {
|
||||
SSLContext sc = SSLContext.getInstance("TLS");
|
||||
sc.init(null, trustAllCerts, new java.security.SecureRandom());
|
||||
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -55,6 +55,7 @@ import org.xmlpull.v1.XmlPullParserException;
|
|||
|
||||
public class XhtmlParser {
|
||||
public static final String XHTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
private static final char END_OF_CHARS = (char) -1;
|
||||
|
||||
public class NSMap {
|
||||
private Map<String, String> nslist = new HashMap<String, String>();
|
||||
|
@ -515,7 +516,7 @@ private boolean elementIsOk(String name) throws FHIRFormatError {
|
|||
private void parseElementInner(XhtmlNode node, List<XhtmlNode> parents, NSMap nsm, boolean escaping) throws FHIRFormatError, IOException
|
||||
{
|
||||
StringBuilder s = new StringBuilder();
|
||||
while (peekChar() != '\0' && !parents.contains(unwindPoint) && !(node == unwindPoint))
|
||||
while (peekChar() != END_OF_CHARS && !parents.contains(unwindPoint) && !(node == unwindPoint))
|
||||
{
|
||||
if (peekChar() == '<')
|
||||
{
|
||||
|
@ -606,7 +607,7 @@ private boolean elementIsOk(String name) throws FHIRFormatError {
|
|||
{
|
||||
while (Character.isWhitespace(peekChar()))
|
||||
readChar();
|
||||
while (peekChar() != '>' && peekChar() != '/' && peekChar() != '\0')
|
||||
while (peekChar() != '>' && peekChar() != '/' && peekChar() != END_OF_CHARS)
|
||||
{
|
||||
String name = readName();
|
||||
if (name.length() == 0)
|
||||
|
@ -630,7 +631,7 @@ private boolean elementIsOk(String name) throws FHIRFormatError {
|
|||
if (peekChar() == '"' || peekChar() == '\'')
|
||||
node.getAttributes().put(name, parseAttributeValue(readChar()));
|
||||
else
|
||||
node.getAttributes().put(name, parseAttributeValue('\0'));
|
||||
node.getAttributes().put(name, parseAttributeValue(END_OF_CHARS));
|
||||
}
|
||||
while (Character.isWhitespace(peekChar()))
|
||||
readChar();
|
||||
|
@ -640,7 +641,7 @@ private boolean elementIsOk(String name) throws FHIRFormatError {
|
|||
private String parseAttributeValue(char term) throws IOException, FHIRFormatError
|
||||
{
|
||||
StringBuilder b = new StringBuilder();
|
||||
while (peekChar() != '\0' && peekChar() != '>' && (term != '\0' || peekChar() != '/') && peekChar() != term)
|
||||
while (peekChar() != END_OF_CHARS && peekChar() != '>' && (term != END_OF_CHARS || peekChar() != '/') && peekChar() != term)
|
||||
{
|
||||
if (peekChar() == '&')
|
||||
{
|
||||
|
@ -704,15 +705,15 @@ private boolean elementIsOk(String name) throws FHIRFormatError {
|
|||
if (cache.length() > 0)
|
||||
return cache.charAt(0);
|
||||
else if (!rdr.ready())
|
||||
return '\0';
|
||||
return END_OF_CHARS;
|
||||
else
|
||||
{
|
||||
char c = (char)rdr.read();
|
||||
if (c == (char)-1)
|
||||
{
|
||||
int i = rdr.read();
|
||||
if (i == -1) {
|
||||
cache = "";
|
||||
return '\0';
|
||||
return END_OF_CHARS;
|
||||
}
|
||||
char c = (char) i;
|
||||
cache = Character.toString(c);
|
||||
return c;
|
||||
}
|
||||
|
@ -727,7 +728,7 @@ private boolean elementIsOk(String name) throws FHIRFormatError {
|
|||
cache = cache.length() == 1 ? "" : cache.substring(1);
|
||||
}
|
||||
else if (!rdr.ready())
|
||||
c = '\0';
|
||||
c = END_OF_CHARS;
|
||||
else
|
||||
c = (char)rdr.read();
|
||||
if (c == '\r' || c == '\n') {
|
||||
|
@ -744,9 +745,9 @@ private boolean elementIsOk(String name) throws FHIRFormatError {
|
|||
private String readToTagEnd() throws IOException, FHIRFormatError
|
||||
{
|
||||
StringBuilder s = new StringBuilder();
|
||||
while (peekChar() != '>' && peekChar() != '\0')
|
||||
while (peekChar() != '>' && peekChar() != END_OF_CHARS)
|
||||
s.append(readChar());
|
||||
if (peekChar() != '\0')
|
||||
if (peekChar() != END_OF_CHARS)
|
||||
{
|
||||
readChar();
|
||||
skipWhiteSpace();
|
||||
|
@ -765,7 +766,7 @@ private boolean elementIsOk(String name) throws FHIRFormatError {
|
|||
if (c == '>') {
|
||||
done = true;
|
||||
readChar();
|
||||
} else if (c != '\0')
|
||||
} else if (c != END_OF_CHARS)
|
||||
s.append(readChar());
|
||||
else if (mustBeWellFormed)
|
||||
throw new FHIRFormatError("Unexpected termination of html source"+descLoc());
|
||||
|
@ -814,12 +815,12 @@ private boolean elementIsOk(String name) throws FHIRFormatError {
|
|||
} else if (c == '[' && s.toString().startsWith("DOCTYPE ")) {
|
||||
doctypeEntities = true;
|
||||
s.append(readChar());
|
||||
} else if (c != '\0')
|
||||
} else if (c != END_OF_CHARS)
|
||||
s.append(readChar());
|
||||
else if (mustBeWellFormed)
|
||||
throw new FHIRFormatError("Unexpected termination of html source"+descLoc());
|
||||
}
|
||||
if (peekChar() != '\0')
|
||||
if (peekChar() != END_OF_CHARS)
|
||||
{
|
||||
readChar();
|
||||
skipWhiteSpace();
|
||||
|
|
|
@ -7,6 +7,7 @@ Bundle_BUNDLE_Entry_MismatchIdUrl = The canonical URL ({0}) cannot match the ful
|
|||
Bundle_BUNDLE_Entry_NoFirst = Documents or Messages must contain at least one entry
|
||||
Bundle_BUNDLE_Entry_NoFirstResource = No resource on first entry
|
||||
Bundle_BUNDLE_Entry_NoFullUrl = Bundle entry missing fullUrl
|
||||
BUNDLE_BUNDLE_ENTRY_FULLURL_REQUIRED = Except for transactions and batches, each entry in a Bundle must have a fullUrl which is the identity of the resource in the entry
|
||||
Bundle_BUNDLE_Entry_NoProfile = No profile found for contained resource of type ''{0}''
|
||||
Bundle_BUNDLE_Entry_NotFound = Can''t find ''{0}'' in the bundle ({1})
|
||||
Bundle_BUNDLE_Entry_Orphan = Entry {0} isn''t reachable by traversing from first Bundle entry
|
||||
|
@ -223,7 +224,9 @@ Validation_VAL_Profile_NotAllowed = This element is not allowed by the profile {
|
|||
Validation_VAL_Profile_NotSlice = This element does not match any known slice {0} and slicing is CLOSED: {1}
|
||||
Validation_VAL_Profile_OutOfOrder = As specified by profile {0}, Element ''{1}'' is out of order
|
||||
Validation_VAL_Profile_SliceOrder = As specified by profile {0}, Element ''{1}'' is out of order in ordered slice
|
||||
Validation_VAL_Profile_Unknown = Profile reference ''{0}'' could not be resolved, so has not been checked
|
||||
Validation_VAL_Profile_Unknown = Profile reference ''{0}'' has not been checked because it is unknown
|
||||
VALIDATION_VAL_PROFILE_UNKNOWN_NOT_POLICY = Profile reference ''{0}'' has not been checked because it is unknown, and the validator is set to not fetch unknown profiles
|
||||
VALIDATION_VAL_PROFILE_UNKNOWN_ERROR = Profile reference ''{0}'' has not been checked because it is unknown, and fetching it resulted in the error {1}
|
||||
Validation_VAL_Profile_WrongType = Specified profile type was ''{0}'', but found type ''{1}''
|
||||
Validation_VAL_Unknown_Profile = Unknown profile {0}
|
||||
XHTML_XHTML_Attribute_Illegal = Illegal attribute name in the XHTML (''{0}'' on ''{1}'')
|
||||
|
@ -502,8 +505,8 @@ Validation_VAL_Profile_WrongType = Specified profile type was ''{0}'', but found
|
|||
Validation_VAL_Profile_WrongType2 = Type mismatch processing profile {0} at path {1}: The element type is {4}, but the profile {3} is for a different type {2}
|
||||
VALIDATION_VAL_ILLEGAL_TYPE_CONSTRAINT = Illegal constraint in profile {0} at path {1} - cannot constrain to type {2} from base types {3}
|
||||
EXTENSION_EXT_CONTEXT_WRONG_XVER = The extension {0} from FHIR version {3} is not allowed to be used at this point (allowed = {1}; this element is [{2}; this is a warning since contexts may be renamed between FHIR versions)
|
||||
SECURITY_STRING_CONTENT_ERROR = The string value contains embedded HTML tags, which are not allowed for security reasons in this context
|
||||
SECURITY_STRING_CONTENT_WARNING = The string value contains embedded HTML tags. Note that all inputs should be escaped when rendered to HTML as a matter of course
|
||||
SECURITY_STRING_CONTENT_ERROR = The string value contains text that looks like embedded HTML tags, which are not allowed for security reasons in this context
|
||||
SECURITY_STRING_CONTENT_WARNING = The string value contains text that looks like embedded HTML tags. If this content is rendered to HTML withour appropraite post-processing, it may be a security risk
|
||||
ALL_OK = All OK
|
||||
SEARCHPARAMETER_NOTFOUND = Unable to find the base Search Parameter {0} so can''t check that this SearchParameter is a proper derivation from it
|
||||
SEARCHPARAMETER_BASE_WRONG = The resource type {1} is not listed as a base in the SearchParameter this is derived from ({0})
|
||||
|
@ -615,7 +618,8 @@ SD_NESTED_MUST_SUPPORT_SNAPSHOT = The element {0} has types/profiles/targets tha
|
|||
Unable_to_connect_to_terminology_server = Unable to connect to terminology server. Error = {0}
|
||||
SD_ED_TYPE_PROFILE_UNKNOWN = Unable to resolve profile {0}
|
||||
SD_ED_TYPE_PROFILE_NOTYPE = Found profile {0}, but unable to determine the type it applies it
|
||||
SD_ED_TYPE_PROFILE_WRONG = Profile {0} is for type {1}, but this element has type {2}
|
||||
SD_ED_TYPE_PROFILE_WRONG = Profile {0} is for type {1}, but the {3} element has type {2}
|
||||
SD_ED_TYPE_NO_TARGET_PROFILE = Type {0} does not allow for target Profiles
|
||||
TERMINOLOGY_TX_NOSVC_BOUND_REQ = Could not confirm that the codes provided are from the required value set {0} because there is no terminology service
|
||||
TERMINOLOGY_TX_NOSVC_BOUND_EXT = Could not confirm that the codes provided are from the extensible value set {0} because there is no terminology service
|
||||
ARRAY_CANNOT_BE_EMPTY = Array cannot be empty - the property should not be present if it has no values
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package org.hl7.fhir.utilities.tests;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRFormatError;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlParser;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -88,4 +90,9 @@ public class XhtmlNodeTest {
|
|||
ObjectOutputStream oout = new ObjectOutputStream(bout);
|
||||
oout.writeObject(node);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseBadChars() throws FHIRFormatError, IOException {
|
||||
XhtmlNode x = new XhtmlParser().parse(BaseTestingUtilities.loadTestResource("xhtml", "bad-chars.html"), "div");
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>org.hl7.fhir.core</artifactId>
|
||||
<version>5.1.23-SNAPSHOT</version>
|
||||
<version>5.2.8-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
@ -149,6 +149,20 @@
|
|||
</lifecycleMappingMetadata>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.sonatype.plugins</groupId>
|
||||
<artifactId>nexus-staging-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>org.hl7.fhir.core</artifactId>
|
||||
<version>5.1.23-SNAPSHOT</version>
|
||||
<version>5.2.8-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -73,8 +73,11 @@ import org.hl7.fhir.r5.elementmodel.Element;
|
|||
import org.hl7.fhir.r5.model.Base;
|
||||
import org.hl7.fhir.r5.model.DomainResource;
|
||||
import org.hl7.fhir.r5.model.Resource;
|
||||
import org.hl7.fhir.r5.model.StructureDefinition;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
import org.hl7.fhir.r5.terminologies.ValueSetUtilities;
|
||||
import org.hl7.fhir.r5.utils.XVerExtensionManager;
|
||||
import org.hl7.fhir.r5.utils.XVerExtensionManager.XVerExtensionStatus;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.i18n.I18nConstants;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||
|
@ -118,7 +121,18 @@ public class BaseValidator {
|
|||
protected Source source;
|
||||
protected IWorkerContext context;
|
||||
protected TimeTracker timeTracker = new TimeTracker();
|
||||
|
||||
protected XVerExtensionManager xverManager;
|
||||
|
||||
public BaseValidator(IWorkerContext context, XVerExtensionManager xverManager) {
|
||||
super();
|
||||
this.context = context;
|
||||
this.xverManager = xverManager;
|
||||
if (this.xverManager == null) {
|
||||
this.xverManager = new XVerExtensionManager(context);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Use to control what validation the validator performs.
|
||||
* Using this, you can turn particular kinds of validation on and off
|
||||
|
@ -129,11 +143,7 @@ public class BaseValidator {
|
|||
*/
|
||||
private Map<String, ValidationControl> validationControl = new HashMap<>();
|
||||
|
||||
public BaseValidator(IWorkerContext context){
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails
|
||||
*
|
||||
* @param thePass
|
||||
|
@ -873,5 +883,74 @@ public class BaseValidator {
|
|||
return validationControl;
|
||||
}
|
||||
|
||||
public XVerExtensionStatus xverStatus(String url) {
|
||||
return xverManager.status(url);
|
||||
}
|
||||
|
||||
public boolean isXverUrl(String url) {
|
||||
return xverManager.matchingUrl(url);
|
||||
}
|
||||
|
||||
public StructureDefinition xverDefn(String url) {
|
||||
return xverManager.makeDefinition(url);
|
||||
}
|
||||
|
||||
public String xverVersion(String url) {
|
||||
return xverManager.getVersion(url);
|
||||
}
|
||||
|
||||
public String xverElementId(String url) {
|
||||
return xverManager.getElementId(url);
|
||||
}
|
||||
|
||||
public StructureDefinition getXverExt(StructureDefinition profile, List<ValidationMessage> errors, String url) {
|
||||
if (isXverUrl(url)) {
|
||||
switch (xverStatus(url)) {
|
||||
case BadVersion:
|
||||
rule(errors, IssueType.BUSINESSRULE, profile.getId(), false, I18nConstants.EXTENSION_EXT_VERSION_INVALID, url, xverVersion(url));
|
||||
return null;
|
||||
case Unknown:
|
||||
rule(errors, IssueType.BUSINESSRULE, profile.getId(), false, I18nConstants.EXTENSION_EXT_VERSION_INVALIDID, url, xverElementId(url));
|
||||
return null;
|
||||
case Invalid:
|
||||
rule(errors, IssueType.BUSINESSRULE, profile.getId(), false, I18nConstants.EXTENSION_EXT_VERSION_NOCHANGE, url, xverElementId(url));
|
||||
return null;
|
||||
case Valid:
|
||||
StructureDefinition defn = xverDefn(url);
|
||||
context.generateSnapshot(defn);
|
||||
context.cacheResource(defn);
|
||||
return defn;
|
||||
default:
|
||||
rule(errors, IssueType.INVALID, profile.getId(), false, I18nConstants.EXTENSION_EXT_VERSION_INTERNAL, url);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public StructureDefinition getXverExt(List<ValidationMessage> errors, String path, Element element, String url) {
|
||||
if (isXverUrl(url)) {
|
||||
switch (xverStatus(url)) {
|
||||
case BadVersion:
|
||||
rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXTENSION_EXT_VERSION_INVALID, url, xverVersion(url));
|
||||
break;
|
||||
case Unknown:
|
||||
rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXTENSION_EXT_VERSION_INVALIDID, url, xverElementId(url));
|
||||
break;
|
||||
case Invalid:
|
||||
rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXTENSION_EXT_VERSION_NOCHANGE, url, xverElementId(url));
|
||||
break;
|
||||
case Valid:
|
||||
StructureDefinition ex = xverDefn(url);
|
||||
context.generateSnapshot(ex);
|
||||
context.cacheResource(ex);
|
||||
return ex;
|
||||
default:
|
||||
rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXTENSION_EXT_VERSION_INTERNAL, url);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -68,6 +68,7 @@ import org.hl7.fhir.r5.formats.JsonParser;
|
|||
import org.hl7.fhir.r5.formats.XmlParser;
|
||||
import org.hl7.fhir.r5.model.Base;
|
||||
import org.hl7.fhir.r5.model.Bundle;
|
||||
import org.hl7.fhir.r5.model.CanonicalResource;
|
||||
import org.hl7.fhir.r5.model.Coding;
|
||||
import org.hl7.fhir.r5.model.Constants;
|
||||
import org.hl7.fhir.r5.model.DomainResource;
|
||||
|
@ -1007,7 +1008,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
|
|||
}
|
||||
|
||||
public void loadIg(String src, boolean recursive) throws IOException, FHIRException {
|
||||
NpmPackage npm = src.matches(FilesystemPackageCacheManager.PACKAGE_VERSION_REGEX_OPT) ? pcm.loadPackage(src, null) : null;
|
||||
NpmPackage npm = src.matches(FilesystemPackageCacheManager.PACKAGE_VERSION_REGEX_OPT) && !new File(src).exists() ? pcm.loadPackage(src, null) : null;
|
||||
if (npm != null) {
|
||||
for (String s : npm.dependencies()) {
|
||||
if (!context.getLoadedPackages().contains(s)) {
|
||||
|
@ -1563,7 +1564,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
|
|||
}
|
||||
|
||||
public InstanceValidator getValidator() {
|
||||
InstanceValidator validator = new InstanceValidator(context, null);
|
||||
InstanceValidator validator = new InstanceValidator(context, null, null);
|
||||
validator.setHintAboutNonMustSupport(hintAboutNonMustSupport);
|
||||
validator.setAnyExtensionsAllowed(anyExtensionsAllowed);
|
||||
validator.setNoInvariantChecks(isNoInvariantChecks());
|
||||
|
@ -1907,6 +1908,17 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
|
|||
this.locale = locale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CanonicalResource fetchCanonicalResource(String url) throws URISyntaxException {
|
||||
return fetcher != null ? fetcher.fetchCanonicalResource(url) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean fetchesCanonicalResource(String url) {
|
||||
return fetcher != null ? fetcher.fetchesCanonicalResource(url) : false;
|
||||
}
|
||||
|
||||
|
||||
public void handleOutput(Resource r, String output, String version) throws FHIRException, IOException {
|
||||
if (output.startsWith("http://") || output.startsWith("http://")) {
|
||||
ByteArrayOutputStream bs = new ByteArrayOutputStream();
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -2,13 +2,17 @@ package org.hl7.fhir.validation.cli.services;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.hl7.fhir.convertors.txClient.TerminologyClientFactory;
|
||||
import org.hl7.fhir.exceptions.DefinitionException;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.exceptions.FHIRFormatError;
|
||||
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||
import org.hl7.fhir.r5.elementmodel.Element;
|
||||
import org.hl7.fhir.r5.model.CanonicalResource;
|
||||
import org.hl7.fhir.r5.terminologies.TerminologyClient;
|
||||
import org.hl7.fhir.r5.utils.IResourceValidator.IValidatorResourceFetcher;
|
||||
import org.hl7.fhir.r5.utils.IResourceValidator.ReferenceValidationPolicy;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
@ -38,12 +42,12 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher {
|
|||
|
||||
@Override
|
||||
public Element fetch(Object appContext, String url) throws FHIRFormatError, DefinitionException, FHIRException, IOException {
|
||||
throw new Error("Not done yet");
|
||||
throw new FHIRException("The URL '"+url+"' is not known to the FHIR validator, and has not been provided as part of the setup / parameters");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReferenceValidationPolicy validationPolicy(Object appContext, String path, String url) {
|
||||
throw new Error("Not done yet");
|
||||
return ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -111,12 +115,40 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher {
|
|||
|
||||
@Override
|
||||
public byte[] fetchRaw(String url) throws MalformedURLException, IOException {
|
||||
throw new Error("Not done yet");
|
||||
throw new FHIRException("The URL '"+url+"' is not known to the FHIR validator, and has not been provided as part of the setup / parameters");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLocale(Locale locale) {
|
||||
throw new Error("Not done yet");
|
||||
// nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public CanonicalResource fetchCanonicalResource(String url) throws URISyntaxException {
|
||||
String[] p = url.split("\\/");
|
||||
String root = getRoot(p, url);
|
||||
if (root != null) {
|
||||
TerminologyClient c;
|
||||
c = TerminologyClientFactory.makeClient(root, context.getVersion());
|
||||
return c.read(p[p.length-2], p[p.length-1]);
|
||||
} else {
|
||||
throw new FHIRException("The URL '"+url+"' is not known to the FHIR validator, and has not been provided as part of the setup / parameters");
|
||||
}
|
||||
}
|
||||
|
||||
private String getRoot(String[] p, String url) {
|
||||
if (p.length > 3 && Utilities.isValidId(p[p.length-1]) && context.getResourceNames().contains(p[p.length-2])) {
|
||||
url = url.substring(0, url.lastIndexOf("/"));
|
||||
return url.substring(0, url.lastIndexOf("/"));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean fetchesCanonicalResource(String url) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package org.hl7.fhir.validation.cli.utils;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
|
||||
import org.hl7.fhir.utilities.npm.ToolsVersion;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Class for displaying output to the cli user.
|
||||
|
@ -32,9 +34,9 @@ public class Display {
|
|||
*/
|
||||
public static void displayHelpDetails() {
|
||||
ClassLoader classLoader = Display.class.getClassLoader();
|
||||
File file = new File(classLoader.getResource("help.txt").getFile());
|
||||
InputStream help = classLoader.getResourceAsStream("help.txt");
|
||||
try {
|
||||
String data = FileUtils.readFileToString(file, "UTF-8");
|
||||
String data = IOUtils.toString(help, "UTF-8");
|
||||
System.out.println(data);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
|
|
|
@ -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) {
|
||||
|
@ -234,8 +238,6 @@ public class Params {
|
|||
cliContext.addSource(args[i]);
|
||||
}
|
||||
}
|
||||
if (cliContext.getSources().isEmpty())
|
||||
throw new Exception("Must provide at least one source file");
|
||||
return cliContext;
|
||||
}
|
||||
|
||||
|
|
|
@ -39,14 +39,15 @@ import java.util.Set;
|
|||
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||
import org.hl7.fhir.r5.model.CodeSystem;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
|
||||
import org.hl7.fhir.r5.utils.XVerExtensionManager;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
|
||||
import org.hl7.fhir.validation.BaseValidator;
|
||||
|
||||
public class CodeSystemValidator extends BaseValidator {
|
||||
|
||||
public CodeSystemValidator(IWorkerContext context) {
|
||||
super(context);
|
||||
public CodeSystemValidator(IWorkerContext context, XVerExtensionManager xverManager) {
|
||||
super(context, xverManager);
|
||||
}
|
||||
|
||||
public List<ValidationMessage> validate(CodeSystem cs, boolean forBuild) {
|
||||
|
|
|
@ -368,7 +368,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
|
||||
private EnableWhenEvaluator myEnableWhenEvaluator = new EnableWhenEvaluator();
|
||||
private String executionId;
|
||||
private XVerExtensionManager xverManager;
|
||||
private IValidationProfileUsageTracker tracker;
|
||||
private ValidatorHostServices validatorServices;
|
||||
private boolean assumeValidRestReferences;
|
||||
|
@ -380,8 +379,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
private boolean validateValueSetCodesOnTxServer = true;
|
||||
private QuestionnaireMode questionnaireMode;
|
||||
|
||||
public InstanceValidator(IWorkerContext theContext, IEvaluationContext hostServices) {
|
||||
super(theContext);
|
||||
public InstanceValidator(IWorkerContext theContext, IEvaluationContext hostServices, XVerExtensionManager xverManager) {
|
||||
super(theContext, xverManager);
|
||||
this.externalHostServices = hostServices;
|
||||
this.profileUtilities = new ProfileUtilities(theContext, null, null);
|
||||
fpe = new FHIRPathEngine(context);
|
||||
|
@ -1504,30 +1503,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
StructureDefinition ex = Utilities.isAbsoluteUrl(url) ? context.fetchResource(StructureDefinition.class, url) : null;
|
||||
timeTracker.sd(t);
|
||||
if (ex == null) {
|
||||
if (xverManager == null) {
|
||||
xverManager = new XVerExtensionManager(context);
|
||||
}
|
||||
if (xverManager.matchingUrl(url)) {
|
||||
switch (xverManager.status(url)) {
|
||||
case BadVersion:
|
||||
rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXTENSION_EXT_VERSION_INVALID, url, xverManager.getVersion(url));
|
||||
break;
|
||||
case Unknown:
|
||||
rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXTENSION_EXT_VERSION_INVALIDID, url, xverManager.getElementId(url));
|
||||
break;
|
||||
case Invalid:
|
||||
rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXTENSION_EXT_VERSION_NOCHANGE, url, xverManager.getElementId(url));
|
||||
break;
|
||||
case Valid:
|
||||
ex = xverManager.makeDefinition(url);
|
||||
context.generateSnapshot(ex);
|
||||
context.cacheResource(ex);
|
||||
break;
|
||||
default:
|
||||
rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXTENSION_EXT_VERSION_INTERNAL, url);
|
||||
break;
|
||||
}
|
||||
} else if (extensionUrl != null && !isAbsolute(url)) {
|
||||
ex = getXverExt(errors, path, element, url);
|
||||
}
|
||||
if (ex == null) {
|
||||
if (extensionUrl != null && !isAbsolute(url)) {
|
||||
if (extensionUrl.equals(profile.getUrl())) {
|
||||
rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", hasExtensionSlice(profile, url), I18nConstants.EXTENSION_EXT_SUBEXTENSION_INVALID, url, profile.getUrl());
|
||||
}
|
||||
|
@ -1568,6 +1547,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
return ex;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private boolean hasExtensionSlice(StructureDefinition profile, String sliceName) {
|
||||
for (ElementDefinition ed : profile.getSnapshot().getElement()) {
|
||||
|
@ -1951,7 +1931,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
boolean found;
|
||||
try {
|
||||
found = isDefinitionURL(url) || (allowExamples && (url.contains("example.org") || url.contains("acme.com")) || url.contains("acme.org")) || (url.startsWith("http://hl7.org/fhir/tools")) || fetcher.resolveURL(appContext, path, url) ||
|
||||
SpecialExtensions.isKnownExtension(url);
|
||||
SpecialExtensions.isKnownExtension(url) || isXverUrl(url);
|
||||
} catch (IOException e1) {
|
||||
found = false;
|
||||
}
|
||||
|
@ -2446,25 +2426,26 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
|
||||
if (pol.checkExists()) {
|
||||
if (we == null) {
|
||||
if (fetcher == null) {
|
||||
if (!refType.equals("contained"))
|
||||
if (!refType.equals("contained")) {
|
||||
if (fetcher == null) {
|
||||
throw new FHIRException(context.formatMessage(I18nConstants.RESOURCE_RESOLUTION_SERVICES_NOT_PROVIDED));
|
||||
} else {
|
||||
Element ext = null;
|
||||
if (fetchCache.containsKey(ref)) {
|
||||
ext = fetchCache.get(ref);
|
||||
} else {
|
||||
try {
|
||||
ext = fetcher.fetch(hostContext.getAppContext(), ref);
|
||||
} catch (IOException e) {
|
||||
throw new FHIRException(e);
|
||||
}
|
||||
if (ext != null) {
|
||||
setParents(ext);
|
||||
fetchCache.put(ref, ext);
|
||||
Element ext = null;
|
||||
if (fetchCache.containsKey(ref)) {
|
||||
ext = fetchCache.get(ref);
|
||||
} else {
|
||||
try {
|
||||
ext = fetcher.fetch(hostContext.getAppContext(), ref);
|
||||
} catch (IOException e) {
|
||||
throw new FHIRException(e);
|
||||
}
|
||||
if (ext != null) {
|
||||
setParents(ext);
|
||||
fetchCache.put(ref, ext);
|
||||
}
|
||||
}
|
||||
we = ext == null ? null : makeExternalRef(ext, path);
|
||||
}
|
||||
we = ext == null ? null : makeExternalRef(ext, path);
|
||||
}
|
||||
}
|
||||
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, (allowExamples && (ref.contains("example.org") || ref.contains("acme.com"))) || (we != null || pol == ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS), I18nConstants.REFERENCE_REF_CANTRESOLVE, ref);
|
||||
|
@ -3760,10 +3741,29 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
StructureDefinition sdt = context.fetchResource(StructureDefinition.class, vu.getUrl());
|
||||
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_THIS_VERSION_OTHER, sdt == null ? "null" : sdt.getType());
|
||||
}
|
||||
} else if (warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", sd != null, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN, profile.primitiveValue())) {
|
||||
signpost(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), !crumbTrails, I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST_META, sd.getUrl());
|
||||
stack.resetIds();
|
||||
startInner(hostContext, errors, resource, element, sd, stack, false);
|
||||
} else {
|
||||
if (sd == null) {
|
||||
// we'll try fetching it directly from it's source, but this is likely to fail later even if the resolution succeeds
|
||||
if (fetcher == null) {
|
||||
warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN, profile.primitiveValue());
|
||||
} else if (!fetcher.fetchesCanonicalResource(profile.primitiveValue())) {
|
||||
warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN_NOT_POLICY, profile.primitiveValue());
|
||||
} else {
|
||||
try {
|
||||
sd = (StructureDefinition) fetcher.fetchCanonicalResource(profile.primitiveValue());
|
||||
} catch (Exception e) {
|
||||
warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN_ERROR, profile.primitiveValue(), e.getMessage());
|
||||
}
|
||||
if (sd != null) {
|
||||
context.cacheResource(sd);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sd != null) {
|
||||
signpost(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), !crumbTrails, I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST_META, sd.getUrl());
|
||||
stack.resetIds();
|
||||
startInner(hostContext, errors, resource, element, sd, stack, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
i++;
|
||||
|
@ -3869,27 +3869,27 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
public void checkSpecials(ValidatorHostContext hostContext, List<ValidationMessage> errors, Element element, NodeStack stack, boolean checkSpecials) {
|
||||
// specific known special validations
|
||||
if (element.getType().equals(BUNDLE)) {
|
||||
new BundleValidator(context, serverBase, this).validateBundle(errors, element, stack, checkSpecials, hostContext);
|
||||
new BundleValidator(context, serverBase, this, xverManager).validateBundle(errors, element, stack, checkSpecials, hostContext);
|
||||
} else if (element.getType().equals("Observation")) {
|
||||
validateObservation(errors, element, stack);
|
||||
} else if (element.getType().equals("Questionnaire")) {
|
||||
new QuestionnaireValidator(context, myEnableWhenEvaluator, fpe, timeTracker, questionnaireMode).validateQuestionannaire(errors, element, element, stack);
|
||||
new QuestionnaireValidator(context, myEnableWhenEvaluator, fpe, timeTracker, questionnaireMode, xverManager).validateQuestionannaire(errors, element, element, stack);
|
||||
} else if (element.getType().equals("QuestionnaireResponse")) {
|
||||
new QuestionnaireValidator(context, myEnableWhenEvaluator, fpe, timeTracker, questionnaireMode).validateQuestionannaireResponse(hostContext, errors, element, stack);
|
||||
new QuestionnaireValidator(context, myEnableWhenEvaluator, fpe, timeTracker, questionnaireMode, xverManager).validateQuestionannaireResponse(hostContext, errors, element, stack);
|
||||
} else if (element.getType().equals("Measure")) {
|
||||
new MeasureValidator(context, timeTracker).validateMeasure(hostContext, errors, element, stack);
|
||||
new MeasureValidator(context, timeTracker, xverManager).validateMeasure(hostContext, errors, element, stack);
|
||||
} else if (element.getType().equals("MeasureReport")) {
|
||||
new MeasureValidator(context, timeTracker).validateMeasureReport(hostContext, errors, element, stack);
|
||||
new MeasureValidator(context, timeTracker, xverManager).validateMeasureReport(hostContext, errors, element, stack);
|
||||
} else if (element.getType().equals("CapabilityStatement")) {
|
||||
validateCapabilityStatement(errors, element, stack);
|
||||
} else if (element.getType().equals("CodeSystem")) {
|
||||
new CodeSystemValidator(context, timeTracker).validateCodeSystem(errors, element, stack);
|
||||
new CodeSystemValidator(context, timeTracker, xverManager).validateCodeSystem(errors, element, stack);
|
||||
} else if (element.getType().equals("SearchParameter")) {
|
||||
new SearchParameterValidator(context, timeTracker, fpe).validateSearchParameter(errors, element, stack);
|
||||
new SearchParameterValidator(context, timeTracker, fpe, xverManager).validateSearchParameter(errors, element, stack);
|
||||
} else if (element.getType().equals("StructureDefinition")) {
|
||||
new StructureDefinitionValidator(context, timeTracker, fpe, wantCheckSnapshotUnchanged).validateStructureDefinition(errors, element, stack);
|
||||
new StructureDefinitionValidator(context, timeTracker, fpe, wantCheckSnapshotUnchanged, xverManager).validateStructureDefinition(errors, element, stack);
|
||||
} else if (element.getType().equals("ValueSet")) {
|
||||
new ValueSetValidator(context, timeTracker, this).validateValueSet(errors, element, stack);
|
||||
new ValueSetValidator(context, timeTracker, this, xverManager).validateValueSet(errors, element, stack);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -36,12 +36,18 @@ import org.hl7.fhir.exceptions.FHIRException;
|
|||
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||
import org.hl7.fhir.r5.context.SimpleWorkerContext.IValidatorFactory;
|
||||
import org.hl7.fhir.r5.utils.IResourceValidator;
|
||||
import org.hl7.fhir.r5.utils.XVerExtensionManager;
|
||||
|
||||
public class InstanceValidatorFactory implements IValidatorFactory {
|
||||
|
||||
@Override
|
||||
public IResourceValidator makeValidator(IWorkerContext ctxt, XVerExtensionManager xverManager) throws FHIRException {
|
||||
return new InstanceValidator(ctxt, null, xverManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IResourceValidator makeValidator(IWorkerContext ctxt) throws FHIRException {
|
||||
return new InstanceValidator(ctxt, null);
|
||||
return new InstanceValidator(ctxt, null, null);
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -13,6 +13,7 @@ import org.hl7.fhir.r5.elementmodel.Element;
|
|||
import org.hl7.fhir.r5.model.Constants;
|
||||
import org.hl7.fhir.r5.model.Enumerations.FHIRVersion;
|
||||
import org.hl7.fhir.r5.model.StructureDefinition;
|
||||
import org.hl7.fhir.r5.utils.XVerExtensionManager;
|
||||
import org.hl7.fhir.r5.utils.IResourceValidator.BundleValidationRule;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.VersionUtilities;
|
||||
|
@ -31,8 +32,8 @@ public class BundleValidator extends BaseValidator{
|
|||
private String serverBase;
|
||||
private InstanceValidator validator;
|
||||
|
||||
public BundleValidator(IWorkerContext context, String serverBase, InstanceValidator validator) {
|
||||
super(context);
|
||||
public BundleValidator(IWorkerContext context, String serverBase, InstanceValidator validator, XVerExtensionManager xverManager) {
|
||||
super(context, xverManager);
|
||||
this.serverBase = serverBase;
|
||||
this.validator = validator;
|
||||
}
|
||||
|
@ -42,7 +43,7 @@ public class BundleValidator extends BaseValidator{
|
|||
bundle.getNamedChildren(ENTRY, entries);
|
||||
String type = bundle.getNamedChildValue(TYPE);
|
||||
type = StringUtils.defaultString(type);
|
||||
|
||||
|
||||
if (entries.size() == 0) {
|
||||
rule(errors, IssueType.INVALID, stack.getLiteralPath(), !(type.equals(DOCUMENT) || type.equals(MESSAGE)), I18nConstants.BUNDLE_BUNDLE_ENTRY_NOFIRST);
|
||||
} else {
|
||||
|
@ -79,6 +80,8 @@ public class BundleValidator extends BaseValidator{
|
|||
int count = 0;
|
||||
Map<String, Integer> counter = new HashMap<>();
|
||||
|
||||
boolean fullUrlOptional = Utilities.existsInList(type, "transaction", "transaction-response", "batch", "batch-response");
|
||||
|
||||
for (Element entry : entries) {
|
||||
NodeStack estack = stack.push(entry, count, null, null);
|
||||
String fullUrl = entry.getNamedChildValue(FULL_URL);
|
||||
|
@ -90,6 +93,9 @@ public class BundleValidator extends BaseValidator{
|
|||
rule(errors, IssueType.INVALID, entry.line(), entry.col(), stack.addToLiteralPath(ENTRY, PATH_ARG), !url.equals(fullUrl) || serverBase == null || (url.equals(Utilities.pathURL(serverBase, entry.getNamedChild(RESOURCE).fhirType(), id))), I18nConstants.BUNDLE_BUNDLE_ENTRY_CANONICAL, url, fullUrl);
|
||||
}
|
||||
|
||||
if (!VersionUtilities.isR2Ver(context.getVersion())) {
|
||||
rule(errors, IssueType.INVALID, entry.line(), entry.col(), estack.getLiteralPath(), fullUrlOptional || fullUrl != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_FULLURL_REQUIRED);
|
||||
}
|
||||
// check bundle profile requests
|
||||
if (entry.hasChild(RESOURCE)) {
|
||||
String rtype = entry.getNamedChild(RESOURCE).fhirType();
|
||||
|
|
|
@ -6,6 +6,7 @@ import org.hl7.fhir.exceptions.FHIRException;
|
|||
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||
import org.hl7.fhir.r5.elementmodel.Element;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
import org.hl7.fhir.r5.utils.XVerExtensionManager;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.i18n.I18nConstants;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||
|
@ -17,8 +18,8 @@ import org.hl7.fhir.validation.instance.utils.NodeStack;
|
|||
|
||||
public class CodeSystemValidator extends BaseValidator {
|
||||
|
||||
public CodeSystemValidator(IWorkerContext context, TimeTracker timeTracker) {
|
||||
super(context);
|
||||
public CodeSystemValidator(IWorkerContext context, TimeTracker timeTracker, XVerExtensionManager xverManager) {
|
||||
super(context, xverManager);
|
||||
source = Source.InstanceValidator;
|
||||
this.timeTracker = timeTracker;
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.hl7.fhir.r5.model.Measure.MeasureGroupPopulationComponent;
|
|||
import org.hl7.fhir.r5.model.Measure.MeasureGroupStratifierComponent;
|
||||
import org.hl7.fhir.r5.model.Resource;
|
||||
import org.hl7.fhir.r5.renderers.DataRenderer;
|
||||
import org.hl7.fhir.r5.utils.XVerExtensionManager;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.i18n.I18nConstants;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||
|
@ -40,8 +41,8 @@ import org.w3c.dom.Document;
|
|||
|
||||
public class MeasureValidator extends BaseValidator {
|
||||
|
||||
public MeasureValidator(IWorkerContext context, TimeTracker timeTracker) {
|
||||
super(context);
|
||||
public MeasureValidator(IWorkerContext context, TimeTracker timeTracker, XVerExtensionManager xverManager) {
|
||||
super(context, xverManager);
|
||||
source = Source.InstanceValidator;
|
||||
this.timeTracker = timeTracker;
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ import org.hl7.fhir.r5.model.StringType;
|
|||
import org.hl7.fhir.r5.model.TimeType;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
import org.hl7.fhir.r5.utils.FHIRPathEngine;
|
||||
import org.hl7.fhir.r5.utils.XVerExtensionManager;
|
||||
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.i18n.I18nConstants;
|
||||
|
@ -58,8 +59,8 @@ public class QuestionnaireValidator extends BaseValidator {
|
|||
private FHIRPathEngine fpe;
|
||||
private QuestionnaireMode questionnaireMode;
|
||||
|
||||
public QuestionnaireValidator(IWorkerContext context, EnableWhenEvaluator myEnableWhenEvaluator, FHIRPathEngine fpe, TimeTracker timeTracker, QuestionnaireMode questionnaireMode) {
|
||||
super(context);
|
||||
public QuestionnaireValidator(IWorkerContext context, EnableWhenEvaluator myEnableWhenEvaluator, FHIRPathEngine fpe, TimeTracker timeTracker, QuestionnaireMode questionnaireMode, XVerExtensionManager xverManager) {
|
||||
super(context, xverManager);
|
||||
source = Source.InstanceValidator;
|
||||
this.myEnableWhenEvaluator = myEnableWhenEvaluator;
|
||||
this.fpe = fpe;
|
||||
|
|
|
@ -12,6 +12,7 @@ import org.hl7.fhir.r5.model.ExpressionNode.Kind;
|
|||
import org.hl7.fhir.r5.model.ExpressionNode.Operation;
|
||||
import org.hl7.fhir.r5.model.SearchParameter;
|
||||
import org.hl7.fhir.r5.utils.FHIRPathEngine;
|
||||
import org.hl7.fhir.r5.utils.XVerExtensionManager;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.i18n.I18nConstants;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||
|
@ -34,8 +35,8 @@ public class SearchParameterValidator extends BaseValidator {
|
|||
|
||||
private FHIRPathEngine fpe;
|
||||
|
||||
public SearchParameterValidator(IWorkerContext context, TimeTracker timeTracker, FHIRPathEngine fpe) {
|
||||
super(context);
|
||||
public SearchParameterValidator(IWorkerContext context, TimeTracker timeTracker, FHIRPathEngine fpe, XVerExtensionManager xverManager) {
|
||||
super(context, xverManager);
|
||||
source = Source.InstanceValidator;
|
||||
this.fpe = fpe;
|
||||
this.timeTracker = timeTracker;
|
||||
|
|
|
@ -20,9 +20,11 @@ import org.hl7.fhir.r5.formats.IParser.OutputStyle;
|
|||
import org.hl7.fhir.r5.model.ElementDefinition;
|
||||
import org.hl7.fhir.r5.model.ExpressionNode;
|
||||
import org.hl7.fhir.r5.model.StructureDefinition;
|
||||
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
|
||||
import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
|
||||
import org.hl7.fhir.r5.utils.FHIRPathEngine;
|
||||
import org.hl7.fhir.r5.utils.ToolingExtensions;
|
||||
import org.hl7.fhir.r5.utils.XVerExtensionManager;
|
||||
import org.hl7.fhir.utilities.VersionUtilities;
|
||||
import org.hl7.fhir.utilities.i18n.I18nConstants;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||
|
@ -46,8 +48,8 @@ public class StructureDefinitionValidator extends BaseValidator {
|
|||
private FHIRPathEngine fpe;
|
||||
private boolean wantCheckSnapshotUnchanged;
|
||||
|
||||
public StructureDefinitionValidator(IWorkerContext context, TimeTracker timeTracker, FHIRPathEngine fpe, boolean wantCheckSnapshotUnchanged) {
|
||||
super(context);
|
||||
public StructureDefinitionValidator(IWorkerContext context, TimeTracker timeTracker, FHIRPathEngine fpe, boolean wantCheckSnapshotUnchanged, XVerExtensionManager xverManager) {
|
||||
super(context, xverManager);
|
||||
source = Source.InstanceValidator;
|
||||
this.fpe = fpe;
|
||||
this.timeTracker = timeTracker;
|
||||
|
@ -66,6 +68,7 @@ public class StructureDefinitionValidator extends BaseValidator {
|
|||
if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) {
|
||||
List<ValidationMessage> msgs = new ArrayList<>();
|
||||
ProfileUtilities pu = new ProfileUtilities(context, msgs, null);
|
||||
pu.setXver(xverManager);
|
||||
pu.generateSnapshot(base, sd, sd.getUrl(), "http://hl7.org/fhir", sd.getName());
|
||||
if (msgs.size() > 0) {
|
||||
for (ValidationMessage msg : msgs) {
|
||||
|
@ -137,12 +140,50 @@ public class StructureDefinitionValidator extends BaseValidator {
|
|||
}
|
||||
if (code != null) {
|
||||
List<Element> profiles = type.getChildrenByName("profile");
|
||||
for (Element profile : profiles) {
|
||||
validateTypeProfile(errors, profile, code, stack.push(type, -1, null, null));
|
||||
if (VersionUtilities.isR2Ver(context.getVersion()) || VersionUtilities.isR2BVer(context.getVersion()) ) {
|
||||
for (Element profile : profiles) {
|
||||
validateProfileTypeOrTarget(errors, profile, code, stack.push(profile, -1, null, null), path);
|
||||
}
|
||||
|
||||
} else {
|
||||
for (Element profile : profiles) {
|
||||
validateTypeProfile(errors, profile, code, stack.push(profile, -1, null, null), path);
|
||||
}
|
||||
profiles = type.getChildrenByName("targetProfile");
|
||||
for (Element profile : profiles) {
|
||||
validateTargetProfile(errors, profile, code, stack.push(profile, -1, null, null), path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateProfileTypeOrTarget(List<ValidationMessage> errors, Element profile, String code, NodeStack stack, String path) {
|
||||
String p = profile.primitiveValue();
|
||||
StructureDefinition sd = context.fetchResource(StructureDefinition.class, p);
|
||||
if (code.equals("Reference")) {
|
||||
if (warning(errors, IssueType.EXCEPTION, stack.getLiteralPath(), sd != null, I18nConstants.SD_ED_TYPE_PROFILE_UNKNOWN, p)) {
|
||||
String t = determineBaseType(sd);
|
||||
if (t == null) {
|
||||
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), code.equals(t), I18nConstants.SD_ED_TYPE_PROFILE_NOTYPE, p);
|
||||
} else {
|
||||
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), sd.getKind() == StructureDefinitionKind.RESOURCE, I18nConstants.SD_ED_TYPE_PROFILE_WRONG, p, t, code, path);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (sd == null ) {
|
||||
sd = getXverExt(errors, stack.getLiteralPath(), profile, p);
|
||||
}
|
||||
if (warning(errors, IssueType.EXCEPTION, stack.getLiteralPath(), sd != null, I18nConstants.SD_ED_TYPE_PROFILE_UNKNOWN, p)) {
|
||||
String t = determineBaseType(sd);
|
||||
if (t == null) {
|
||||
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), code.equals(t), I18nConstants.SD_ED_TYPE_PROFILE_NOTYPE, p);
|
||||
} else {
|
||||
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), isInstanceOf(t, code), I18nConstants.SD_ED_TYPE_PROFILE_WRONG, p, t, code, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getTypeCodeFromSD(StructureDefinition sd, String path) {
|
||||
ElementDefinition ed = null;
|
||||
for (ElementDefinition t : sd.getSnapshot().getElement()) {
|
||||
|
@ -157,19 +198,48 @@ public class StructureDefinitionValidator extends BaseValidator {
|
|||
return ed != null && ed.getType().size() == 1 ? ed.getTypeFirstRep().getCode() : null;
|
||||
}
|
||||
|
||||
private void validateTypeProfile(List<ValidationMessage> errors, Element profile, String code, NodeStack stack) {
|
||||
private void validateTypeProfile(List<ValidationMessage> errors, Element profile, String code, NodeStack stack, String path) {
|
||||
String p = profile.primitiveValue();
|
||||
StructureDefinition sd = context.fetchResource(StructureDefinition.class, p);
|
||||
if (sd == null ) {
|
||||
sd = getXverExt(errors, stack.getLiteralPath(), profile, p);
|
||||
}
|
||||
if (warning(errors, IssueType.EXCEPTION, stack.getLiteralPath(), sd != null, I18nConstants.SD_ED_TYPE_PROFILE_UNKNOWN, p)) {
|
||||
String t = determineBaseType(sd);
|
||||
if (t == null) {
|
||||
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), code.equals(t), I18nConstants.SD_ED_TYPE_PROFILE_NOTYPE, p);
|
||||
} else {
|
||||
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), isInstanceOf(t, code), I18nConstants.SD_ED_TYPE_PROFILE_WRONG, p, t, code);
|
||||
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), isInstanceOf(t, code), I18nConstants.SD_ED_TYPE_PROFILE_WRONG, p, t, code, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateTargetProfile(List<ValidationMessage> errors, Element profile, String code, NodeStack stack, String path) {
|
||||
String p = profile.primitiveValue();
|
||||
StructureDefinition sd = context.fetchResource(StructureDefinition.class, p);
|
||||
if (code.equals("Reference")) {
|
||||
if (warning(errors, IssueType.EXCEPTION, stack.getLiteralPath(), sd != null, I18nConstants.SD_ED_TYPE_PROFILE_UNKNOWN, p)) {
|
||||
String t = determineBaseType(sd);
|
||||
if (t == null) {
|
||||
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), code.equals(t), I18nConstants.SD_ED_TYPE_PROFILE_NOTYPE, p);
|
||||
} else {
|
||||
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), sd.getKind() == StructureDefinitionKind.RESOURCE, I18nConstants.SD_ED_TYPE_PROFILE_WRONG, p, t, code, path);
|
||||
}
|
||||
}
|
||||
} else if (code.equals("canonical")) {
|
||||
if (warning(errors, IssueType.EXCEPTION, stack.getLiteralPath(), sd != null, I18nConstants.SD_ED_TYPE_PROFILE_UNKNOWN, p)) {
|
||||
String t = determineBaseType(sd);
|
||||
if (t == null) {
|
||||
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), code.equals(t), I18nConstants.SD_ED_TYPE_PROFILE_NOTYPE, p);
|
||||
} else {
|
||||
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), VersionUtilities.getCanonicalResourceNames(context.getVersion()).contains(t), I18nConstants.SD_ED_TYPE_PROFILE_WRONG, p, t, code, path);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.SD_ED_TYPE_NO_TARGET_PROFILE, code);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInstanceOf(String t, String code) {
|
||||
StructureDefinition sd = context.fetchTypeDefinition(t);
|
||||
while (sd != null) {
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.hl7.fhir.r5.model.Coding;
|
|||
import org.hl7.fhir.r5.model.Resource;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
import org.hl7.fhir.r5.terminologies.ValueSetExpander.TerminologyServiceErrorClass;
|
||||
import org.hl7.fhir.r5.utils.XVerExtensionManager;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.VersionUtilities;
|
||||
import org.hl7.fhir.utilities.i18n.I18nConstants;
|
||||
|
@ -42,8 +43,8 @@ public class ValueSetValidator extends BaseValidator {
|
|||
|
||||
private InstanceValidator parent;
|
||||
|
||||
public ValueSetValidator(IWorkerContext context, TimeTracker timeTracker, InstanceValidator parent) {
|
||||
super(context);
|
||||
public ValueSetValidator(IWorkerContext context, TimeTracker timeTracker, InstanceValidator parent, XVerExtensionManager xverManager) {
|
||||
super(context, xverManager);
|
||||
source = Source.InstanceValidator;
|
||||
this.timeTracker = timeTracker;
|
||||
this.parent = parent;
|
||||
|
|
|
@ -40,7 +40,9 @@ import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintCompon
|
|||
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
|
||||
import org.hl7.fhir.r5.model.StructureDefinition;
|
||||
import org.hl7.fhir.r5.utils.FHIRPathEngine;
|
||||
import org.hl7.fhir.r5.utils.XVerExtensionManager;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.i18n.I18nConstants;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
|
||||
import org.hl7.fhir.validation.BaseValidator;
|
||||
|
@ -50,8 +52,8 @@ public class ProfileValidator extends BaseValidator {
|
|||
private boolean checkAggregation = false;
|
||||
private boolean checkMustSupport = false;
|
||||
|
||||
public ProfileValidator(IWorkerContext context) {
|
||||
super(context);
|
||||
public ProfileValidator(IWorkerContext context, XVerExtensionManager xverManager) {
|
||||
super(context, xverManager);
|
||||
}
|
||||
|
||||
public boolean isCheckAggregation() {
|
||||
|
@ -138,7 +140,12 @@ public class ProfileValidator extends BaseValidator {
|
|||
if (!ec.getType().isEmpty() && "Extension".equals(ec.getType().get(0).getWorkingCode()) && ec.getType().get(0).hasProfile()) {
|
||||
String url = ec.getType().get(0).getProfile().get(0).getValue();
|
||||
StructureDefinition defn = context.fetchResource(StructureDefinition.class, url);
|
||||
if (defn == null) {
|
||||
defn = getXverExt(profile, errors, url);
|
||||
}
|
||||
rule(errors, IssueType.BUSINESSRULE, profile.getId(), defn != null, "Unable to find Extension '"+url+"' referenced at "+profile.getUrl()+" "+kind+" "+ec.getPath()+" ("+ec.getSliceName()+")");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -52,10 +52,12 @@ import org.hl7.fhir.r5.utils.IResourceValidator.ReferenceValidationPolicy;
|
|||
import org.hl7.fhir.utilities.TextFile;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.VersionUtilities;
|
||||
import org.hl7.fhir.utilities.json.JSONUtil;
|
||||
import org.hl7.fhir.utilities.npm.NpmPackage;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
|
||||
import org.hl7.fhir.validation.ValidationEngine;
|
||||
import org.hl7.fhir.validation.cli.services.StandAloneValidatorFetcher;
|
||||
import org.hl7.fhir.validation.instance.InstanceValidator;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
|
@ -81,8 +83,9 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
|
|||
|
||||
Map<String, JsonObject> examples = new HashMap<String, JsonObject>();
|
||||
manifest = (JsonObject) new com.google.gson.JsonParser().parse(contents);
|
||||
for (Entry<String, JsonElement> e : manifest.getAsJsonObject("test-cases").entrySet()) {
|
||||
examples.put(e.getKey(), e.getValue().getAsJsonObject());
|
||||
for (JsonElement e : manifest.getAsJsonArray("test-cases")) {
|
||||
JsonObject o = (JsonObject) e;
|
||||
examples.put(JSONUtil.str(o, "name"), o);
|
||||
}
|
||||
|
||||
List<String> names = new ArrayList<String>(examples.size());
|
||||
|
@ -145,7 +148,6 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
|
|||
throw new Exception("unknown version " + version);
|
||||
}
|
||||
vCurr = ve.get(version);
|
||||
vCurr.setFetcher(this);
|
||||
if (TestingUtilities.fcontexts == null) {
|
||||
TestingUtilities.fcontexts = new HashMap<>();
|
||||
}
|
||||
|
@ -154,11 +156,17 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
|
|||
if (content.has("use-test") && !content.get("use-test").getAsBoolean())
|
||||
return;
|
||||
|
||||
String testCaseContent = TestingUtilities.loadTestResource("validator", name);
|
||||
String testCaseContent = TestingUtilities.loadTestResource("validator", JSONUtil.str(content, "file"));
|
||||
InstanceValidator val = vCurr.getValidator();
|
||||
val.setWantCheckSnapshotUnchanged(true);
|
||||
val.getContext().setClientRetryCount(4);
|
||||
val.setDebug(false);
|
||||
if (content.has("fetcher") && "standalone".equals(JSONUtil.str(content, "fetcher"))) {
|
||||
val.setFetcher(vCurr);
|
||||
vCurr.setFetcher(new StandAloneValidatorFetcher(vCurr.getPcm(), vCurr.getContext(), vCurr));
|
||||
} else {
|
||||
val.setFetcher(this);
|
||||
}
|
||||
if (content.has("allowed-extension-domain"))
|
||||
val.getExtensionDomains().add(content.get("allowed-extension-domain").getAsString());
|
||||
if (content.has("allowed-extension-domains"))
|
||||
|
@ -168,7 +176,6 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
|
|||
val.setValidationLanguage(content.get("language").getAsString());
|
||||
else
|
||||
val.setValidationLanguage(null);
|
||||
val.setFetcher(this);
|
||||
if (content.has("packages")) {
|
||||
for (JsonElement e : content.getAsJsonArray("packages")) {
|
||||
String n = e.getAsString();
|
||||
|
@ -226,7 +233,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
|
|||
if (content.has("logical")==false) {
|
||||
val.setAssumeValidRestReferences(content.has("assumeValidRestReferences") ? content.get("assumeValidRestReferences").getAsBoolean() : false);
|
||||
System.out.println(String.format("Start Validating (%d to set up)", (System.nanoTime() - setup) / 1000000));
|
||||
if (name.endsWith(".json"))
|
||||
if (JSONUtil.str(content, "file").endsWith(".json"))
|
||||
val.validate(null, errors, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), FhirFormat.JSON);
|
||||
else
|
||||
val.validate(null, errors, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), FhirFormat.XML);
|
||||
|
@ -258,7 +265,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
|
|||
val.getContext().cacheResource(sd);
|
||||
val.setAssumeValidRestReferences(profile.has("assumeValidRestReferences") ? profile.get("assumeValidRestReferences").getAsBoolean() : false);
|
||||
List<ValidationMessage> errorsProfile = new ArrayList<ValidationMessage>();
|
||||
if (name.endsWith(".json"))
|
||||
if (JSONUtil.str(content, "file").endsWith(".json"))
|
||||
val.validate(null, errorsProfile, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), FhirFormat.JSON, asSdList(sd));
|
||||
else
|
||||
val.validate(null, errorsProfile, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), FhirFormat.XML, asSdList(sd));
|
||||
|
@ -524,4 +531,14 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
|
|||
URLConnection c = url.openConnection();
|
||||
return TextFile.streamToBytes(c.getInputStream());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CanonicalResource fetchCanonicalResource(String url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean fetchesCanonicalResource(String url) {
|
||||
return false;
|
||||
}
|
||||
}
|
6
pom.xml
6
pom.xml
|
@ -11,15 +11,15 @@
|
|||
<!--
|
||||
Note: Version of this project and the version of HAPI FHIR are not locked to
|
||||
each other. It is fine to bump the point version of this POM without affecting
|
||||
HAPI FHIR.
|
||||
HAPI FHIR
|
||||
-->
|
||||
<artifactId>org.hl7.fhir.core</artifactId>
|
||||
<version>5.1.23-SNAPSHOT</version>
|
||||
<version>5.2.8-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<properties>
|
||||
<hapi_fhir_version>5.1.0</hapi_fhir_version>
|
||||
<validator_test_case_version>1.1.50</validator_test_case_version>
|
||||
<validator_test_case_version>1.1.55</validator_test_case_version>
|
||||
<junit_jupiter_version>5.6.2</junit_jupiter_version>
|
||||
<maven_surefire_version>3.0.0-M4</maven_surefire_version>
|
||||
<jacoco_version>0.8.5</jacoco_version>
|
||||
|
|
Loading…
Reference in New Issue