From cacce9b05ea2201762de1a8d25b0478b3ed22f59 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 9 Oct 2024 23:10:09 +0800 Subject: [PATCH 01/20] fix up processing of server responses --- .../r4/utils/OperationOutcomeUtilities.java | 17 +++ .../client/network/FhirRequestBuilder.java | 121 ++++++++---------- .../r4b/utils/OperationOutcomeUtilities.java | 15 +++ .../client/network/FhirRequestBuilder.java | 121 +++++++++--------- .../r5/utils/OperationOutcomeUtilities.java | 15 +++ .../client/network/FhirRequestBuilder.java | 116 +++++++++-------- .../hl7/fhir/utilities/xhtml/XhtmlUtils.java | 16 +++ 7 files changed, 244 insertions(+), 177 deletions(-) create mode 100644 org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlUtils.java diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/OperationOutcomeUtilities.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/OperationOutcomeUtilities.java index 1272a4aa8..b0172f8bf 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/OperationOutcomeUtilities.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/OperationOutcomeUtilities.java @@ -31,11 +31,14 @@ package org.hl7.fhir.r4.utils; import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.Narrative.NarrativeStatus; import org.hl7.fhir.r4.model.OperationOutcome; import org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity; import org.hl7.fhir.r4.model.OperationOutcome.IssueType; import org.hl7.fhir.r4.model.OperationOutcome.OperationOutcomeIssueComponent; import org.hl7.fhir.utilities.validation.ValidationMessage; +import org.hl7.fhir.utilities.xhtml.NodeType; +import org.hl7.fhir.utilities.xhtml.XhtmlNode; public class OperationOutcomeUtilities { @@ -142,4 +145,18 @@ public class OperationOutcomeUtilities { } return IssueType.NULL; } + + + public static OperationOutcome outcomeFromTextError(String text) { + OperationOutcome oo = new OperationOutcome(); + oo.getText().setStatus(NarrativeStatus.GENERATED); + oo.getText().setDiv(new XhtmlNode(NodeType.Element, "div")); + oo.getText().getDiv().tx(text); + OperationOutcomeIssueComponent issue = oo.addIssue(); + issue.setSeverity(IssueSeverity.ERROR); + issue.setCode(IssueType.EXCEPTION); + issue.getDetails().setText(text); + return oo; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilder.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilder.java index 59c596d08..fb2fd464c 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilder.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilder.java @@ -1,5 +1,7 @@ package org.hl7.fhir.r4.utils.client.network; +import static org.hl7.fhir.r4.utils.OperationOutcomeUtilities.outcomeFromTextError; + import java.io.IOException; import java.util.List; import java.util.Map; @@ -15,20 +17,19 @@ import org.hl7.fhir.r4.formats.XmlParser; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.OperationOutcome; import org.hl7.fhir.r4.model.Resource; +import org.hl7.fhir.r4.utils.OperationOutcomeUtilities; import org.hl7.fhir.r4.utils.ResourceUtilities; import org.hl7.fhir.r4.utils.client.EFhirClientException; import org.hl7.fhir.r4.utils.client.ResourceFormat; import org.hl7.fhir.utilities.MimeType; -import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.settings.FhirSettings; +import org.hl7.fhir.utilities.xhtml.XhtmlUtils; import okhttp3.Authenticator; import okhttp3.Credentials; import okhttp3.Headers; -import okhttp3.Headers.Builder; import okhttp3.OkHttpClient; import okhttp3.Request; -import okhttp3.RequestBody; import okhttp3.Response; public class FhirRequestBuilder { @@ -242,7 +243,7 @@ public class FhirRequestBuilder { public ResourceRequest execute() throws IOException { formatHeaders(httpRequest, resourceFormat, headers); Response response = getHttpClient().newCall(httpRequest.build()).execute(); - T resource = unmarshalReference(response, resourceFormat); + T resource = unmarshalReference(response, resourceFormat, null); return new ResourceRequest(resource, response.code(), getLocationHeader(response.headers())); } @@ -256,80 +257,64 @@ public class FhirRequestBuilder { * Unmarshalls a resource from the response stream. */ @SuppressWarnings("unchecked") - protected T unmarshalReference(Response response, String format) { - T resource = null; - OperationOutcome error = null; - byte[] body = null; - - if (response.body() != null) { - try { - body = response.body().bytes(); - resource = (T) getParser(format).parse(body); - if (resource instanceof OperationOutcome && hasError((OperationOutcome) resource)) { - error = (OperationOutcome) resource; + protected T unmarshalReference(Response response, String format, String resourceType) { + if (response.body() == null) { + return null; + } + Resource resource = null; + try { + String ct = response.header("Content-Type"); + if (ct == null) { + resource = getParser(format).parse(response.body().bytes()); + } else { + switch (ct) { + case "application/json": + case "application/fhir+json": + resource = getParser(ResourceFormat.RESOURCE_JSON.getHeader()).parse(response.body().bytes()); + break; + case "application/xml": + case "application/fhir+xml": + case "text/xml": + resource = getParser(ResourceFormat.RESOURCE_JSON.getHeader()).parse(response.body().bytes()); + break; + case "text/plain": + resource = outcomeFromTextError(response.body().string()); + break; + case "text/html" : + resource = OperationOutcomeUtilities.outcomeFromTextError(XhtmlUtils.convertHtmlToText(response.body().string())); + break; + default: // not sure what else to do? + System.out.println("Got content-type '"+ct+"' from "+source); + resource = OperationOutcomeUtilities.outcomeFromTextError(response.body().string()); } - } catch (IOException ioe) { - throw new EFhirClientException("Error reading Http Response from "+source+": " + ioe.getMessage(), ioe); - } catch (Exception e) { - throw new EFhirClientException("Error parsing response message from "+source+": " + e.getMessage(), e); + } + } catch (IOException ioe) { + throw new EFhirClientException("Error reading Http Response from "+source+":"+ioe.getMessage(), ioe); + } catch (Exception e) { + throw new EFhirClientException("Error parsing response message from "+source+": "+e.getMessage(), e); + } + if (resource instanceof OperationOutcome && !"OperationOutcome".equals(resourceType)) { + OperationOutcome error = (OperationOutcome) resource; + if (hasError((OperationOutcome) resource)) { + throw new EFhirClientException("Error from "+source+": " + ResourceUtilities.getErrorDescription(error), error); + } else { + // umm, weird... } } - - if (error != null) { - String s = ResourceUtilities.getErrorDescription(error); - String reqid = response.header("x-request-id"); - if (reqid == null) { - reqid = response.header("X-Request-Id"); - } - if (reqid != null) { - s = s + " [x-request-id: "+reqid+"]"; - } - System.out.println("Error from "+source+": " + s); - throw new EFhirClientException("Error from "+source+": " + ResourceUtilities.getErrorDescription(error), error); + if (resource == null) { + return null; // shouldn't get here? } - - return resource; + if (resourceType != null && !resource.fhirType().equals(resourceType)) { + throw new EFhirClientException("Error parsing response message from "+source+": Found an "+resource.fhirType()+" looking for a "+resourceType); + } + return (T) 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(); - String contentType = response.header("Content-Type"); - if (body != null) { - if (contentType.contains(ResourceFormat.RESOURCE_XML.getHeader()) - || contentType.contains(ResourceFormat.RESOURCE_JSON.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 from "+source+": a resource was returned instead"); - } - } - } - if (!response.isSuccessful() && feed == null && error == null) { - String text = TextFile.bytesToString(body); - throw new EFhirClientException("Error from "+source+": " + text); - } - } catch (EFhirClientException e) { - throw e; - } catch (IOException ioe) { - throw new EFhirClientException("Error reading Http Response from "+source+":"+ioe.getMessage(), ioe); - } catch (Exception e) { - throw new EFhirClientException("Error parsing response message from "+source+":"+e.getMessage(), e); - } - if (error != null) { - throw new EFhirClientException("Error from "+source+": " + ResourceUtilities.getErrorDescription(error), error); - } - return feed; + return unmarshalReference(response, format, "Bundle"); } /** diff --git a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/OperationOutcomeUtilities.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/OperationOutcomeUtilities.java index 25ae9b664..00f39675a 100644 --- a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/OperationOutcomeUtilities.java +++ b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/OperationOutcomeUtilities.java @@ -33,11 +33,14 @@ import java.util.List; import org.hl7.fhir.r4b.model.CodeableConcept; import org.hl7.fhir.r4b.model.IntegerType; +import org.hl7.fhir.r4b.model.Narrative.NarrativeStatus; import org.hl7.fhir.r4b.model.OperationOutcome; import org.hl7.fhir.r4b.model.OperationOutcome.IssueSeverity; import org.hl7.fhir.r4b.model.OperationOutcome.IssueType; import org.hl7.fhir.r4b.model.OperationOutcome.OperationOutcomeIssueComponent; import org.hl7.fhir.utilities.validation.ValidationMessage; +import org.hl7.fhir.utilities.xhtml.NodeType; +import org.hl7.fhir.utilities.xhtml.XhtmlNode; public class OperationOutcomeUtilities { @@ -154,4 +157,16 @@ public class OperationOutcomeUtilities { } return res; } + + public static OperationOutcome outcomeFromTextError(String text) { + OperationOutcome oo = new OperationOutcome(); + oo.getText().setStatus(NarrativeStatus.GENERATED); + oo.getText().setDiv(new XhtmlNode(NodeType.Element, "div")); + oo.getText().getDiv().tx(text); + OperationOutcomeIssueComponent issue = oo.addIssue(); + issue.setSeverity(IssueSeverity.ERROR); + issue.setCode(IssueType.EXCEPTION); + issue.getDetails().setText(text); + return oo; + } } \ No newline at end of file diff --git a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/FhirRequestBuilder.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/FhirRequestBuilder.java index 24a99b2cf..965322809 100644 --- a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/FhirRequestBuilder.java +++ b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/FhirRequestBuilder.java @@ -1,6 +1,12 @@ package org.hl7.fhir.r4b.utils.client.network; -import okhttp3.*; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nonnull; + import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.r4b.formats.IParser; import org.hl7.fhir.r4b.formats.JsonParser; @@ -8,18 +14,18 @@ import org.hl7.fhir.r4b.formats.XmlParser; import org.hl7.fhir.r4b.model.Bundle; import org.hl7.fhir.r4b.model.OperationOutcome; import org.hl7.fhir.r4b.model.Resource; +import org.hl7.fhir.r4b.utils.OperationOutcomeUtilities; import org.hl7.fhir.r4b.utils.ResourceUtilities; import org.hl7.fhir.r4b.utils.client.EFhirClientException; import org.hl7.fhir.r4b.utils.client.ResourceFormat; -import org.hl7.fhir.utilities.ToolingClientLogger; +import org.hl7.fhir.utilities.xhtml.XhtmlUtils; -import javax.annotation.Nonnull; -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; +import okhttp3.Authenticator; +import okhttp3.Credentials; +import okhttp3.Headers; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; public class FhirRequestBuilder { @@ -227,7 +233,7 @@ public class FhirRequestBuilder { public ResourceRequest execute() throws IOException { formatHeaders(httpRequest, resourceFormat, headers); Response response = getHttpClient().newCall(httpRequest.build()).execute(); - T resource = unmarshalReference(response, resourceFormat); + T resource = unmarshalReference(response, resourceFormat, null); return new ResourceRequest(resource, response.code(), getLocationHeader(response.headers())); } @@ -241,63 +247,64 @@ public class FhirRequestBuilder { * Unmarshalls a resource from the response stream. */ @SuppressWarnings("unchecked") - protected T unmarshalReference(Response response, String format) { - T resource = null; - OperationOutcome error = null; - - if (response.body() != null) { - try { - byte[] body = response.body().bytes(); - resource = (T) getParser(format).parse(body); - if (resource instanceof OperationOutcome && hasError((OperationOutcome) resource)) { - error = (OperationOutcome) resource; + protected T unmarshalReference(Response response, String format, String resourceType) { + if (response.body() == null) { + return null; + } + Resource resource = null; + try { + String ct = response.header("Content-Type"); + if (ct == null) { + resource = getParser(format).parse(response.body().bytes()); + } else { + switch (ct) { + case "application/json": + case "application/fhir+json": + resource = getParser(ResourceFormat.RESOURCE_JSON.getHeader()).parse(response.body().bytes()); + break; + case "application/xml": + case "application/fhir+xml": + case "text/xml": + resource = getParser(ResourceFormat.RESOURCE_JSON.getHeader()).parse(response.body().bytes()); + break; + case "text/plain": + resource = OperationOutcomeUtilities.outcomeFromTextError(response.body().string()); + break; + case "text/html" : + resource = OperationOutcomeUtilities.outcomeFromTextError(XhtmlUtils.convertHtmlToText(response.body().string())); + break; + default: // not sure what else to do? + System.out.println("Got content-type '"+ct+"' from "+source); + resource = OperationOutcomeUtilities.outcomeFromTextError(response.body().string()); } - } catch (IOException ioe) { - throw new EFhirClientException("Error reading Http Response from "+source+": " + ioe.getMessage(), ioe); - } catch (Exception e) { - throw new EFhirClientException("Error parsing response message from "+source+": " + e.getMessage(), e); + } + } catch (IOException ioe) { + throw new EFhirClientException("Error reading Http Response from "+source+":"+ioe.getMessage(), ioe); + } catch (Exception e) { + throw new EFhirClientException("Error parsing response message from "+source+": "+e.getMessage(), e); + } + if (resource instanceof OperationOutcome && !"OperationOutcome".equals(resourceType)) { + OperationOutcome error = (OperationOutcome) resource; + if (hasError((OperationOutcome) resource)) { + throw new EFhirClientException("Error from "+source+": " + ResourceUtilities.getErrorDescription(error), error); + } else { + // umm, weird... } } - - if (error != null) { - throw new EFhirClientException("Error from server: " + ResourceUtilities.getErrorDescription(error), error); + if (resource == null) { + return null; // shouldn't get here? } - - return resource; + if (resourceType != null && !resource.fhirType().equals(resourceType)) { + throw new EFhirClientException("Error parsing response message from "+source+": Found an "+resource.fhirType()+" looking for a "+resourceType); + } + return (T) 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(); - String contentType = response.header("Content-Type"); - if (body != null) { - if (contentType.contains(ResourceFormat.RESOURCE_XML.getHeader()) - || contentType.contains(ResourceFormat.RESOURCE_JSON.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 from "+source+": "+ioe.getMessage(), ioe); - } catch (Exception e) { - throw new EFhirClientException("Error parsing response message from "+source+": "+e.getMessage(), e); - } - if (error != null) { - throw new EFhirClientException("Error from "+source+": " + ResourceUtilities.getErrorDescription(error), error); - } - return feed; + return unmarshalReference(response, format, "Bundle"); } /** diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/OperationOutcomeUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/OperationOutcomeUtilities.java index 6b9551e60..2c11306b6 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/OperationOutcomeUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/OperationOutcomeUtilities.java @@ -35,6 +35,7 @@ import java.util.List; import org.hl7.fhir.r5.model.CodeableConcept; import org.hl7.fhir.r5.model.IntegerType; +import org.hl7.fhir.r5.model.Narrative.NarrativeStatus; import org.hl7.fhir.r5.model.OperationOutcome; import org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity; import org.hl7.fhir.r5.model.OperationOutcome.IssueType; @@ -43,6 +44,8 @@ import org.hl7.fhir.r5.model.StringType; import org.hl7.fhir.r5.model.UrlType; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.utilities.validation.ValidationMessage; +import org.hl7.fhir.utilities.xhtml.NodeType; +import org.hl7.fhir.utilities.xhtml.XhtmlNode; public class OperationOutcomeUtilities { @@ -180,4 +183,16 @@ public class OperationOutcomeUtilities { return res; } + public static OperationOutcome outcomeFromTextError(String text) { + OperationOutcome oo = new OperationOutcome(); + oo.getText().setStatus(NarrativeStatus.GENERATED); + oo.getText().setDiv(new XhtmlNode(NodeType.Element, "div")); + oo.getText().getDiv().tx(text); + OperationOutcomeIssueComponent issue = oo.addIssue(); + issue.setSeverity(IssueSeverity.ERROR); + issue.setCode(IssueType.EXCEPTION); + issue.getDetails().setText(text); + return oo; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/FhirRequestBuilder.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/FhirRequestBuilder.java index f57b05125..e1d0b9448 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/FhirRequestBuilder.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/FhirRequestBuilder.java @@ -1,6 +1,12 @@ package org.hl7.fhir.r5.utils.client.network; -import okhttp3.*; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nonnull; + import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r5.formats.IParser; @@ -9,18 +15,21 @@ 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.OperationOutcomeUtilities; 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.MimeType; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.settings.FhirSettings; +import org.hl7.fhir.utilities.xhtml.XhtmlUtils; -import javax.annotation.Nonnull; -import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; +import okhttp3.Authenticator; +import okhttp3.Credentials; +import okhttp3.Headers; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; public class FhirRequestBuilder { @@ -227,7 +236,7 @@ public class FhirRequestBuilder { public ResourceRequest execute() throws IOException { formatHeaders(httpRequest, resourceFormat, headers); Response response = getHttpClient().newCall(httpRequest.build()).execute(); - T resource = unmarshalReference(response, resourceFormat); + T resource = unmarshalReference(response, resourceFormat, null); return new ResourceRequest(resource, response.code(), getLocationHeader(response.headers())); } @@ -241,50 +250,35 @@ public class FhirRequestBuilder { * Unmarshalls a resource from the response stream. */ @SuppressWarnings("unchecked") - protected T unmarshalReference(Response response, String format) { - T resource = null; - OperationOutcome error = null; - - if (response.body() != null) { - try { - byte[] body = response.body().bytes(); - 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 from "+source+": " + ioe.getMessage(), ioe); - } catch (Exception e) { - throw new EFhirClientException("Error parsing response message from "+source+": " + e.getMessage(), e); - } + protected T unmarshalReference(Response response, String format, String resourceType) { + if (response.body() == null) { + return null; } - - if (error != null) { - throw new EFhirClientException("Error from "+source+": " + ResourceUtilities.getErrorDescription(error), error); - } - - return resource; - } - - /** - * Unmarshalls Bundle from response stream. - */ - protected Bundle unmarshalFeed(Response response, String format) { - Bundle feed = null; - OperationOutcome error = null; + Resource resource = null; try { - byte[] body = response.body().bytes(); - String contentType = response.header("Content-Type"); - if (body != null) { - if (contentType.contains(ResourceFormat.RESOURCE_XML.getHeader()) || contentType.contains(ResourceFormat.RESOURCE_JSON.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"); - } + String ct = response.header("Content-Type"); + if (ct == null) { + resource = getParser(format).parse(response.body().bytes()); + } else { + switch (ct) { + case "application/json": + case "application/fhir+json": + resource = getParser(ResourceFormat.RESOURCE_JSON.getHeader()).parse(response.body().bytes()); + break; + case "application/xml": + case "application/fhir+xml": + case "text/xml": + resource = getParser(ResourceFormat.RESOURCE_JSON.getHeader()).parse(response.body().bytes()); + break; + case "text/plain": + resource = OperationOutcomeUtilities.outcomeFromTextError(response.body().string()); + break; + case "text/html" : + resource = OperationOutcomeUtilities.outcomeFromTextError(XhtmlUtils.convertHtmlToText(response.body().string())); + break; + default: // not sure what else to do? + System.out.println("Got content-type '"+ct+"' from "+source); + resource = OperationOutcomeUtilities.outcomeFromTextError(response.body().string()); } } } catch (IOException ioe) { @@ -292,10 +286,28 @@ public class FhirRequestBuilder { } catch (Exception e) { throw new EFhirClientException("Error parsing response message from "+source+": "+e.getMessage(), e); } - if (error != null) { - throw new EFhirClientException("Error from "+source+": " + ResourceUtilities.getErrorDescription(error), error); + if (resource instanceof OperationOutcome && !"OperationOutcome".equals(resourceType)) { + OperationOutcome error = (OperationOutcome) resource; + if (hasError((OperationOutcome) resource)) { + throw new EFhirClientException("Error from "+source+": " + ResourceUtilities.getErrorDescription(error), error); + } else { + // umm, weird... + } } - return feed; + if (resource == null) { + return null; // shouldn't get here? + } + if (resourceType != null && !resource.fhirType().equals(resourceType)) { + throw new EFhirClientException("Error parsing response message from "+source+": Found an "+resource.fhirType()+" looking for a "+resourceType); + } + return (T) resource; + } + + /** + * Unmarshalls Bundle from response stream. + */ + protected Bundle unmarshalFeed(Response response, String format) { + return unmarshalReference(response, format, "Bundle"); } /** diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlUtils.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlUtils.java new file mode 100644 index 000000000..c793191d0 --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlUtils.java @@ -0,0 +1,16 @@ +package org.hl7.fhir.utilities.xhtml; + +public class XhtmlUtils { + + public static String convertHtmlToText(String source) { + try { + XhtmlDocument doc = new XhtmlParser().parse(source, "html"); + return doc.getDocumentElement().allText(); + } catch (Exception e) { + e.printStackTrace(); + // todo - should we try another way? + return "Unparseable HTML"; + } + } + +} From 447400daf9d7df254f9bf915991b3af1930338c1 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 9 Oct 2024 23:10:22 +0800 Subject: [PATCH 02/20] Stop hitting VSAC server directly --- .../client/TerminologyClientManager.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientManager.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientManager.java index ff28f6333..a7bbc4fc8 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientManager.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientManager.java @@ -201,12 +201,16 @@ public class TerminologyClientManager { // no agreement? Then what we do depends if (vs != null) { if (vs.hasUserData("External.Link")) { - if (systems.size() == 1) { - internalLog.add(vs.getVersionedUrl()+" uses the system "+systems.toString()+" not handled by any servers. Using source @ '"+vs.getUserString("External.Link")+"'"); - } else { - internalLog.add(vs.getVersionedUrl()+" includes multiple systems "+systems.toString()+" best handled by multiple servers: "+choices.toString()+". Using source @ '"+vs.getUserString("External.Link")+"'"); + String el = vs.getUserString("External.Link"); + if ("https://vsac.nlm.nih.gov".equals(el)) { + el = getMaster().getAddress(); } - return findClient(vs.getUserString("External.Link"), systems, expand); + if (systems.size() == 1) { + internalLog.add(vs.getVersionedUrl()+" uses the system "+systems.toString()+" not handled by any servers. Using source @ '"+el+"'"); + } else { + internalLog.add(vs.getVersionedUrl()+" includes multiple systems "+systems.toString()+" best handled by multiple servers: "+choices.toString()+". Using source @ '"+el+"'"); + } + return findClient(el, systems, expand); } else { if (systems.size() == 1) { internalLog.add(vs.getVersionedUrl()+" uses the system "+systems.toString()+" not handled by any servers. Using master @ '"+serverList.get(0)+"'"); From 57926073bc53a36bbb860d26637cd2efd2417781 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 9 Oct 2024 23:10:35 +0800 Subject: [PATCH 03/20] Add support for $id$ in terminology tests --- .../main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java index e6d799919..63a857ce7 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java @@ -64,6 +64,7 @@ public class CompareUtilities extends BaseTestingUtilities { case "$$" : return "$$"; case "$instant$": return "\"An Instant\""; case "$uuid$": return "\"A Uuid\""; + case "$id$": return "\"An Id\""; default: return "Unhandled template: "+expected; } } @@ -465,6 +466,7 @@ public class CompareUtilities extends BaseTestingUtilities { case "$$" : return true; case "$instant$": return actualJsonString.matches("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]{1,9})?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))"); case "$uuid$": return actualJsonString.matches("urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"); + case "$id$": return actualJsonString.matches("[A-Za-z0-9\\-\\.]{1,64}"); default: throw new Error("Unhandled template: "+expectedJsonString); } From fd34048c814c58a50aba4a78450a6b96bd52cd9e Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 9 Oct 2024 23:10:51 +0800 Subject: [PATCH 04/20] test case version bump --- .../java/org/hl7/fhir/r5/utils/client/network/ByteUtils.java | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/ByteUtils.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/ByteUtils.java index 44199bf67..bf66c5622 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/ByteUtils.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/ByteUtils.java @@ -18,8 +18,8 @@ public class ByteUtils { public static byte[] resourceToByteArray(T resource, boolean pretty, boolean isJson, boolean noXhtml) { ByteArrayOutputStream baos = null; byte[] byteArray = null; + baos = new ByteArrayOutputStream(); try { - baos = new ByteArrayOutputStream(); IParser parser = null; if (isJson) { parser = new JsonParser(); diff --git a/pom.xml b/pom.xml index 0071f570c..1b15f2d1f 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ 2.17.0 32.0.1-jre 6.4.1 - 1.5.25 + 1.5.26-SNAPSHOT 2.17.0 5.9.2 1.8.2 From b9299fa9e248e0422f89756768c2b0a716eaac1e Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 9 Oct 2024 23:14:11 +0800 Subject: [PATCH 05/20] fix typo --- .../hl7/fhir/r4/utils/client/network/FhirRequestBuilder.java | 2 +- .../hl7/fhir/r4b/utils/client/network/FhirRequestBuilder.java | 2 +- .../hl7/fhir/r5/utils/client/network/FhirRequestBuilder.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilder.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilder.java index fb2fd464c..1e3098b59 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilder.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilder.java @@ -275,7 +275,7 @@ public class FhirRequestBuilder { case "application/xml": case "application/fhir+xml": case "text/xml": - resource = getParser(ResourceFormat.RESOURCE_JSON.getHeader()).parse(response.body().bytes()); + resource = getParser(ResourceFormat.RESOURCE_XML.getHeader()).parse(response.body().bytes()); break; case "text/plain": resource = outcomeFromTextError(response.body().string()); diff --git a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/FhirRequestBuilder.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/FhirRequestBuilder.java index 965322809..4d7d9a229 100644 --- a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/FhirRequestBuilder.java +++ b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/FhirRequestBuilder.java @@ -265,7 +265,7 @@ public class FhirRequestBuilder { case "application/xml": case "application/fhir+xml": case "text/xml": - resource = getParser(ResourceFormat.RESOURCE_JSON.getHeader()).parse(response.body().bytes()); + resource = getParser(ResourceFormat.RESOURCE_XML.getHeader()).parse(response.body().bytes()); break; case "text/plain": resource = OperationOutcomeUtilities.outcomeFromTextError(response.body().string()); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/FhirRequestBuilder.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/FhirRequestBuilder.java index e1d0b9448..14153a38b 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/FhirRequestBuilder.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/FhirRequestBuilder.java @@ -268,7 +268,7 @@ public class FhirRequestBuilder { case "application/xml": case "application/fhir+xml": case "text/xml": - resource = getParser(ResourceFormat.RESOURCE_JSON.getHeader()).parse(response.body().bytes()); + resource = getParser(ResourceFormat.RESOURCE_XML.getHeader()).parse(response.body().bytes()); break; case "text/plain": resource = OperationOutcomeUtilities.outcomeFromTextError(response.body().string()); From c7ccbb0110e77eb34731f6eb71da2d3470de6a27 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 10 Oct 2024 19:29:58 +0800 Subject: [PATCH 06/20] more work on http client --- .../client/network/FhirRequestBuilder.java | 44 +++++++++++++--- .../client/network/FhirRequestBuilder.java | 52 +++++++++++++++---- .../client/network/FhirRequestBuilder.java | 46 +++++++++++++--- .../hl7/fhir/utilities/xhtml/XhtmlNode.java | 8 ++- .../hl7/fhir/utilities/xhtml/XhtmlUtils.java | 6 ++- 5 files changed, 127 insertions(+), 29 deletions(-) diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilder.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilder.java index 1e3098b59..68e60979f 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilder.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/network/FhirRequestBuilder.java @@ -258,34 +258,59 @@ public class FhirRequestBuilder { */ @SuppressWarnings("unchecked") protected T unmarshalReference(Response response, String format, String resourceType) { + int code = response.code(); + boolean ok = code >= 200 && code < 300; if (response.body() == null) { - return null; + if (!ok) { + throw new EFhirClientException(response.message()); + } else { + return null; + } } + String body; + Resource resource = null; try { + body = response.body().string(); String ct = response.header("Content-Type"); if (ct == null) { - resource = getParser(format).parse(response.body().bytes()); + if (ok) { + resource = getParser(format).parse(body); + } else { + System.out.println("Got error response with no Content-Type from "+source+" with status "+code); + System.out.println(body); + resource = OperationOutcomeUtilities.outcomeFromTextError(body); + } } else { + if (ct.contains(";")) { + ct = ct.substring(0, ct.indexOf(";")); + } switch (ct) { case "application/json": case "application/fhir+json": - resource = getParser(ResourceFormat.RESOURCE_JSON.getHeader()).parse(response.body().bytes()); + if (!format.contains("json")) { + System.out.println("Got json response expecting "+format+" from "+source+" with status "+code); + } + resource = getParser(ResourceFormat.RESOURCE_JSON.getHeader()).parse(body); break; case "application/xml": case "application/fhir+xml": case "text/xml": + if (!format.contains("xml")) { + System.out.println("Got xml response expecting "+format+" from "+source+" with status "+code); + } resource = getParser(ResourceFormat.RESOURCE_XML.getHeader()).parse(response.body().bytes()); break; case "text/plain": - resource = outcomeFromTextError(response.body().string()); + resource = OperationOutcomeUtilities.outcomeFromTextError(body); break; case "text/html" : - resource = OperationOutcomeUtilities.outcomeFromTextError(XhtmlUtils.convertHtmlToText(response.body().string())); + resource = OperationOutcomeUtilities.outcomeFromTextError(XhtmlUtils.convertHtmlToText(response.body().string(), source)); break; default: // not sure what else to do? System.out.println("Got content-type '"+ct+"' from "+source); - resource = OperationOutcomeUtilities.outcomeFromTextError(response.body().string()); + System.out.println(body); + resource = OperationOutcomeUtilities.outcomeFromTextError(body); } } } catch (IOException ioe) { @@ -293,15 +318,20 @@ public class FhirRequestBuilder { } catch (Exception e) { throw new EFhirClientException("Error parsing response message from "+source+": "+e.getMessage(), e); } - if (resource instanceof OperationOutcome && !"OperationOutcome".equals(resourceType)) { + if (resource instanceof OperationOutcome && (!"OperationOutcome".equals(resourceType) || !ok)) { OperationOutcome error = (OperationOutcome) resource; if (hasError((OperationOutcome) resource)) { throw new EFhirClientException("Error from "+source+": " + ResourceUtilities.getErrorDescription(error), error); } else { // umm, weird... + System.out.println("Got OperationOutcome with no error from "+source+" with status "+code); + System.out.println(body); + return null; } } if (resource == null) { + System.out.println("No resource from "+source+" with status "+code); + System.out.println(body); return null; // shouldn't get here? } if (resourceType != null && !resource.fhirType().equals(resourceType)) { diff --git a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/FhirRequestBuilder.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/FhirRequestBuilder.java index 4d7d9a229..fa7a66300 100644 --- a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/FhirRequestBuilder.java +++ b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/client/network/FhirRequestBuilder.java @@ -248,34 +248,59 @@ public class FhirRequestBuilder { */ @SuppressWarnings("unchecked") protected T unmarshalReference(Response response, String format, String resourceType) { + int code = response.code(); + boolean ok = code >= 200 && code < 300; if (response.body() == null) { - return null; - } + if (!ok) { + throw new EFhirClientException(response.message()); + } else { + return null; + } + } + String body; + Resource resource = null; try { + body = response.body().string(); String ct = response.header("Content-Type"); if (ct == null) { - resource = getParser(format).parse(response.body().bytes()); - } else { + if (ok) { + resource = getParser(format).parse(body); + } else { + System.out.println("Got error response with no Content-Type from "+source+" with status "+code); + System.out.println(body); + resource = OperationOutcomeUtilities.outcomeFromTextError(body); + } + } else { + if (ct.contains(";")) { + ct = ct.substring(0, ct.indexOf(";")); + } switch (ct) { case "application/json": case "application/fhir+json": - resource = getParser(ResourceFormat.RESOURCE_JSON.getHeader()).parse(response.body().bytes()); + if (!format.contains("json")) { + System.out.println("Got json response expecting "+format+" from "+source+" with status "+code); + } + resource = getParser(ResourceFormat.RESOURCE_JSON.getHeader()).parse(body); break; case "application/xml": case "application/fhir+xml": case "text/xml": - resource = getParser(ResourceFormat.RESOURCE_XML.getHeader()).parse(response.body().bytes()); + if (!format.contains("xml")) { + System.out.println("Got xml response expecting "+format+" from "+source+" with status "+code); + } + resource = getParser(ResourceFormat.RESOURCE_XML.getHeader()).parse(body); break; case "text/plain": - resource = OperationOutcomeUtilities.outcomeFromTextError(response.body().string()); + resource = OperationOutcomeUtilities.outcomeFromTextError(body); break; case "text/html" : - resource = OperationOutcomeUtilities.outcomeFromTextError(XhtmlUtils.convertHtmlToText(response.body().string())); + resource = OperationOutcomeUtilities.outcomeFromTextError(XhtmlUtils.convertHtmlToText(response.body().string(), source)); break; default: // not sure what else to do? System.out.println("Got content-type '"+ct+"' from "+source); - resource = OperationOutcomeUtilities.outcomeFromTextError(response.body().string()); + System.out.println(body); + resource = OperationOutcomeUtilities.outcomeFromTextError(body); } } } catch (IOException ioe) { @@ -283,21 +308,26 @@ public class FhirRequestBuilder { } catch (Exception e) { throw new EFhirClientException("Error parsing response message from "+source+": "+e.getMessage(), e); } - if (resource instanceof OperationOutcome && !"OperationOutcome".equals(resourceType)) { + if (resource instanceof OperationOutcome && (!"OperationOutcome".equals(resourceType) || !ok)) { OperationOutcome error = (OperationOutcome) resource; if (hasError((OperationOutcome) resource)) { throw new EFhirClientException("Error from "+source+": " + ResourceUtilities.getErrorDescription(error), error); } else { // umm, weird... + System.out.println("Got OperationOutcome with no error from "+source+" with status "+code); + System.out.println(body); + return null; } } if (resource == null) { + System.out.println("No resource from "+source+" with status "+code); + System.out.println(body); return null; // shouldn't get here? } if (resourceType != null && !resource.fhirType().equals(resourceType)) { throw new EFhirClientException("Error parsing response message from "+source+": Found an "+resource.fhirType()+" looking for a "+resourceType); } - return (T) resource; + return (T) resource; } /** diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/FhirRequestBuilder.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/FhirRequestBuilder.java index 14153a38b..5704247be 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/FhirRequestBuilder.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/FhirRequestBuilder.java @@ -251,34 +251,59 @@ public class FhirRequestBuilder { */ @SuppressWarnings("unchecked") protected T unmarshalReference(Response response, String format, String resourceType) { + int code = response.code(); + boolean ok = code >= 200 && code < 300; if (response.body() == null) { - return null; + if (!ok) { + throw new EFhirClientException(response.message()); + } else { + return null; + } } + String body; + Resource resource = null; try { + body = response.body().string(); String ct = response.header("Content-Type"); if (ct == null) { - resource = getParser(format).parse(response.body().bytes()); + if (ok) { + resource = getParser(format).parse(body); + } else { + System.out.println("Got error response with no Content-Type from "+source+" with status "+code); + System.out.println(body); + resource = OperationOutcomeUtilities.outcomeFromTextError(body); + } } else { + if (ct.contains(";")) { + ct = ct.substring(0, ct.indexOf(";")); + } switch (ct) { case "application/json": case "application/fhir+json": - resource = getParser(ResourceFormat.RESOURCE_JSON.getHeader()).parse(response.body().bytes()); + if (!format.contains("json")) { + System.out.println("Got json response expecting "+format+" from "+source+" with status "+code); + } + resource = getParser(ResourceFormat.RESOURCE_JSON.getHeader()).parse(body); break; case "application/xml": case "application/fhir+xml": case "text/xml": - resource = getParser(ResourceFormat.RESOURCE_XML.getHeader()).parse(response.body().bytes()); + if (!format.contains("xml")) { + System.out.println("Got xml response expecting "+format+" from "+source+" with status "+code); + } + resource = getParser(ResourceFormat.RESOURCE_XML.getHeader()).parse(body); break; case "text/plain": - resource = OperationOutcomeUtilities.outcomeFromTextError(response.body().string()); + resource = OperationOutcomeUtilities.outcomeFromTextError(body); break; case "text/html" : - resource = OperationOutcomeUtilities.outcomeFromTextError(XhtmlUtils.convertHtmlToText(response.body().string())); + resource = OperationOutcomeUtilities.outcomeFromTextError(XhtmlUtils.convertHtmlToText(response.body().string(), source)); break; default: // not sure what else to do? System.out.println("Got content-type '"+ct+"' from "+source); - resource = OperationOutcomeUtilities.outcomeFromTextError(response.body().string()); + System.out.println(body); + resource = OperationOutcomeUtilities.outcomeFromTextError(body); } } } catch (IOException ioe) { @@ -286,15 +311,20 @@ public class FhirRequestBuilder { } catch (Exception e) { throw new EFhirClientException("Error parsing response message from "+source+": "+e.getMessage(), e); } - if (resource instanceof OperationOutcome && !"OperationOutcome".equals(resourceType)) { + if (resource instanceof OperationOutcome && (!"OperationOutcome".equals(resourceType) || !ok)) { OperationOutcome error = (OperationOutcome) resource; if (hasError((OperationOutcome) resource)) { throw new EFhirClientException("Error from "+source+": " + ResourceUtilities.getErrorDescription(error), error); } else { // umm, weird... + System.out.println("Got OperationOutcome with no error from "+source+" with status "+code); + System.out.println(body); + return null; } } if (resource == null) { + System.out.println("No resource from "+source+" with status "+code); + System.out.println(body); return null; // shouldn't get here? } if (resourceType != null && !resource.fhirType().equals(resourceType)) { diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java index 72e251336..fd10795d3 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java @@ -348,7 +348,13 @@ public class XhtmlNode extends XhtmlFluent implements IBaseXhtml { } } if (n.getNodeType() == NodeType.Element) { - b.append(n.allText()); + if (!Utilities.existsInList(n.getName(), "img")) { + b.append(n.allText()); + } else if (n.hasAttribute("alt")) { + b.append(n.getAttribute("alt")); + } else { + b.append("[image]"); + } if (Utilities.existsInList(n.getName(), "p", "div", "tr", "th", "ul", "ol", "li", "h1", "h2", "h3", "h4", "h5", "h6")) { b.append("\r\n"); } else if (Utilities.existsInList(n.getName(), "th", "td", "span")) { diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlUtils.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlUtils.java index c793191d0..4a48b6056 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlUtils.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlUtils.java @@ -2,13 +2,15 @@ package org.hl7.fhir.utilities.xhtml; public class XhtmlUtils { - public static String convertHtmlToText(String source) { + public static String convertHtmlToText(String source, String desc) { try { XhtmlDocument doc = new XhtmlParser().parse(source, "html"); return doc.getDocumentElement().allText(); } catch (Exception e) { - e.printStackTrace(); // todo - should we try another way? + System.err.println("XHTML content could not be parsed from "+desc); + e.printStackTrace(); + System.err.println(source); return "Unparseable HTML"; } } From a1c64498a046a90675614dd83e8edf1e19fc12bd Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 10 Oct 2024 19:30:26 +0800 Subject: [PATCH 07/20] don't send expansion if there's a compose when sending value sets --- .../main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java index 89dde24e0..29a98c91c 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java @@ -1764,6 +1764,10 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte } else if (options.getVsAsUrl()){ pin.addParameter().setName("url").setValue(new UriType(vs.getUrl())); } else { + if (vs.hasCompose() && vs.hasExpansion()) { + vs = vs.copy(); + vs.setExpansion(null); + } pin.addParameter().setName("valueSet").setResource(vs); if (vs.getUrl() != null) { terminologyClientContext.getCached().add(vs.getUrl()+"|"+ vs.getVersion()); From 72a45a2c10b2dc4cac093003254ad26389e3cf92 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 10 Oct 2024 20:06:40 +0800 Subject: [PATCH 08/20] release notes --- RELEASE_NOTES.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 7b06c6ab5..76932ac7f 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,7 +1,11 @@ ## Validator Changes -* no changes +* Don't send expansion to tx server if there's a compose when sending value sets +* Better handling of error messages from terminology servers +* Stop hitting VSAC server directly ## Other code changes -* no changes \ No newline at end of file +* Add support for $id$ in terminology tests +* Move new documentbuilderfactory calls to XMLUtils (Security) + From 15ab6895477607aecb98d30b931e711aa008914e Mon Sep 17 00:00:00 2001 From: Mark Iantorno Date: Thu, 10 Oct 2024 13:13:41 +0000 Subject: [PATCH 09/20] Updating test case dependency to v1.5.26 ***NO_CI*** --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1b15f2d1f..9c3d1b320 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ 2.17.0 32.0.1-jre 6.4.1 - 1.5.26-SNAPSHOT + 1.5.26 2.17.0 5.9.2 1.8.2 From 0388bd0835efd07f8684e3ed3fc22aaff7d4225a Mon Sep 17 00:00:00 2001 From: markiantorno Date: Thu, 10 Oct 2024 13:54:40 +0000 Subject: [PATCH 10/20] Release: v6.3.31 ## Validator Changes * Don't send expansion to tx server if there's a compose when sending value sets * Better handling of error messages from terminology servers * Stop hitting VSAC server directly ## Other code changes * Add support for $id$ in terminology tests * Move new documentbuilderfactory calls to XMLUtils (Security) ***NO_CI*** --- org.hl7.fhir.convertors/pom.xml | 2 +- org.hl7.fhir.dstu2/pom.xml | 2 +- org.hl7.fhir.dstu2016may/pom.xml | 2 +- org.hl7.fhir.dstu3/pom.xml | 2 +- org.hl7.fhir.r4/pom.xml | 2 +- org.hl7.fhir.r4b/pom.xml | 2 +- org.hl7.fhir.r5/pom.xml | 2 +- org.hl7.fhir.report/pom.xml | 2 +- org.hl7.fhir.utilities/pom.xml | 2 +- org.hl7.fhir.validation.cli/pom.xml | 2 +- org.hl7.fhir.validation/pom.xml | 2 +- pom.xml | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/org.hl7.fhir.convertors/pom.xml b/org.hl7.fhir.convertors/pom.xml index 8ed73b243..38fdf6877 100644 --- a/org.hl7.fhir.convertors/pom.xml +++ b/org.hl7.fhir.convertors/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.31-SNAPSHOT + 6.3.31 ../pom.xml diff --git a/org.hl7.fhir.dstu2/pom.xml b/org.hl7.fhir.dstu2/pom.xml index 7f8afdda1..393349da4 100644 --- a/org.hl7.fhir.dstu2/pom.xml +++ b/org.hl7.fhir.dstu2/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.31-SNAPSHOT + 6.3.31 ../pom.xml diff --git a/org.hl7.fhir.dstu2016may/pom.xml b/org.hl7.fhir.dstu2016may/pom.xml index dfdcbf910..5449e5434 100644 --- a/org.hl7.fhir.dstu2016may/pom.xml +++ b/org.hl7.fhir.dstu2016may/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.31-SNAPSHOT + 6.3.31 ../pom.xml diff --git a/org.hl7.fhir.dstu3/pom.xml b/org.hl7.fhir.dstu3/pom.xml index c00080fd6..0e9ba89f3 100644 --- a/org.hl7.fhir.dstu3/pom.xml +++ b/org.hl7.fhir.dstu3/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.31-SNAPSHOT + 6.3.31 ../pom.xml diff --git a/org.hl7.fhir.r4/pom.xml b/org.hl7.fhir.r4/pom.xml index 3420758be..175e9d60d 100644 --- a/org.hl7.fhir.r4/pom.xml +++ b/org.hl7.fhir.r4/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.31-SNAPSHOT + 6.3.31 ../pom.xml diff --git a/org.hl7.fhir.r4b/pom.xml b/org.hl7.fhir.r4b/pom.xml index bd03ab323..37d4c3759 100644 --- a/org.hl7.fhir.r4b/pom.xml +++ b/org.hl7.fhir.r4b/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.31-SNAPSHOT + 6.3.31 ../pom.xml diff --git a/org.hl7.fhir.r5/pom.xml b/org.hl7.fhir.r5/pom.xml index 07e4665a1..49f69e4dc 100644 --- a/org.hl7.fhir.r5/pom.xml +++ b/org.hl7.fhir.r5/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.31-SNAPSHOT + 6.3.31 ../pom.xml diff --git a/org.hl7.fhir.report/pom.xml b/org.hl7.fhir.report/pom.xml index b2304c29e..51b348c1b 100644 --- a/org.hl7.fhir.report/pom.xml +++ b/org.hl7.fhir.report/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.31-SNAPSHOT + 6.3.31 ../pom.xml diff --git a/org.hl7.fhir.utilities/pom.xml b/org.hl7.fhir.utilities/pom.xml index b3f7b2b6d..e2ca42647 100644 --- a/org.hl7.fhir.utilities/pom.xml +++ b/org.hl7.fhir.utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.31-SNAPSHOT + 6.3.31 ../pom.xml diff --git a/org.hl7.fhir.validation.cli/pom.xml b/org.hl7.fhir.validation.cli/pom.xml index d34a46936..916d786eb 100644 --- a/org.hl7.fhir.validation.cli/pom.xml +++ b/org.hl7.fhir.validation.cli/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.31-SNAPSHOT + 6.3.31 ../pom.xml diff --git a/org.hl7.fhir.validation/pom.xml b/org.hl7.fhir.validation/pom.xml index 126ec06b9..a7a5f1d92 100644 --- a/org.hl7.fhir.validation/pom.xml +++ b/org.hl7.fhir.validation/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.31-SNAPSHOT + 6.3.31 ../pom.xml diff --git a/pom.xml b/pom.xml index 9c3d1b320..48dd76b4a 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ HAPI FHIR --> org.hl7.fhir.core - 6.3.31-SNAPSHOT + 6.3.31 pom From a7e216e2c3388ccda0ee25c0420dcaf05cd06a5e Mon Sep 17 00:00:00 2001 From: markiantorno Date: Thu, 10 Oct 2024 14:35:11 +0000 Subject: [PATCH 11/20] Updating version to: 6.3.32-SNAPSHOT and incrementing test cases dependency. --- RELEASE_NOTES.md | 8 ++------ org.hl7.fhir.convertors/pom.xml | 2 +- org.hl7.fhir.dstu2/pom.xml | 2 +- org.hl7.fhir.dstu2016may/pom.xml | 2 +- org.hl7.fhir.dstu3/pom.xml | 2 +- org.hl7.fhir.r4/pom.xml | 2 +- org.hl7.fhir.r4b/pom.xml | 2 +- org.hl7.fhir.r5/pom.xml | 2 +- org.hl7.fhir.report/pom.xml | 2 +- org.hl7.fhir.utilities/pom.xml | 2 +- org.hl7.fhir.validation.cli/pom.xml | 2 +- org.hl7.fhir.validation/pom.xml | 2 +- pom.xml | 2 +- 13 files changed, 14 insertions(+), 18 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 76932ac7f..7b06c6ab5 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,11 +1,7 @@ ## Validator Changes -* Don't send expansion to tx server if there's a compose when sending value sets -* Better handling of error messages from terminology servers -* Stop hitting VSAC server directly +* no changes ## Other code changes -* Add support for $id$ in terminology tests -* Move new documentbuilderfactory calls to XMLUtils (Security) - +* no changes \ No newline at end of file diff --git a/org.hl7.fhir.convertors/pom.xml b/org.hl7.fhir.convertors/pom.xml index 38fdf6877..33376a626 100644 --- a/org.hl7.fhir.convertors/pom.xml +++ b/org.hl7.fhir.convertors/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.31 + 6.3.32-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.dstu2/pom.xml b/org.hl7.fhir.dstu2/pom.xml index 393349da4..71f09aca7 100644 --- a/org.hl7.fhir.dstu2/pom.xml +++ b/org.hl7.fhir.dstu2/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.31 + 6.3.32-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.dstu2016may/pom.xml b/org.hl7.fhir.dstu2016may/pom.xml index 5449e5434..7df21feed 100644 --- a/org.hl7.fhir.dstu2016may/pom.xml +++ b/org.hl7.fhir.dstu2016may/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.31 + 6.3.32-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.dstu3/pom.xml b/org.hl7.fhir.dstu3/pom.xml index 0e9ba89f3..8215581c9 100644 --- a/org.hl7.fhir.dstu3/pom.xml +++ b/org.hl7.fhir.dstu3/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.31 + 6.3.32-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.r4/pom.xml b/org.hl7.fhir.r4/pom.xml index 175e9d60d..3972a04ab 100644 --- a/org.hl7.fhir.r4/pom.xml +++ b/org.hl7.fhir.r4/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.31 + 6.3.32-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.r4b/pom.xml b/org.hl7.fhir.r4b/pom.xml index 37d4c3759..da3cef753 100644 --- a/org.hl7.fhir.r4b/pom.xml +++ b/org.hl7.fhir.r4b/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.31 + 6.3.32-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.r5/pom.xml b/org.hl7.fhir.r5/pom.xml index 49f69e4dc..5927b5768 100644 --- a/org.hl7.fhir.r5/pom.xml +++ b/org.hl7.fhir.r5/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.31 + 6.3.32-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.report/pom.xml b/org.hl7.fhir.report/pom.xml index 51b348c1b..6da6e25ff 100644 --- a/org.hl7.fhir.report/pom.xml +++ b/org.hl7.fhir.report/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.31 + 6.3.32-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.utilities/pom.xml b/org.hl7.fhir.utilities/pom.xml index e2ca42647..116ba8175 100644 --- a/org.hl7.fhir.utilities/pom.xml +++ b/org.hl7.fhir.utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.31 + 6.3.32-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.validation.cli/pom.xml b/org.hl7.fhir.validation.cli/pom.xml index 916d786eb..2bed28b35 100644 --- a/org.hl7.fhir.validation.cli/pom.xml +++ b/org.hl7.fhir.validation.cli/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.31 + 6.3.32-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.validation/pom.xml b/org.hl7.fhir.validation/pom.xml index a7a5f1d92..af3059a81 100644 --- a/org.hl7.fhir.validation/pom.xml +++ b/org.hl7.fhir.validation/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.31 + 6.3.32-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index 48dd76b4a..7679b0e5a 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ HAPI FHIR --> org.hl7.fhir.core - 6.3.31 + 6.3.32-SNAPSHOT pom From 38587eb508fe15acb4cea93443716fcbfb2a31d2 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Sun, 13 Oct 2024 22:38:15 +0800 Subject: [PATCH 12/20] Render extensions on some data types, and fix rendering of complex data types when doing profile rendering --- .../conformance/profile/ProfileUtilities.java | 11 ++ .../hl7/fhir/r5/renderers/DataRenderer.java | 80 ++++++++---- .../r5/renderers/ProfileDrivenRenderer.java | 120 +++++++++++------- .../fhir/r5/renderers/ResourceRenderer.java | 3 +- 4 files changed, 146 insertions(+), 68 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java index a4bfb0e1b..b1e80f184 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java @@ -294,17 +294,28 @@ public class ProfileUtilities { public static class SourcedChildDefinitions { private StructureDefinition source; private List list; + private String path; public SourcedChildDefinitions(StructureDefinition source, List list) { super(); this.source = source; this.list = list; } + public SourcedChildDefinitions(StructureDefinition source, List list, String path) { + super(); + this.source = source; + this.list = list; + this.path = path; + } public StructureDefinition getSource() { return source; } public List getList() { return list; } + public String getPath() { + return path; + } + } public class ElementDefinitionResolution { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java index 839648e4d..79980da72 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java @@ -41,6 +41,7 @@ import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent; import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceDesignationComponent; +import org.hl7.fhir.r5.renderers.Renderer.RenderingStatus; import org.hl7.fhir.r5.renderers.utils.RenderingContext; import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules; import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; @@ -486,24 +487,45 @@ public class DataRenderer extends Renderer implements CodeResolver { // } // } // - public void renderExtensionsInText(RenderingStatus status, XhtmlNode div, ResourceWrapper element, String sep) throws FHIRFormatError, DefinitionException, IOException { + public void renderExtensionsInText(RenderingStatus status, XhtmlNode x, ResourceWrapper element, String sep) throws FHIRFormatError, DefinitionException, IOException { boolean first = true; for (ResourceWrapper ext : element.extensions()) { if (canRender(ext)) { + status.setExtensions(true); if (first) { first = false; } else { - div.tx(sep); - div.tx(" "); + x.tx(sep); + x.tx(" "); } String lbl = getExtensionLabel(ext); - div.tx(lbl); - div.tx(": "); - renderDataType(status, div, ext.child("value")); + x.tx(lbl); + x.tx(": "); + renderDataType(status, x, ext.child("value")); } } } + + + protected void checkRenderExtensions(RenderingStatus status, XhtmlNode x, ResourceWrapper element) throws FHIRFormatError, DefinitionException, IOException { + if (element.has("extension")) { + boolean someCanRender = false; + for (ResourceWrapper ext : element.children("extension")) { + ResourceWrapper value = ext.child("value"); + if (canRender(ext) && value.isPrimitive()) { + someCanRender = true; + } + } + if (someCanRender) { + status.setExtensions(true); + x.tx(" ("); + renderExtensionsInText(status, x, element, ", "); + x.tx(")"); + } + } + + } // public void renderExtensionsInList(XhtmlNode div, BackboneType element, String sep) throws FHIRFormatError, DefinitionException, IOException { // boolean first = true; @@ -742,13 +764,14 @@ public class DataRenderer extends Renderer implements CodeResolver { } public boolean canRenderDataType(String type) { - return context.getContextUtilities().isPrimitiveType(type) || Utilities.existsInList(type, "Annotation", "Coding", "CodeableConcept", "Identifier", "HumanName", "Address", + return context.getContextUtilities().isPrimitiveType(type) || Utilities.existsInList(type, "Annotation", "Coding", "CodeableConcept", "Identifier", "HumanName", "Address", "Dosage", "Expression", "Money", "ContactPoint", "Quantity", "Range", "Period", "Timing", "SampledData", "Reference", "UsageContext", "ContactDetail", "Ratio", "Attachment", "CodeableReference"); } public boolean renderDataType(RenderingStatus status, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException { return renderDataType(status, null, x, type); } + public boolean renderDataType(RenderingStatus status, XhtmlNode parent, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException { if (type == null) { return false; @@ -863,10 +886,11 @@ public class DataRenderer extends Renderer implements CodeResolver { renderUri(status, x, type); } - private void renderRatio(RenderingStatus status, XhtmlNode x, ResourceWrapper type) { + private void renderRatio(RenderingStatus status, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException { renderQuantity(status, x, type.child("numerator")); x.tx("/"); - renderQuantity(status, x, type.child("denominator")); + renderQuantity(status, x, type.child("denominator")); + checkRenderExtensions(status, x, type); } private void renderAttachment(RenderingStatus status, XhtmlNode x, ResourceWrapper att) { @@ -892,6 +916,7 @@ public class DataRenderer extends Renderer implements CodeResolver { private void renderDateTime(RenderingStatus status, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException { if (!renderPrimitiveWithNoValue(status, x, type)) { x.tx(displayDateTime(type)); + checkRenderExtensions(status, x, type); } } @@ -1002,7 +1027,11 @@ public class DataRenderer extends Renderer implements CodeResolver { return true; } - private String tail(String url) { + protected String tail(String url) { + return url.substring(url.lastIndexOf(".")+1); + } + + protected String utail(String url) { return url.contains("/") ? url.substring(url.lastIndexOf("/")+1) : url; } @@ -1038,7 +1067,8 @@ public class DataRenderer extends Renderer implements CodeResolver { } } } - } + } + checkRenderExtensions(status, x, uri); } protected void renderAnnotation(RenderingStatus status, XhtmlNode x, ResourceWrapper a) throws FHIRException { @@ -1270,7 +1300,7 @@ public class DataRenderer extends Renderer implements CodeResolver { return new CodeResolution(null, null, null, code.getText(), code.getText()); } } - protected void renderCodingWithDetails(RenderingStatus status, XhtmlNode x, ResourceWrapper c) { + protected void renderCodingWithDetails(RenderingStatus status, XhtmlNode x, ResourceWrapper c) throws FHIRFormatError, DefinitionException, IOException { String s = ""; if (c.has("display")) s = context.getTranslated(c.child("display")); @@ -1296,6 +1326,7 @@ public class DataRenderer extends Renderer implements CodeResolver { if (c.has("version")) { x.tx(" "+context.formatPhrase(RenderingContext.DATA_REND_VERSION, c.primitiveValue("version"), ")")); } + checkRenderExtensions(status, x, c); } protected void renderCoding(RenderingStatus status, XhtmlNode x, ResourceWrapper c) { @@ -1434,7 +1465,8 @@ public class DataRenderer extends Renderer implements CodeResolver { } x.span(null, context.formatPhrase(RenderingContext.DATA_REND_CODES) +b.toString()).addText(s); - } + } + checkRenderExtensions(status, x, cc); } protected String displayIdentifier(ResourceWrapper ii) { @@ -1477,7 +1509,7 @@ public class DataRenderer extends Renderer implements CodeResolver { return s; } - protected void renderIdentifier(RenderingStatus status, XhtmlNode x, ResourceWrapper ii) { + protected void renderIdentifier(RenderingStatus status, XhtmlNode x, ResourceWrapper ii) throws FHIRFormatError, DefinitionException, IOException { if (ii.has("type")) { ResourceWrapper type = ii.child("type"); if (type.has("text")) { @@ -1527,7 +1559,8 @@ public class DataRenderer extends Renderer implements CodeResolver { x.tx(displayPeriod(ii.child("period"))); } x.tx(")"); - } + } + checkRenderExtensions(status, x, ii); } public static String displayHumanName(ResourceWrapper name) { @@ -1550,7 +1583,7 @@ public class DataRenderer extends Renderer implements CodeResolver { } - protected void renderHumanName(RenderingStatus status, XhtmlNode x, ResourceWrapper name) { + protected void renderHumanName(RenderingStatus status, XhtmlNode x, ResourceWrapper name) throws FHIRFormatError, DefinitionException, IOException { StringBuilder s = new StringBuilder(); if (name.has("text")) s.append(context.getTranslated(name.child("text"))); @@ -1567,7 +1600,8 @@ public class DataRenderer extends Renderer implements CodeResolver { if (name.has("use") && !name.primitiveValue("use").equals("usual")) { s.append("("+context.getTranslatedCode(name.primitiveValue("use"), "http://hl7.org/fhir/name-use")+")"); } - x.addText(s.toString()); + x.addText(s.toString()); + checkRenderExtensions(status, x, name); } private String displayAddress(ResourceWrapper address) { @@ -1604,8 +1638,9 @@ public class DataRenderer extends Renderer implements CodeResolver { return s.toString(); } - protected void renderAddress(RenderingStatus status, XhtmlNode x, ResourceWrapper address) { - x.addText(displayAddress(address)); + protected void renderAddress(RenderingStatus status, XhtmlNode x, ResourceWrapper address) throws FHIRFormatError, DefinitionException, IOException { + x.addText(displayAddress(address)); + checkRenderExtensions(status, x, address); } @@ -1788,7 +1823,7 @@ public class DataRenderer extends Renderer implements CodeResolver { return s.toString(); } - protected void renderQuantity(RenderingStatus status, XhtmlNode x, ResourceWrapper q) { + protected void renderQuantity(RenderingStatus status, XhtmlNode x, ResourceWrapper q) throws FHIRFormatError, DefinitionException, IOException { if (q.has("comparator")) x.addText(q.primitiveValue("comparator")); if (q.has("value")) { @@ -1805,7 +1840,8 @@ public class DataRenderer extends Renderer implements CodeResolver { } if (context.isTechnicalMode() && q.has("code")) { x.span("background: LightGoldenRodYellow", null).tx(" "+ (context.formatPhrase(RenderingContext.DATA_REND_DETAILS, displaySystem(q.primitiveValue("system")))) +q.primitiveValue("code")+" = '"+lookupCode(q.primitiveValue("system"), null, q.primitiveValue("code"))+"')"); - } + } + checkRenderExtensions(status, x, q); } @@ -2073,7 +2109,7 @@ public class DataRenderer extends Renderer implements CodeResolver { st = st + "-"+rep.primitiveValue("frequencyMax"); } if (rep.has("period")) { - st = st + " "+ (context.formatPhrase(RenderingContext.DATA_REND_PER))+rep.primitiveValue("period"); + st = st + " "+ (context.formatPhrase(RenderingContext.DATA_REND_PER))+" "+rep.primitiveValue("period"); if (rep.has("periodMax")) st = st + "-"+rep.primitiveValue("periodMax"); st = st + " "+displayTimeUnits(rep.primitiveValue("periodUnit")); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java index 0e0227efb..90b3d55ac 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java @@ -3,6 +3,7 @@ package org.hl7.fhir.r5.renderers; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -19,6 +20,7 @@ import org.hl7.fhir.r5.model.ElementDefinition; import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; +import org.hl7.fhir.r5.renderers.StructureDefinitionRenderer.SourcedElementDefinition; import org.hl7.fhir.r5.renderers.utils.RenderingContext; import org.hl7.fhir.r5.renderers.utils.ResourceWrapper; import org.hl7.fhir.r5.renderers.utils.ResourceWrapper.NamedResourceWrapperList; @@ -49,7 +51,7 @@ public class ProfileDrivenRenderer extends ResourceRenderer { } else { ElementDefinition ed = sd.getSnapshot().getElement().get(0); containedIds.clear(); - generateByProfile(status, r, sd, r, sd.getSnapshot().getElement(), ed, context.getProfileUtilities().getChildList(sd, ed), x, r.fhirType(), context.isTechnicalMode(), 0); + generateByProfile(status, r, sd, r, ed, context.getProfileUtilities().getChildList(sd, ed), x, r.fhirType(), context.isTechnicalMode(), 0); } } catch (Exception e) { if (DEBUG) { @@ -159,7 +161,7 @@ public class ProfileDrivenRenderer extends ResourceRenderer { return null; } - private void renderLeaf(RenderingStatus status, ResourceWrapper res, ResourceWrapper ew, StructureDefinition sd, ElementDefinition defn, XhtmlNode parent, XhtmlNode x, boolean title, boolean showCodeDetails, Map displayHints, String path, int indent) throws FHIRException, UnsupportedEncodingException, IOException, EOperationOutcome { + private void renderLeaf(RenderingStatus status, ResourceWrapper res, ResourceWrapper ew, StructureDefinition sd, ElementDefinition defn, XhtmlNode parent, XhtmlNode x, boolean title, boolean showCodeDetails, Map displayHints, int indent) throws FHIRException, UnsupportedEncodingException, IOException, EOperationOutcome { if (ew == null) return; @@ -173,7 +175,7 @@ public class ProfileDrivenRenderer extends ResourceRenderer { } else if (!renderDataType(status, parent, x, ew)) { // well, we have a cell (x) to render this thing, whatever it is // it's not a data type for which we have a built rendering, so we're going to get a list of it's renderable datatype properties, and render them in a list - SourcedChildDefinitions childDefs = context.getProfileUtilities().getChildMap(sd, defn); + // SourcedChildDefinitions childDefs = context.getProfileUtilities().getChildMap(sd, defn); boolean first = true; x.tx(" ("); for (ResourceWrapper child : ew.children()) { @@ -199,24 +201,24 @@ public class ProfileDrivenRenderer extends ResourceRenderer { } } - private boolean displayLeaf(ResourceWrapper res, ResourceWrapper ew, ElementDefinition defn, XhtmlNode x, String name, boolean showCodeDetails, boolean allowLinks) throws FHIRException, UnsupportedEncodingException, IOException { - if (ew == null) - return false; - - Map displayHints = readDisplayHints(defn); - - if (name.endsWith("[x]")) - name = name.substring(0, name.length() - 3); - - if (!showCodeDetails && ew.isPrimitive() && isDefault(displayHints, ew)) { - return false; - } else if (Utilities.existsInList(ew.fhirType(), "Extension")) { - return false; - } else { - x.addText(name+": "+ displayDataType(ew)); - return true; - } - } +// private boolean displayLeaf(ResourceWrapper res, ResourceWrapper ew, ElementDefinition defn, XhtmlNode x, String name, boolean showCodeDetails, boolean allowLinks) throws FHIRException, UnsupportedEncodingException, IOException { +// if (ew == null) +// return false; +// +// Map displayHints = readDisplayHints(defn); +// +// if (name.endsWith("[x]")) +// name = name.substring(0, name.length() - 3); +// +// if (!showCodeDetails && ew.isPrimitive() && isDefault(displayHints, ew)) { +// return false; +// } else if (Utilities.existsInList(ew.fhirType(), "Extension")) { +// return false; +// } else { +// x.addText(name+": "+ displayDataType(ew)); +// return true; +// } +// } @@ -250,7 +252,8 @@ public class ProfileDrivenRenderer extends ResourceRenderer { return code != null && (code.equals("Element") || code.equals("BackboneElement")); } - private List getChildrenForPath(StructureDefinition profile, List elements, String path) throws DefinitionException { + private SourcedChildDefinitions getChildrenForPath(StructureDefinition profile, String path) throws DefinitionException { + var elements = profile.getSnapshot().getElement(); // do we need to do a name reference substitution? for (ElementDefinition e : elements) { if (e.getPath().equals(path) && e.hasContentReference()) { @@ -279,45 +282,45 @@ public class ProfileDrivenRenderer extends ResourceRenderer { } if (results.isEmpty() && t != null && t.getType().size() == 1) { StructureDefinition tsd = context.getWorker().fetchTypeDefinition(t.getTypeFirstRep().getWorkingCode()); - return getChildrenForPath(tsd, tsd.getSnapshot().getElement(), tsd.getType()); + return getChildrenForPath(tsd, tsd.getType()); } - return results; + return new SourcedChildDefinitions(profile, results, path); } - private void generateByProfile(RenderingStatus status, ResourceWrapper res, StructureDefinition profile, ResourceWrapper e, List allElements, ElementDefinition defn, List children, XhtmlNode x, String path, boolean showCodeDetails, int indent) throws FHIRException, UnsupportedEncodingException, IOException, EOperationOutcome { + private void generateByProfile(RenderingStatus status, ResourceWrapper res, StructureDefinition profile, ResourceWrapper e, ElementDefinition defn, List children, XhtmlNode x, String path, boolean showCodeDetails, int indent) throws FHIRException, UnsupportedEncodingException, IOException, EOperationOutcome { if (children.isEmpty()) { StructureDefinition sdt = context.getWorker().fetchTypeDefinition(e.fhirType()); if (sdt != null && (sdt.getKind() == StructureDefinitionKind.COMPLEXTYPE || sdt.getKind() == StructureDefinitionKind.PRIMITIVETYPE)) { - renderLeaf(status, res, e, profile, defn, x, x, false, showCodeDetails, readDisplayHints(defn), path, indent); + renderLeaf(status, res, e, profile, defn, x, x, false, showCodeDetails, readDisplayHints(defn), indent); } else { // we don't have anything to render? } } else { List pl = splitExtensions(profile, e.childrenInGroups()); for (NamedResourceWrapperList p : pl) { - generateForProperty(status, res, profile, allElements, children, x, path, showCodeDetails, indent, false, p); + generateForProperty(status, res, profile, children, x, path, showCodeDetails, indent, false, p); } for (NamedResourceWrapperList p : pl) { - generateForProperty(status, res, profile, allElements, children, x, path, showCodeDetails, indent, true, p); + generateForProperty(status, res, profile, children, x, path, showCodeDetails, indent, true, p); } } } private void generateForProperty(RenderingStatus status, ResourceWrapper res, StructureDefinition profile, - List allElements, List children, XhtmlNode x, String path, + List children, XhtmlNode x, String path, boolean showCodeDetails, int indent, boolean round2, NamedResourceWrapperList p) throws UnsupportedEncodingException, IOException, EOperationOutcome { if (!p.getValues().isEmpty()) { ElementDefinition child = getElementDefinition(children, path+"."+p.getName()); if (child != null) { if (!child.getBase().hasPath() || !child.getBase().getPath().startsWith("Resource.")) { - generateElementByProfile(status, res, profile, allElements, x, path, showCodeDetails, indent, p, child, round2); + generateElementByProfile(status, res, profile, x, path, showCodeDetails, indent, p, child, round2); } } } } - public void generateElementByProfile(RenderingStatus status, ResourceWrapper res, StructureDefinition profile, List allElements, XhtmlNode x, String path, + public void generateElementByProfile(RenderingStatus status, ResourceWrapper res, StructureDefinition profile, XhtmlNode x, String path, boolean showCodeDetails, int indent, NamedResourceWrapperList p, ElementDefinition child, boolean round2) throws UnsupportedEncodingException, IOException, EOperationOutcome { Map displayHints = readDisplayHints(child); if ("DomainResource.contained".equals(child.getBase().getPath())) { @@ -336,8 +339,8 @@ public class ProfileDrivenRenderer extends ResourceRenderer { if (isExt) { status.setExtensions(true); } - List grandChildren = getChildrenForPath(profile, allElements, path+"."+p.getName()); - filterGrandChildren(grandChildren, path+"."+p.getName(), p); + SourcedChildDefinitions grandChildren = getChildrenForPath(profile, path+"."+p.getName()); + filterGrandChildren(grandChildren.getList(), path+"."+p.getName(), p); if (p.getValues().size() > 0) { if (isSimple(child) && !isExt) { XhtmlNode para = x.isPara() ? para = x : x.para(); @@ -351,7 +354,7 @@ public class ProfileDrivenRenderer extends ResourceRenderer { if (renderAsList(child) && p.getValues().size() > 1) { XhtmlNode list = x.ul(); for (ResourceWrapper v : p.getValues()) - renderLeaf(status, res, v, profile, child, x, list.li(), false, showCodeDetails, displayHints, path, indent); + renderLeaf(status, res, v, profile, child, x, list.li(), false, showCodeDetails, displayHints, indent); } else { boolean first = true; for (ResourceWrapper v : p.getValues()) { @@ -360,23 +363,23 @@ public class ProfileDrivenRenderer extends ResourceRenderer { } else { para.tx(", "); } - renderLeaf(status, res, v, profile, child, x, para, false, showCodeDetails, displayHints, path, indent); + renderLeaf(status, res, v, profile, child, x, para, false, showCodeDetails, displayHints, indent); } } } - } else if (canDoTable(path, p, grandChildren, x)) { + } else if (canDoTable(path, p, grandChildren.getList(), x)) { XhtmlNode xn = new XhtmlNode(NodeType.Element, getHeader()); xn.addText(Utilities.capitalize(Utilities.camelCase(Utilities.pluralizeMe(p.getName())))); XhtmlNode tbl = new XhtmlNode(NodeType.Element, "table"); tbl.setAttribute("class", "grid"); XhtmlNode tr = tbl.tr(); tr.td().style("display: none").tx("-"); // work around problem with empty table rows - boolean add = addColumnHeadings(tr, grandChildren); + boolean add = addColumnHeadings(tr, grandChildren.getList()); for (ResourceWrapper v : p.getValues()) { if (v != null) { tr = tbl.tr(); tr.td().style("display: none").tx("*"); // work around problem with empty table rows - add = addColumnValues(status, res, tr, profile, grandChildren, v, showCodeDetails, displayHints, path, indent) || add; + add = addColumnValues(status, res, tr, profile, grandChildren.getList(), v, showCodeDetails, displayHints, indent) || add; } } if (add) { @@ -393,7 +396,7 @@ public class ProfileDrivenRenderer extends ResourceRenderer { XhtmlNode para = x.para(); para.b().addText(labelforExtension(sd, p.getUrl())); para.tx(": "); - renderLeaf(status, res, vp, profile, child, x, para, false, showCodeDetails, displayHints, path, indent); + renderLeaf(status, res, vp, profile, child, x, para, false, showCodeDetails, displayHints, indent); } else if (!ev.isEmpty()) { XhtmlNode bq = x.addTag("blockquote"); bq.para().b().addText(labelforExtension(sd, p.getUrl())); @@ -410,13 +413,13 @@ public class ProfileDrivenRenderer extends ResourceRenderer { XhtmlNode li = ul.li(); li.tx(labelForSubExtension(vv.primitiveValue("url"), sd)); li.tx(": "); - renderLeaf(status, res, vv.child("value"), sd, child, x, li, isExt, showCodeDetails, displayHints, path, indent); + renderLeaf(status, res, vv.child("value"), sd, child, x, li, isExt, showCodeDetails, displayHints, indent); } } else { for (ResourceWrapper vv : ev) { StructureDefinition ex = context.getWorker().fetchTypeDefinition("Extension"); - List children = getChildrenForPath(profile, ex.getSnapshot().getElement(), "Extension"); - generateByProfile(status, res, ex, vv, allElements, child, children, bq, "Extension", showCodeDetails, indent+1); + SourcedChildDefinitions children = getChildrenForPath(ex, "Extension"); + generateByProfile(status, res, ex, vv, child, children.getList(), bq, "Extension", showCodeDetails, indent+1); } } } @@ -427,7 +430,7 @@ public class ProfileDrivenRenderer extends ResourceRenderer { if (v != null) { XhtmlNode bq = x.addTag("blockquote"); bq.para().b().addText(p.getName()); - generateByProfile(status, res, profile, v, allElements, child, grandChildren, bq, path+"."+p.getName(), showCodeDetails, indent+1); + generateByProfile(status, res, grandChildren.getSource(), v, child, grandChildren.getList(), bq, grandChildren.getPath(), showCodeDetails, indent+1); } } } @@ -435,6 +438,29 @@ public class ProfileDrivenRenderer extends ResourceRenderer { } } +// +// private String getGrandChildBase(List grandChildren) { +// if (grandChildren == null || grandChildren.isEmpty()) { +// return null; +// } +// String[] path = grandChildren.get(0).getPath().split("\\."); +// for (int i = 1; i < grandChildren.size(); i++) { +// path = getSharedString(path, grandChildren.get(1).getPath().split("\\.")); +// } +// return CommaSeparatedStringBuilder.join(".", path); +// } +// +// private String[] getSharedString(String[] path, String[] path2) { +// int m = -1; +// for (int i = 0; i < Integer.min(path.length, path2.length); i++) { +// if (path[i].equals(path2[i])) { +// m = i; +// } else { +// break; +// } +// } +// return m == -1 ? new String[0] : Arrays.copyOfRange(path, 0, m+1); +// } private String labelForSubExtension(String url, StructureDefinition sd) { return url; @@ -530,7 +556,7 @@ public class ProfileDrivenRenderer extends ResourceRenderer { return b; } - private boolean addColumnValues(RenderingStatus status, ResourceWrapper res, XhtmlNode tr, StructureDefinition profile, List grandChildren, ResourceWrapper v, boolean showCodeDetails, Map displayHints, String path, int indent) throws FHIRException, UnsupportedEncodingException, IOException, EOperationOutcome { + private boolean addColumnValues(RenderingStatus status, ResourceWrapper res, XhtmlNode tr, StructureDefinition profile, List grandChildren, ResourceWrapper v, boolean showCodeDetails, Map displayHints, int indent) throws FHIRException, UnsupportedEncodingException, IOException, EOperationOutcome { boolean b = false; for (ElementDefinition e : grandChildren) { List p = v.children(e.getPath().substring(e.getPath().lastIndexOf(".")+1)); @@ -542,7 +568,7 @@ public class ProfileDrivenRenderer extends ResourceRenderer { for (ResourceWrapper vv : p) { b = true; td.sep(", "); - renderLeaf(status, res, vv, profile, e, td, td, false, showCodeDetails, displayHints, path, indent); + renderLeaf(status, res, vv, profile, e, td, td, false, showCodeDetails, displayHints, indent); } } } @@ -656,6 +682,10 @@ public class ProfileDrivenRenderer extends ResourceRenderer { return path.substring(path.lastIndexOf(".")+1); } + protected String utail(String path) { + return path.contains("/") ? path.substring(path.lastIndexOf("/")+1) : path; + } + public boolean canRender(Resource resource) { return context.getWorker().getResourceNames().contains(resource.fhirType()); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java index ace526204..5bfafc50e 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java @@ -35,7 +35,6 @@ import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; import org.hl7.fhir.r5.utils.EOperationOutcome; import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.r5.utils.XVerExtensionManager; -import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece; @@ -369,8 +368,10 @@ public abstract class ResourceRenderer extends DataRenderer { } else { x.tx("??"); } + checkRenderExtensions(status, x, type); } + public void renderReference(ResourceWrapper res, HierarchicalTableGenerator gen, List pieces, Reference r, boolean allowLinks) throws UnsupportedEncodingException, IOException { if (r == null) { pieces.add(gen.new Piece(null, "null!", null)); From 9809800d8d0622426c45753bb8cfd676b0313556 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Sun, 13 Oct 2024 22:38:54 +0800 Subject: [PATCH 13/20] Add better tx routing logging --- .../client/TerminologyClientManager.java | 93 +++++++++++++++---- .../utilities/TerminologyCache.java | 2 +- 2 files changed, 76 insertions(+), 19 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientManager.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientManager.java index a7bbc4fc8..6ca4050f0 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientManager.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientManager.java @@ -83,6 +83,42 @@ public class TerminologyClientManager { ITerminologyClient makeClient(String id, String url, String userAgent, ToolingClientLogger logger) throws URISyntaxException; String getVersion(); } + + public class InternalLogEvent { + private String message; + private String server; + private String vs; + private String systems; + private String choices; + protected InternalLogEvent(String message, String server, String vs, String systems, String choices) { + super(); + this.message = message; + this.server = server; + this.vs = vs; + this.systems = systems; + this.choices = choices; + } + protected InternalLogEvent(String message) { + super(); + this.message = message; + } + public String getMessage() { + return message; + } + public String getVs() { + return vs; + } + public String getSystems() { + return systems; + } + public String getChoices() { + return choices; + } + public String getServer() { + return server; + } + + } public static final String UNRESOLVED_VALUESET = "--unknown--"; @@ -93,7 +129,7 @@ public class TerminologyClientManager { private List serverList = new ArrayList<>(); // clients by server address private Map serverMap = new HashMap<>(); // clients by server address private Map resMap = new HashMap<>(); // client resolution list - private List internalLog = new ArrayList<>(); + private List internalLog = new ArrayList<>(); protected Parameters expParameters; private TerminologyCache cache; @@ -124,6 +160,7 @@ public class TerminologyClientManager { monitorServiceURL = other.monitorServiceURL; factory = other.factory; usage = other.usage; + internalLog = other.internalLog; } @@ -150,6 +187,7 @@ public class TerminologyClientManager { } } if (ok) { + log(vs, s, systems, choices, "Found authoritative server "+s); return findClient(s, systems, expand); } } @@ -165,6 +203,7 @@ public class TerminologyClientManager { } } if (ok) { + log(vs, s, systems, choices, "Found partially authoritative server "+s); return findClient(s, systems, expand); } } @@ -180,6 +219,7 @@ public class TerminologyClientManager { } } if (ok) { + log(vs, s, systems, choices, "Found candidate server "+s); return findClient(s, systems, expand); } } @@ -194,6 +234,7 @@ public class TerminologyClientManager { "urn:ietf:rfc:3986", "http://www.ama-assn.org/go/cpt", "urn:oid:1.2.36.1.2001.1005.17", "urn:iso:std:iso:3166", "http://varnomen.hgvs.org", "http://unstats.un.org/unsd/methods/m49/m49.htm", "urn:iso:std:iso:4217", "http://hl7.org/fhir/sid/ndc", "http://fhir.ohdsi.org/CodeSystem/concepts", "http://fdasis.nlm.nih.gov", "https://www.usps.com/")) { + log(vs, serverList.get(0).getAddress(), systems, choices, "Use primary server for "+uri); return serverList.get(0); } } @@ -206,29 +247,37 @@ public class TerminologyClientManager { el = getMaster().getAddress(); } if (systems.size() == 1) { - internalLog.add(vs.getVersionedUrl()+" uses the system "+systems.toString()+" not handled by any servers. Using source @ '"+el+"'"); + log(vs, el, systems, choices, "Not handled by any servers. Using source @ '"+el+"'"); } else { - internalLog.add(vs.getVersionedUrl()+" includes multiple systems "+systems.toString()+" best handled by multiple servers: "+choices.toString()+". Using source @ '"+el+"'"); + log(vs, el, systems, choices, "Handled by multiple servers. Using source @ '"+el+"'"); } return findClient(el, systems, expand); } else { if (systems.size() == 1) { - internalLog.add(vs.getVersionedUrl()+" uses the system "+systems.toString()+" not handled by any servers. Using master @ '"+serverList.get(0)+"'"); + log(vs, serverList.get(0).getAddress(), systems, choices, "System not handled by any servers. Using primary server"); } else { - internalLog.add(vs.getVersionedUrl()+" includes multiple systems "+systems.toString()+" best handled by multiple servers: "+choices.toString()+". Using master @ '"+serverList.get(0)+"'"); + log(vs, serverList.get(0).getAddress(), systems, choices, "Systems handled by multiple servers. Using primary server"); } return findClient(serverList.get(0).getAddress(), systems, expand); } } else { if (systems.size() == 1) { - internalLog.add("Request for system "+systems.toString()+" not handled by any servers. Using master @ '"+serverList.get(0)+"'"); + log(vs, serverList.get(0).getAddress(), systems, choices, "System not handled by any servers. Using primary server"); } else { - internalLog.add("Request for multiple systems "+systems.toString()+" best handled by multiple servers: "+choices.toString()+". Using master @ '"+serverList.get(0)+"'"); + log(vs, serverList.get(0).getAddress(), systems, choices, "Systems handled by multiple servers. Using primary server"); } + log(vs, serverList.get(0).getAddress(), systems, choices, "Fallback: primary server"); return findClient(serverList.get(0).getAddress(), systems, expand); } } + private void log(ValueSet vs, String server, Set systems, List choices, String message) { + String svs = (vs == null ? "null" : vs.getVersionedUrl()); + String sys = systems.isEmpty() ? "--" : systems.size() == 1 ? systems.iterator().next() : systems.toString(); + String sch = choices.isEmpty() ? "--" : choices.size() == 1 ? choices.iterator().next().toString() : choices.toString(); + internalLog.add(new InternalLogEvent(message, server, svs, sys, sch)); + } + private TerminologyClientContext findClient(String server, Set systems, boolean expand) { TerminologyClientContext client = serverMap.get(server); if (client == null) { @@ -297,8 +346,8 @@ public class TerminologyClientManager { return ret; } catch (Exception e) { String msg = "Error resolving system "+url+": "+e.getMessage()+" ("+request+")"; - if (!internalLog.contains(msg)) { - internalLog.add(msg); + if (!hasMessage(msg)) { + internalLog.add(new InternalLogEvent(msg)); } if (!monitorServiceURL.contains("tx.fhir.org")) { e.printStackTrace(); @@ -308,6 +357,15 @@ public class TerminologyClientManager { } + private boolean hasMessage(String msg) { + for (InternalLogEvent log : internalLog) { + if (msg.equals(log.message)) { + return true; + } + } + return false; + } + public List serverList() { return serverList; } @@ -410,10 +468,6 @@ public class TerminologyClientManager { } } - public List getInternalLog() { - return internalLog; - } - public List getServerList() { return serverList; } @@ -518,8 +572,8 @@ public class TerminologyClientManager { } catch (Exception e) { e.printStackTrace(); String msg = "Error resolving valueSet "+canonical+": "+e.getMessage()+" ("+request+")"; - if (!internalLog.contains(msg)) { - internalLog.add(msg); + if (!hasMessage(msg)) { + internalLog.add(new InternalLogEvent(msg)); } e.printStackTrace(); return null; @@ -598,8 +652,8 @@ public class TerminologyClientManager { } catch (Exception e) { e.printStackTrace(); String msg = "Error resolving valueSet "+canonical+": "+e.getMessage()+" ("+request+")"; - if (!internalLog.contains(msg)) { - internalLog.add(msg); + if (!hasMessage(msg)) { + internalLog.add(new InternalLogEvent(msg)); } e.printStackTrace(); return null; @@ -614,5 +668,8 @@ public class TerminologyClientManager { } return false; } - + + public List getInternalLog() { + return internalLog; + } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/TerminologyCache.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/TerminologyCache.java index 3a60a33bc..8340cb092 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/TerminologyCache.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/TerminologyCache.java @@ -364,7 +364,7 @@ public class TerminologyCache { csCache.clear(); } - private void clear() throws IOException { + public void clear() throws IOException { if (folder != null) { Utilities.clearDirectory(folder); } From 56965820ea378447dbfe21e862123760ae8b4aec Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Sun, 13 Oct 2024 22:40:17 +0800 Subject: [PATCH 14/20] Add support for -advisor-file, and -clear-tx-cache --- .../utils/validation/IResourceValidator.java | 2 +- .../validation/IValidationPolicyAdvisor.java | 16 ++ .../hl7/fhir/validation/BaseValidator.java | 185 ++++++++---------- .../hl7/fhir/validation/ValidationEngine.java | 10 + .../org/hl7/fhir/validation/ValidatorCli.java | 3 + .../fhir/validation/cli/model/CliContext.java | 39 +++- .../services/StandAloneValidatorFetcher.java | 61 +++++- .../cli/services/ValidationService.java | 28 +++ .../hl7/fhir/validation/cli/utils/Params.java | 15 ++ .../instance/InstanceValidator.java | 16 -- .../BasePolicyAdvisorForFullValidation.java | 10 + .../advisor/JsonDrivenPolicyAdvisor.java | 65 ++---- .../advisor/RulesDrivenPolicyAdvisor.java | 176 +++++++++++++++++ .../advisor/TextDrivenPolicyAdvisor.java | 54 +++++ .../src/main/resources/help/advisor.txt | 2 +- .../validation/tests/ValidationTests.java | 10 + pom.xml | 2 +- 17 files changed, 521 insertions(+), 173 deletions(-) create mode 100644 org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/RulesDrivenPolicyAdvisor.java create mode 100644 org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/TextDrivenPolicyAdvisor.java diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/IResourceValidator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/IResourceValidator.java index 691c4fa73..4a938b2fe 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/IResourceValidator.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/IResourceValidator.java @@ -80,7 +80,7 @@ public interface IResourceValidator { IResourceValidator setFetcher(IValidatorResourceFetcher value); IValidationPolicyAdvisor getPolicyAdvisor(); - IResourceValidator setPolicyAdvisor(IValidationPolicyAdvisor advisor); + void setPolicyAdvisor(IValidationPolicyAdvisor advisor); IValidationProfileUsageTracker getTracker(); IResourceValidator setTracker(IValidationProfileUsageTracker value); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/IValidationPolicyAdvisor.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/IValidationPolicyAdvisor.java index 5af265edc..c87508dcd 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/IValidationPolicyAdvisor.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/IValidationPolicyAdvisor.java @@ -16,6 +16,21 @@ import org.hl7.fhir.r5.utils.validation.constants.BindingKind; public interface IValidationPolicyAdvisor { + /** + * Internal use, for chaining advisors + * + * @return + */ + ReferenceValidationPolicy getReferencePolicy(); + + /** + * + * @param path - the current path of the element + * @param messageId - the message id (from messages.properties) + * @return true if the validator should ignore the message + */ + boolean suppressMessageId(String path, String messageId); + /** * * @param validator @@ -169,5 +184,6 @@ public interface IValidationPolicyAdvisor { boolean valid, IMessagingServices msgServices, List messages); + } \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java index ab76c2d1f..cb6f23fdd 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java @@ -69,9 +69,11 @@ import org.hl7.fhir.r5.utils.XVerExtensionManager; import org.hl7.fhir.r5.utils.XVerExtensionManager.XVerExtensionStatus; import org.hl7.fhir.r5.utils.validation.IMessagingServices; import org.hl7.fhir.r5.utils.validation.IResourceValidator; +import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher; import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier.IValidationContextResourceLoader; import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel; +import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.utilities.FhirPublication; import org.hl7.fhir.utilities.StandardsStatus; @@ -85,6 +87,7 @@ import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; import org.hl7.fhir.utilities.validation.ValidationMessage.Source; import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.hl7.fhir.validation.cli.utils.ValidationLevel; +import org.hl7.fhir.validation.instance.advisor.BasePolicyAdvisorForFullValidation; import org.hl7.fhir.validation.instance.utils.IndexedElement; import org.hl7.fhir.validation.instance.utils.NodeStack; @@ -165,25 +168,31 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi protected final String BUNDLE = "Bundle"; protected final String LAST_UPDATED = "lastUpdated"; + protected String sessionId; protected BaseValidator parent; - protected Source source; protected IWorkerContext context; protected ValidationTimeTracker timeTracker = new ValidationTimeTracker(); protected XVerExtensionManager xverManager; - protected List trackedMessages = new ArrayList<>(); - protected List messagesToRemove = new ArrayList<>(); - protected ValidationLevel level = ValidationLevel.HINTS; - protected Coding jurisdiction; - protected boolean allowExamples; - protected boolean forPublication; - protected boolean debug; - protected boolean warnOnDraftOrExperimental; - protected Set statusWarnings = new HashSet<>(); - protected BestPracticeWarningLevel bpWarnings = BestPracticeWarningLevel.Warning; - protected String sessionId = Utilities.makeUuidLC(); - protected List usageContexts = new ArrayList(); - protected ValidationOptions baseOptions = new ValidationOptions(FhirPublication.R5); protected IValidatorResourceFetcher fetcher; + protected IValidationPolicyAdvisor policyAdvisor; + + // these two related to removing warnings on extensible bindings in structures that have derivatives that replace their bindings + protected List trackedMessages = new ArrayList<>(); + protected List messagesToRemove = new ArrayList<>(); + + // don't repeatedly raise the same warnings all the time + protected Set statusWarnings = new HashSet<>(); + + protected Source source; // @configuration + protected ValidationLevel level = ValidationLevel.HINTS; // @configuration + protected Coding jurisdiction; // @configuration + protected boolean allowExamples; // @configuration + protected boolean forPublication; // @configuration + protected boolean debug; // @configuration + protected boolean warnOnDraftOrExperimental; // @configuration + protected BestPracticeWarningLevel bpWarnings = BestPracticeWarningLevel.Warning; // @configuration + protected List usageContexts = new ArrayList(); // @configuration + protected ValidationOptions baseOptions = new ValidationOptions(FhirPublication.R5); // @configuration public BaseValidator(IWorkerContext context, XVerExtensionManager xverManager, boolean debug) { super(); @@ -193,6 +202,8 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi this.xverManager = new XVerExtensionManager(context); } this.debug = debug; + sessionId = Utilities.makeUuidLC(); + policyAdvisor = new BasePolicyAdvisorForFullValidation(ReferenceValidationPolicy.CHECK_VALID); urlRegex = Constants.URI_REGEX_XVER.replace("$$", CommaSeparatedStringBuilder.join("|", context.getResourceNames())); } @@ -217,6 +228,8 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi this.usageContexts.addAll(parent.usageContexts); this.baseOptions = parent.baseOptions; this.fetcher = parent.fetcher; + this.sessionId = parent.sessionId; + this.policyAdvisor = parent.policyAdvisor; } private boolean doingLevel(IssueSeverity error) { @@ -261,75 +274,22 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi protected String urlRegex; - /** - * Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails - * - * @param thePass - * Set this parameter to false if the validation does not pass - * @return Returns thePass (in other words, returns true if the rule did not fail validation) - */ - @Deprecated - protected boolean fail(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg) { - if (!thePass && doingErrors()) { - addValidationMessage(errors, ruleDate, type, line, col, path, msg, IssueSeverity.FATAL, null); + private boolean suppressMsg(String path, String theMessage) { + if (policyAdvisor == null) { + return false; + } else { + return policyAdvisor.suppressMessageId(path, theMessage); } - return thePass; } protected boolean fail(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingErrors()) { + if (!thePass && doingErrors() && !suppressMsg(path, theMessage)) { String msg = context.formatMessage(theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, line, col, path, msg, IssueSeverity.FATAL, theMessage); } return thePass; } - /** - * Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails - * - * @param thePass - * Set this parameter to false if the validation does not pass - * @return Returns thePass (in other words, returns true if the rule did not fail validation) - */ - @Deprecated - protected boolean fail(List errors, String ruleDate, IssueType type, List pathParts, boolean thePass, String msg) { - if (!thePass && doingErrors()) { - String path = toPath(pathParts); - addValidationMessage(errors, ruleDate, type, -1, -1, path, msg, IssueSeverity.FATAL, null); - } - return thePass; - } - - /** - * Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails - * - * @param thePass - * Set this parameter to false if the validation does not pass - * @return Returns thePass (in other words, returns true if the rule did not fail validation) - */ - @Deprecated - protected boolean fail(List errors, String ruleDate, IssueType type, List pathParts, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingErrors()) { - String path = toPath(pathParts); - addValidationMessage(errors, ruleDate, type, -1, -1, path, context.formatMessage(theMessage, theMessageArguments), IssueSeverity.FATAL, theMessage); - } - return thePass; - } - - /** - * Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails - * - * @param thePass - * Set this parameter to false if the validation does not pass - * @return Returns thePass (in other words, returns true if the rule did not fail validation) - */ - @Deprecated - protected boolean fail(List errors, String ruleDate, IssueType type, String path, boolean thePass, String msg) { - if (!thePass && doingErrors()) { - addValidationMessage(errors, ruleDate, type, -1, -1, path, msg, IssueSeverity.FATAL, null); - } - return thePass; - } //TODO: i18n protected boolean grammarWord(String w) { return w.equals("and") || w.equals("or") || w.equals("a") || w.equals("the") || w.equals("for") || w.equals("this") || w.equals("that") || w.equals("of"); @@ -343,7 +303,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean hint(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg) { - if (!thePass && doingHints()) { + if (!thePass && doingHints() && !suppressMsg(path, msg)) { String message = context.formatMessage(msg); addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.INFORMATION, msg); } @@ -358,7 +318,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean hintInv(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg, String invId) { - if (!thePass && doingHints()) { + if (!thePass && doingHints() && !suppressMsg(path, invId)) { String message = context.formatMessage(msg); addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.INFORMATION, msg).setInvId(invId); } @@ -397,7 +357,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean hint(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingHints()) { + if (!thePass && doingHints() && !suppressMsg(path, theMessage)) { String message = context.formatMessage(theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.INFORMATION, theMessage); } @@ -405,7 +365,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi } protected boolean hintPlural(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, int num, String theMessage, Object... theMessageArguments) { - if (!thePass && doingHints()) { + if (!thePass && doingHints() && !suppressMsg(path, theMessage)) { String message = context.formatMessagePlural(num, theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.INFORMATION, theMessage); } @@ -418,7 +378,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi } protected boolean txHint(List errors, String ruleDate, String txLink, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingHints()) { + if (!thePass && doingHints() && !suppressMsg(path, theMessage)) { String message = context.formatMessage(theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.INFORMATION, Source.TerminologyEngine, theMessage).setTxLink(txLink); } @@ -433,7 +393,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean hint(List errors, String ruleDate, IssueType type, List pathParts, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingHints()) { + if (!thePass && doingHints() && !suppressMsg(CommaSeparatedStringBuilder.join(".", pathParts), theMessage)) { String path = toPath(pathParts); String message = context.formatMessage(theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.INFORMATION, theMessage); @@ -449,7 +409,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean hint(List errors, String ruleDate, IssueType type, String path, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingHints()) { + if (!thePass && doingHints() && !suppressMsg(path, theMessage)) { String message = context.formatMessage(theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.INFORMATION, null); } @@ -464,7 +424,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean rule(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingErrors()) { + if (!thePass && doingErrors() && !suppressMsg(path, theMessage)) { String message = context.formatMessage(theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.ERROR, theMessage); } @@ -472,7 +432,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi } protected boolean ruleInv(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String theMessage, String invId, Object... theMessageArguments) { - if (!thePass && doingErrors()) { + if (!thePass && doingErrors() && !suppressMsg(path, theMessage)) { String message = context.formatMessage(theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.ERROR, invId).setInvId(invId); } @@ -480,7 +440,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi } protected boolean rule(List errors, String ruleDate, IssueType type, NodeStack stack, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingErrors()) { + if (!thePass && doingErrors() && !suppressMsg(stack.getLiteralPath(), theMessage)) { String message = context.formatMessage(theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, stack.line(), stack.col(), stack.getLiteralPath(), message, IssueSeverity.ERROR, theMessage); } @@ -492,7 +452,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi } protected boolean rulePlural(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, int num, String theMessage, Object... theMessageArguments) { - if (!thePass && doingErrors()) { + if (!thePass && doingErrors() && !suppressMsg(path, theMessage)) { String message = context.formatMessagePlural(num, theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.ERROR, theMessage); } @@ -500,7 +460,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi } protected boolean txRule(List errors, String ruleDate, String txLink, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingErrors()) { + if (!thePass && doingErrors() && !suppressMsg(path, theMessage)) { String message = context.formatMessage(theMessage, theMessageArguments); ValidationMessage vm = new ValidationMessage(Source.TerminologyEngine, type, line, col, path, message, IssueSeverity.ERROR).setMessageId(idForMessage(theMessage, message)); vm.setRuleDate(ruleDate); @@ -538,7 +498,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean rule(List errors, String ruleDate, IssueType type, List pathParts, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingErrors()) { + if (!thePass && doingErrors() && !suppressMsg(CommaSeparatedStringBuilder.join(".", pathParts), theMessage)) { String path = toPath(pathParts); String message = context.formatMessage(theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.ERROR, theMessage); @@ -556,7 +516,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi protected boolean rule(List errors, String ruleDate, IssueType type, String path, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingErrors()) { + if (!thePass && doingErrors() && !suppressMsg(path, theMessage)) { String message = context.formatMessage(theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.ERROR, theMessage); } @@ -564,7 +524,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi } protected boolean rulePlural(List errors, String ruleDate, IssueType type, String path, boolean thePass, int num, String theMessage, Object... theMessageArguments) { - if (!thePass && doingErrors()) { + if (!thePass && doingErrors() && !suppressMsg(path, theMessage)) { String message = context.formatMessagePlural(num, theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.ERROR, theMessage); } @@ -630,7 +590,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean warning(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(path, msg)) { String nmsg = context.formatMessage(msg, theMessageArguments); IssueSeverity severity = IssueSeverity.WARNING; addValidationMessage(errors, ruleDate, type, line, col, path, nmsg, severity, msg); @@ -640,7 +600,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi } protected boolean warning(List errors, String ruleDate, IssueType type, int line, int col, String path, String id, boolean thePass, String msg, Object... theMessageArguments) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(path, msg)) { String nmsg = context.formatMessage(msg, theMessageArguments); IssueSeverity severity = IssueSeverity.WARNING; addValidationMessage(errors, ruleDate, type, line, col, path, nmsg, severity, id); @@ -650,7 +610,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi } protected boolean warningInv(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg, String invId, Object... theMessageArguments) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(path, invId)) { String nmsg = context.formatMessage(msg, theMessageArguments); IssueSeverity severity = IssueSeverity.WARNING; String id = idForMessage(msg, nmsg); @@ -669,7 +629,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi } protected boolean warningPlural(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, int num, String msg, Object... theMessageArguments) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(path, msg)) { String nmsg = context.formatMessagePlural(num, msg, theMessageArguments); IssueSeverity severity = IssueSeverity.WARNING; addValidationMessage(errors, ruleDate, type, line, col, path, nmsg, severity, msg); @@ -711,7 +671,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean txWarning(List errors, String ruleDate, String txLink, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(path, msg)) { String nmsg = context.formatMessage(msg, theMessageArguments); ValidationMessage vmsg = new ValidationMessage(Source.TerminologyEngine, type, line, col, path, nmsg, IssueSeverity.WARNING).setTxLink(txLink).setMessageId(idForMessage(msg, nmsg)); vmsg.setRuleDate(ruleDate); @@ -744,7 +704,9 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi vmsg.setMessageId(issue.getExtensionString(ToolingExtensions.EXT_ISSUE_MSG_ID)); } - errors.add(vmsg); + if (!suppressMsg(path, vmsg.getMessageId())) { + errors.add(vmsg); + } return vmsg; } @@ -760,7 +722,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean txWarningForLaterRemoval(Object location, List errors, String ruleDate, String txLink, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(path, msg)) { String nmsg = context.formatMessage(msg, theMessageArguments); ValidationMessage vmsg = new ValidationMessage(Source.TerminologyEngine, type, line, col, path, nmsg, IssueSeverity.WARNING).setTxLink(txLink).setMessageId(msg); vmsg.setRuleDate(ruleDate); @@ -789,7 +751,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi } protected boolean warningOrError(boolean isError, List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) { - if (!thePass) { + if (!thePass && !suppressMsg(path, msg)) { String nmsg = context.formatMessage(msg, theMessageArguments); IssueSeverity lvl = isError ? IssueSeverity.ERROR : IssueSeverity.WARNING; if (doingLevel(lvl)) { @@ -805,7 +767,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi } protected boolean hintOrError(boolean isError, List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) { - if (!thePass) { + if (!thePass && !suppressMsg(path, msg)) { String nmsg = context.formatMessage(msg, theMessageArguments); IssueSeverity lvl = isError ? IssueSeverity.ERROR : IssueSeverity.INFORMATION; if (doingLevel(lvl)) { @@ -824,7 +786,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean warning(List errors, String ruleDate, IssueType type, List pathParts, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(CommaSeparatedStringBuilder.join(".", pathParts), theMessage)) { String path = toPath(pathParts); String message = context.formatMessage(theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.WARNING, theMessage); @@ -840,7 +802,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean warning(List errors, String ruleDate, IssueType type, String path, boolean thePass, String msg, Object... theMessageArguments) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(path, msg)) { String message = context.formatMessage(msg, theMessageArguments); addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.WARNING, null); } @@ -855,7 +817,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean warningOrHint(List errors, String ruleDate, IssueType type, String path, boolean thePass, boolean warning, String msg, Object... theMessageArguments) { - if (!thePass) { + if (!thePass && !suppressMsg(path, msg)) { String message = context.formatMessage(msg, theMessageArguments); IssueSeverity lvl = warning ? IssueSeverity.WARNING : IssueSeverity.INFORMATION; if (doingLevel(lvl)) { @@ -873,7 +835,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean warningHtml(List errors, String ruleDate, IssueType type, String path, boolean thePass, String msg, String html) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(path, msg)) { addValidationMessage(errors, ruleDate, type, path, msg, html, IssueSeverity.WARNING, null); } return thePass; @@ -887,7 +849,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean warningHtml(List errors, String ruleDate, IssueType type, String path, boolean thePass, String msg, String html, Object... theMessageArguments) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(path, msg)) { String nmsg = context.formatMessage(msg, theMessageArguments); addValidationMessage(errors, ruleDate, type, path, nmsg, html, IssueSeverity.WARNING, msg); } @@ -903,7 +865,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean suppressedwarning(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(path, msg)) { String nmsg = context.formatMessage(msg, theMessageArguments); addValidationMessage(errors, ruleDate, type, line, col, path, nmsg, IssueSeverity.INFORMATION, msg); } @@ -919,7 +881,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean suppressedwarning(List errors, String ruleDate, IssueType type, List pathParts, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(CommaSeparatedStringBuilder.join(".", pathParts), theMessage)) { String path = toPath(pathParts); String message = context.formatMessage(theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.INFORMATION, theMessage); @@ -974,7 +936,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean suppressedwarning(List errors, String ruleDate, IssueType type, String path, boolean thePass, String msg, String html, Object... theMessageArguments) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(path, msg)) { String nmsg = context.formatMessage(msg, theMessageArguments); addValidationMessage(errors, ruleDate, type, path, nmsg, html, IssueSeverity.INFORMATION, msg); } @@ -1681,4 +1643,17 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi return false; } + public IValidationPolicyAdvisor getPolicyAdvisor() { + return policyAdvisor; + } + + public void setPolicyAdvisor(IValidationPolicyAdvisor advisor) { + if (advisor == null) { + throw new Error("Cannot set advisor to null"); + } + this.policyAdvisor = advisor; + } + + + } \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java index 72808e09c..545670b98 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java @@ -1288,4 +1288,14 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP definition, structure, resource, valid, msgServices, messages); } + @Override + public boolean suppressMessageId(String path, String messageId) { + return policyAdvisor.suppressMessageId(path, messageId); + } + + @Override + public ReferenceValidationPolicy getReferencePolicy() { + return ReferenceValidationPolicy.IGNORE; + } + } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java index 2e8631384..86b461fbe 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java @@ -376,6 +376,9 @@ public class ValidatorCli { ((StandaloneTask) cliTask).executeTask(cliContext,params,tt,tts); } + if (cliContext.getAdvisorFile() != null) { + System.out.println("Note: Some validation issues might be hidden by the advisor settings in the file "+cliContext.getAdvisorFile()); + } System.out.println("Done. " + tt.report()+". Max Memory = "+Utilities.describeSize(Runtime.getRuntime().maxMemory())); SystemExitManager.finish(); } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java index 6d5e0cb4a..37a4b9c5a 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java @@ -132,6 +132,12 @@ public class CliContext { @JsonProperty("showTimes") private boolean showTimes = false; + @JsonProperty("showTerminologyRouting") + private boolean showTerminologyRouting = false; + + @JsonProperty("clearTxCache") + private boolean clearTxCache = false; + @JsonProperty("locale") private String locale = Locale.ENGLISH.toLanguageTag(); @@ -172,6 +178,9 @@ public class CliContext { @JsonProperty("noExperimentalContent") private boolean noExperimentalContent; + @JsonProperty("advisorFile") + private String advisorFile; + @JsonProperty("baseEngine") public String getBaseEngine() { return baseEngine; @@ -754,6 +763,22 @@ public class CliContext { this.showTimes = showTimes; } + public boolean isShowTerminologyRouting() { + return showTerminologyRouting; + } + + public void setShowTerminologyRouting(boolean showTerminologyRouting) { + this.showTerminologyRouting = showTerminologyRouting; + } + + public boolean isClearTxCache() { + return clearTxCache; + } + + public void setClearTxCache(boolean clearTxCache) { + this.clearTxCache = clearTxCache; + } + public String getOutputStyle() { return outputStyle; } @@ -852,6 +877,7 @@ public class CliContext { Objects.equals(watchScanDelay, that.watchScanDelay) && Objects.equals(unknownCodeSystemsCauseErrors, that.unknownCodeSystemsCauseErrors) && Objects.equals(noExperimentalContent, that.noExperimentalContent) && + Objects.equals(advisorFile, that.advisorFile) && Objects.equals(watchSettleTime, that.watchSettleTime) ; } @@ -860,7 +886,7 @@ public class CliContext { return Objects.hash(baseEngine, doNative, extensions, hintAboutNonMustSupport, recursive, doDebug, assumeValidRestReferences, canDoNative, noInternalCaching, noExtensibleBindingMessages, noInvariants, displayWarnings, wantInvariantsInMessages, map, output, outputSuffix, htmlOutput, txServer, sv, txLog, txCache, mapLog, lang, srcLang, tgtLang, fhirpath, snomedCT, targetVer, igs, questionnaireMode, level, profiles, sources, inputs, mode, locale, locations, crumbTrails, showMessageIds, forPublication, showTimes, allowExampleUrls, outputStyle, jurisdiction, noUnicodeBiDiControlChars, - watchMode, watchScanDelay, watchSettleTime, bestPracticeLevel, unknownCodeSystemsCauseErrors, noExperimentalContent, htmlInMarkdownCheck, allowDoubleQuotesInFHIRPath, checkIPSCodes); + watchMode, watchScanDelay, watchSettleTime, bestPracticeLevel, unknownCodeSystemsCauseErrors, noExperimentalContent, advisorFile, htmlInMarkdownCheck, allowDoubleQuotesInFHIRPath, checkIPSCodes); } @Override @@ -922,6 +948,7 @@ public class CliContext { ", watchScanDelay=" + watchScanDelay + ", unknownCodeSystemsCauseErrors=" + unknownCodeSystemsCauseErrors + ", noExperimentalContent=" + noExperimentalContent + + ", advisorFile=" + advisorFile + '}'; } @@ -1002,5 +1029,15 @@ public class CliContext { this.noExperimentalContent = noExperimentalContent; } + @JsonProperty("advisorFile") + public String getAdvisorFile() { + return advisorFile; + } + + @JsonProperty("advisorFile") + public void setAdvisorFile(String advisorFile) { + this.advisorFile = advisorFile; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/StandAloneValidatorFetcher.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/StandAloneValidatorFetcher.java index a239fd2d4..20beab9a8 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/StandAloneValidatorFetcher.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/StandAloneValidatorFetcher.java @@ -17,12 +17,14 @@ import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.context.IWorkerContextManager; import org.hl7.fhir.r5.elementmodel.Element; +import org.hl7.fhir.r5.elementmodel.Element.SpecialElement; import org.hl7.fhir.r5.model.CanonicalResource; import org.hl7.fhir.r5.model.ElementDefinition; 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.client.ITerminologyClient; +import org.hl7.fhir.r5.utils.validation.IMessagingServices; import org.hl7.fhir.r5.utils.validation.IResourceValidator; import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher; @@ -41,13 +43,14 @@ import org.hl7.fhir.utilities.json.model.JsonObject; import org.hl7.fhir.utilities.json.parser.JsonParser; import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager; import org.hl7.fhir.utilities.npm.NpmPackage; +import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.validation.cli.utils.Common; import org.hl7.fhir.validation.instance.advisor.BasePolicyAdvisorForFullValidation; import javax.annotation.Nonnull; -public class StandAloneValidatorFetcher extends BasePolicyAdvisorForFullValidation implements IValidatorResourceFetcher, IValidationPolicyAdvisor, IWorkerContextManager.ICanonicalResourceLocator { +public class StandAloneValidatorFetcher implements IValidatorResourceFetcher, IValidationPolicyAdvisor, IWorkerContextManager.ICanonicalResourceLocator { List mappingsUris = new ArrayList<>(); private FilesystemPackageCacheManager pcm; @@ -56,12 +59,13 @@ public class StandAloneValidatorFetcher extends BasePolicyAdvisorForFullValidati private Map urlList = new HashMap<>(); private Map pidList = new HashMap<>(); private Map pidMap = new HashMap<>(); - + private IValidationPolicyAdvisor policyAdvisor; + public StandAloneValidatorFetcher(FilesystemPackageCacheManager pcm, IWorkerContext context, IPackageInstaller installer) { - super(ReferenceValidationPolicy.IGNORE); this.pcm = pcm; this.context = context; this.installer = installer; + this.policyAdvisor = new BasePolicyAdvisorForFullValidation(ReferenceValidationPolicy.IGNORE); } @Override @@ -297,4 +301,55 @@ public class StandAloneValidatorFetcher extends BasePolicyAdvisorForFullValidati return new HashSet<>(); } + @Override + public boolean suppressMessageId(String path, String messageId) { + return policyAdvisor.suppressMessageId(path, messageId); + } + + @Override + public ContainedReferenceValidationPolicy policyForContained(IResourceValidator validator, Object appContext, + StructureDefinition structure, ElementDefinition element, String containerType, String containerId, + SpecialElement containingResourceType, String path, String url) { + return policyAdvisor.policyForContained(validator, appContext, structure, element, containerType, containerId, containingResourceType, path, url); + } + + @Override + public EnumSet policyForResource(IResourceValidator validator, Object appContext, + StructureDefinition type, String path) { + return policyAdvisor.policyForResource(validator, appContext, type, path); + } + + @Override + public EnumSet policyForElement(IResourceValidator validator, Object appContext, + StructureDefinition structure, ElementDefinition element, String path) { + return policyAdvisor.policyForElement(validator, appContext, structure, element, path); + } + + @Override + public EnumSet policyForCodedContent(IResourceValidator validator, Object appContext, + String stackPath, ElementDefinition definition, StructureDefinition structure, BindingKind kind, + AdditionalBindingPurpose purpose, ValueSet valueSet, List systems) { + return policyAdvisor.policyForCodedContent(validator, appContext, stackPath, definition, structure, kind, purpose, valueSet, systems); + } + + @Override + public List getImpliedProfilesForResource(IResourceValidator validator, Object appContext, + String stackPath, ElementDefinition definition, StructureDefinition structure, Element resource, boolean valid, + IMessagingServices msgServices, List messages) { + return policyAdvisor.getImpliedProfilesForResource(validator, appContext, stackPath, definition, structure, resource, valid, msgServices, messages); + } + + @Override + public ReferenceValidationPolicy getReferencePolicy() { + return policyAdvisor.getReferencePolicy(); + } + + public IValidationPolicyAdvisor getPolicyAdvisor() { + return policyAdvisor; + } + + public void setPolicyAdvisor(IValidationPolicyAdvisor policyAdvisor) { + this.policyAdvisor = policyAdvisor; + } + } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java index 644926361..b06bcdd55 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java @@ -40,6 +40,7 @@ import org.hl7.fhir.r5.renderers.spreadsheets.ConceptMapSpreadsheetGenerator; import org.hl7.fhir.r5.renderers.spreadsheets.StructureDefinitionSpreadsheetGenerator; import org.hl7.fhir.r5.renderers.spreadsheets.ValueSetSpreadsheetGenerator; import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; +import org.hl7.fhir.r5.terminologies.client.TerminologyClientManager.InternalLogEvent; import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache; import org.hl7.fhir.utilities.FhirPublication; import org.hl7.fhir.utilities.SystemExitManager; @@ -69,6 +70,8 @@ import org.hl7.fhir.validation.cli.renderers.ValidationOutputRenderer; import org.hl7.fhir.validation.cli.utils.Common; import org.hl7.fhir.validation.cli.utils.EngineMode; import org.hl7.fhir.validation.cli.utils.VersionSourceInformation; +import org.hl7.fhir.validation.instance.advisor.JsonDrivenPolicyAdvisor; +import org.hl7.fhir.validation.instance.advisor.TextDrivenPolicyAdvisor; public class ValidationService { @@ -155,6 +158,7 @@ public class ValidationService { if (request.getCliContext().isShowTimes()) { response.getValidationTimes().put(fileToValidate.getFileName(), validatedFragments.getValidationTime()); } + } } @@ -289,6 +293,19 @@ public class ValidationService { TextFile.stringToFile(html, cliContext.getHtmlOutput()); System.out.println("HTML Summary in " + cliContext.getHtmlOutput()); } + + if (cliContext.isShowTerminologyRouting()) { + System.out.println(""); + System.out.println("Terminology Routing Dump ---------------------------------------"); + if (validator.getContext().getTxClientManager().getInternalLog().isEmpty()) { + System.out.println("(nothing happened)"); + } else { + for (InternalLogEvent log : validator.getContext().getTxClientManager().getInternalLog()) { + System.out.println(log.getMessage()+" -> "+log.getServer()+" (for VS "+log.getVs()+" with systems '"+log.getSystems()+"', choices = '"+log.getChoices()+"')"); + } + } + validator.getContext().getTxClientManager().getInternalLog().clear(); + } } if (watch != ValidatorWatchMode.NONE) { if (statusNeeded) { @@ -545,6 +562,10 @@ public class ValidationService { System.out.println(" No Terminology Cache"); } else { System.out.println(" Terminology Cache at "+validationEngine.getContext().getTxCache().getFolder()); + if (cliContext.isClearTxCache()) { + System.out.println(" Terminology Cache Entries Cleaned out"); + validationEngine.getContext().getTxCache().clear(); + } } System.out.print(" Get set... "); validationEngine.setQuestionnaireMode(cliContext.getQuestionnaireMode()); @@ -584,6 +605,13 @@ public class ValidationService { validationEngine.setFetcher(fetcher); validationEngine.getContext().setLocator(fetcher); validationEngine.setPolicyAdvisor(fetcher); + if (cliContext.getAdvisorFile() != null) { + if (cliContext.getAdvisorFile().endsWith(".json")) { + fetcher.setPolicyAdvisor(new JsonDrivenPolicyAdvisor(fetcher.getPolicyAdvisor(), new File(cliContext.getAdvisorFile()))); + } else { + fetcher.setPolicyAdvisor(new TextDrivenPolicyAdvisor(fetcher.getPolicyAdvisor(), new File(cliContext.getAdvisorFile()))); + } + } } validationEngine.getBundleValidationRules().addAll(cliContext.getBundleValidationRules()); validationEngine.setJurisdiction(CodeSystemUtilities.readCoding(cliContext.getJurisdiction())); diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java index 8f646e86b..675acd332 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java @@ -54,6 +54,8 @@ public class Params { public static final String TERMINOLOGY = "-tx"; public static final String TERMINOLOGY_LOG = "-txLog"; public static final String TERMINOLOGY_CACHE = "-txCache"; + public static final String TERMINOLOGY_ROUTING = "-tx-routing"; + public static final String TERMINOLOGY_CACHE_CLEAR = "-clear-tx-cache"; public static final String LOG = "-log"; public static final String LANGUAGE = "-language"; public static final String IMPLEMENTATION_GUIDE = "-ig"; @@ -86,6 +88,7 @@ public class Params { public static final String SHOW_TIMES = "-show-times"; public static final String ALLOW_EXAMPLE_URLS = "-allow-example-urls"; public static final String OUTPUT_STYLE = "-output-style"; + public static final String ADVSIOR_FILE = "-advisor-file"; public static final String DO_IMPLICIT_FHIRPATH_STRING_CONVERSION = "-implicit-fhirpath-string-conversions"; public static final String JURISDICTION = "-jurisdiction"; public static final String HTML_IN_MARKDOWN = "-html-in-markdown"; @@ -339,10 +342,22 @@ public class Params { } else { throw new Error("Value for "+ALLOW_EXAMPLE_URLS+" not understood: "+bl); } + } else if (args[i].equals(TERMINOLOGY_ROUTING)) { + cliContext.setShowTerminologyRouting(true); + } else if (args[i].equals(TERMINOLOGY_CACHE_CLEAR)) { + cliContext.setClearTxCache(true); } else if (args[i].equals(SHOW_TIMES)) { cliContext.setShowTimes(true); } else if (args[i].equals(OUTPUT_STYLE)) { cliContext.setOutputStyle(args[++i]); + } else if (args[i].equals(ADVSIOR_FILE)) { + cliContext.setAdvisorFile(args[++i]); + File f = new File(cliContext.getAdvisorFile()); + if (!f.exists()) { + throw new Error("Cannot find advisor file "+cliContext.getAdvisorFile()); + } else if (!Utilities.existsInList(Utilities.getFileExtension(f.getName()), "json", "txt")) { + throw new Error("Advisor file "+cliContext.getAdvisorFile()+" must be a .json or a .txt file"); + } } else if (args[i].equals(SCAN)) { cliContext.setMode(EngineMode.SCAN); } else if (args[i].equals(TERMINOLOGY)) { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java index 2d511bf6a..0028218ed 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java @@ -574,7 +574,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private boolean noBindingMsgSuppressed; private Map fetchCache = new HashMap<>(); private HashMap resourceTracker = new HashMap<>(); - private IValidationPolicyAdvisor policyAdvisor = new BasePolicyAdvisorForFullValidation(ReferenceValidationPolicy.CHECK_VALID); long time = 0; long start = 0; long lastlog = 0; @@ -664,21 +663,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return this; } - - @Override - public IValidationPolicyAdvisor getPolicyAdvisor() { - return policyAdvisor; - } - - @Override - public IResourceValidator setPolicyAdvisor(IValidationPolicyAdvisor advisor) { - if (advisor == null) { - throw new Error("Cannot set advisor to null"); - } - this.policyAdvisor = advisor; - return this; - } - public IValidationProfileUsageTracker getTracker() { return this.tracker; } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/BasePolicyAdvisorForFullValidation.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/BasePolicyAdvisorForFullValidation.java index 4fd00c536..79b63eecb 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/BasePolicyAdvisorForFullValidation.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/BasePolicyAdvisorForFullValidation.java @@ -169,5 +169,15 @@ public class BasePolicyAdvisorForFullValidation implements IValidationPolicyAdvi return false; } + @Override + public boolean suppressMessageId(String path, String messageId) { + return false; + } + + @Override + public ReferenceValidationPolicy getReferencePolicy() { + return refpol; + } + } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/JsonDrivenPolicyAdvisor.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/JsonDrivenPolicyAdvisor.java index abaed6596..8121f9d62 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/JsonDrivenPolicyAdvisor.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/JsonDrivenPolicyAdvisor.java @@ -18,60 +18,35 @@ import org.hl7.fhir.r5.utils.validation.constants.BindingKind; import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy; import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; import org.hl7.fhir.utilities.json.JsonException; +import org.hl7.fhir.utilities.json.model.JsonElement; import org.hl7.fhir.utilities.json.model.JsonObject; import org.hl7.fhir.utilities.json.parser.JsonParser; import org.hl7.fhir.utilities.validation.ValidationMessage; -public class JsonDrivenPolicyAdvisor implements IValidationPolicyAdvisor { +public class JsonDrivenPolicyAdvisor extends RulesDrivenPolicyAdvisor { - private JsonObject config; - private IValidationPolicyAdvisor base; - - public JsonDrivenPolicyAdvisor(IValidationPolicyAdvisor base, JsonObject config) { - this.base = base; - this.config = config; + public JsonDrivenPolicyAdvisor(IValidationPolicyAdvisor base, File source) throws JsonException, IOException { + super(base); + load(source); } - public JsonDrivenPolicyAdvisor(String config) throws JsonException, IOException { - this.config = JsonParser.parseObject(config, true); + public JsonDrivenPolicyAdvisor(ReferenceValidationPolicy refpol, File source) throws JsonException, IOException { + super(refpol); + load(source); } - public JsonDrivenPolicyAdvisor(File config) throws JsonException, IOException { - this.config = JsonParser.parseObject(new FileInputStream(config), true); - } - - public JsonDrivenPolicyAdvisor(FileInputStream config) throws JsonException, IOException { - this.config = JsonParser.parseObject(config, true); - } - - @Override - public ReferenceValidationPolicy policyForReference(IResourceValidator validator, Object appContext, String path, String url) { - return base.policyForReference(validator, appContext, path, url); - } - - @Override - public ContainedReferenceValidationPolicy policyForContained(IResourceValidator validator, Object appContext, StructureDefinition structure, ElementDefinition element, String containerType, String containerId, SpecialElement containingResourceType, String path, String url) { - return base.policyForContained(validator, appContext, structure, element, containerType, containerId, containingResourceType, path, url); - } - - @Override - public EnumSet policyForResource(IResourceValidator validator, Object appContext, StructureDefinition type, String path) { - return base.policyForResource(validator, appContext, type, path); - } - - @Override - public EnumSet policyForElement(IResourceValidator validator, Object appContext, StructureDefinition structure, ElementDefinition element, String path) { - return base.policyForElement(validator, appContext, structure, element, path); - } - - @Override - public EnumSet policyForCodedContent(IResourceValidator validator, Object appContext, String stackPath, ElementDefinition definition, StructureDefinition structure, BindingKind kind, AdditionalBindingPurpose purpose, ValueSet valueSet, List systems) { - return base.policyForCodedContent(validator, appContext, stackPath, definition, structure, kind, purpose, valueSet, systems); - } - - @Override - public List getImpliedProfilesForResource(IResourceValidator validator, Object appContext, String stackPath, ElementDefinition definition, StructureDefinition structure, Element resource, boolean valid, IMessagingServices msgServices, List messages) { - return base.getImpliedProfilesForResource(validator, appContext, stackPath, definition, structure, resource, valid, msgServices, messages); + private void load(File source) throws JsonException, IOException { + JsonObject json = JsonParser.parseObject(source); + for (JsonElement e : json.forceArray("suppress").getItems()) { + String s = e.asString(); + String id = s; + String path = null; + if (s.contains("@")) { + id = s.substring(0, s.indexOf("@")); + path = s.substring(s.indexOf("@")+1); + } + addSuppressMessageRule(id, path); + } } } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/RulesDrivenPolicyAdvisor.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/RulesDrivenPolicyAdvisor.java new file mode 100644 index 000000000..fe5a9f4ad --- /dev/null +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/RulesDrivenPolicyAdvisor.java @@ -0,0 +1,176 @@ +package org.hl7.fhir.validation.instance.advisor; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; + +import org.hl7.fhir.r5.elementmodel.Element; +import org.hl7.fhir.r5.model.ElementDefinition; +import org.hl7.fhir.r5.model.StructureDefinition; +import org.hl7.fhir.r5.model.ValueSet; +import org.hl7.fhir.r5.utils.validation.IMessagingServices; +import org.hl7.fhir.r5.utils.validation.IResourceValidator; +import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; +import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.AdditionalBindingPurpose; +import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.CodedContentValidationAction; +import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.ElementValidationAction; +import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.ResourceValidationAction; +import org.hl7.fhir.r5.utils.validation.constants.BindingKind; +import org.hl7.fhir.r5.utils.validation.constants.CodedContentValidationPolicy; +import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy; +import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; +import org.hl7.fhir.utilities.validation.ValidationMessage; + +public class RulesDrivenPolicyAdvisor extends BasePolicyAdvisorForFullValidation { + + private IValidationPolicyAdvisor base; + + public RulesDrivenPolicyAdvisor(ReferenceValidationPolicy refpol) { + super(refpol); + base = null; + } + + public RulesDrivenPolicyAdvisor(IValidationPolicyAdvisor base) { + super(base.getReferencePolicy()); + this.base = base; + } + + private class SuppressMessageRule { + private String id; + private String path; + protected SuppressMessageRule(String id, String path) { + super(); + this.id = id; + this.path = path; + } + public String getId() { + return id; + } + public String getPath() { + return path; + } + public boolean matches(String mid, String p) { + if (((id == null) || id.equals(mid)) && ((path == null) || path.equals(p))) { + suppressed++; + return true; + } else if (((id == null) || mid.matches(id)) && ((path == null) || p.matches(path))) { + suppressed++; + return true; + } else { + return false; + } + } + } + + private List suppressMessageRules = new ArrayList<>(); + private int suppressed = 0; + + protected void addSuppressMessageRule(String id, String path) { + suppressMessageRules.add(new SuppressMessageRule(id, path)); + } + + @Override + public boolean suppressMessageId(String path, String messageId) { + for (SuppressMessageRule rule : suppressMessageRules) { + if (rule.matches(messageId, path)) { + return true; + } + } + if (base != null) { + return base.suppressMessageId(path, messageId); + } else { + return super.suppressMessageId(path, messageId); + } + } + + + @Override + public ReferenceValidationPolicy policyForReference(IResourceValidator validator, + Object appContext, + String path, + String url) { + if (base != null) { + return base.policyForReference(validator, appContext, path, url); + } else { + return super.policyForReference(validator, appContext, path, url); + } + } + + @Override + public ContainedReferenceValidationPolicy policyForContained(IResourceValidator validator, + Object appContext, + StructureDefinition structure, + ElementDefinition element, + String containerType, + String containerId, + Element.SpecialElement containingResourceType, + String path, + String url) { + if (base != null) { + return base.policyForContained(validator, appContext, structure, element, containerType, containerId, containingResourceType, path, url); + } else { + return super.policyForContained(validator, appContext, structure, element, containerType, containerId, containingResourceType, path, url); + } + } + + @Override + public EnumSet policyForResource(IResourceValidator validator, + Object appContext, + StructureDefinition type, + String path) { + if (base != null) { + return base.policyForResource(validator, appContext, type, path); + } else { + return super.policyForResource(validator, appContext, type, path); + } + } + + @Override + public EnumSet policyForElement(IResourceValidator validator, + Object appContext, + StructureDefinition structure, + ElementDefinition element, + String path) { + if (base != null) { + return base.policyForElement(validator, appContext, structure, element, path); + } else { + return super.policyForElement(validator, appContext, structure, element, path); + } + } + + @Override + public EnumSet policyForCodedContent(IResourceValidator validator, + Object appContext, + String stackPath, + ElementDefinition definition, + StructureDefinition structure, + BindingKind kind, + AdditionalBindingPurpose purpose, + ValueSet valueSet, + List systems) { + if (base != null) { + return base.policyForCodedContent(validator, appContext, stackPath, definition, structure, kind, purpose, valueSet, systems); + } else { + return super.policyForCodedContent(validator, appContext, stackPath, definition, structure, kind, purpose, valueSet, systems); + } + } + + @Override + public List getImpliedProfilesForResource(IResourceValidator validator, + Object appContext, + String stackPath, + ElementDefinition definition, + StructureDefinition structure, + Element resource, + boolean valid, + IMessagingServices msgServices, + List messages) { + if (base != null) { + return base.getImpliedProfilesForResource(validator, appContext, stackPath, definition, structure, resource, valid, msgServices, messages); + } else { + return super.getImpliedProfilesForResource(validator, appContext, stackPath, definition, structure, resource, valid, msgServices, messages); + } + } + + +} diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/TextDrivenPolicyAdvisor.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/TextDrivenPolicyAdvisor.java new file mode 100644 index 000000000..2920d51ae --- /dev/null +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/TextDrivenPolicyAdvisor.java @@ -0,0 +1,54 @@ +package org.hl7.fhir.validation.instance.advisor; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; + +import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; +import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.json.JsonException; + +public class TextDrivenPolicyAdvisor extends RulesDrivenPolicyAdvisor { + + public TextDrivenPolicyAdvisor(IValidationPolicyAdvisor base, File source) throws JsonException, IOException { + super(base); + load(source); + } + + public TextDrivenPolicyAdvisor(ReferenceValidationPolicy refpol, File source) throws JsonException, IOException { + super(refpol); + load(source); + } + + private void load(File source) throws JsonException, IOException { + BufferedReader reader = new BufferedReader(new FileReader(source)); + String line = reader.readLine(); + while (line != null) { + processLine(line); + line = reader.readLine(); + } + reader.close(); + } + + private void processLine(String line) { + line = line.trim(); + if (Utilities.noString(line) || line.startsWith("#")) { + return; + } + if (line.startsWith("-")) { + String s = line.substring(1).trim(); + String id = s; + String path = null; + if (s.contains("@")) { + id = s.substring(0, s.indexOf("@")); + path = s.substring(s.indexOf("@")+1); + } + addSuppressMessageRule(id, path); + } else { + // ignore it for now + } + + } +} diff --git a/org.hl7.fhir.validation/src/main/resources/help/advisor.txt b/org.hl7.fhir.validation/src/main/resources/help/advisor.txt index c86340d44..ac48436f5 100644 --- a/org.hl7.fhir.validation/src/main/resources/help/advisor.txt +++ b/org.hl7.fhir.validation/src/main/resources/help/advisor.txt @@ -34,7 +34,7 @@ Some notes on this approach: * the messageId is directly tied to the format of the message. For some messages, there are multiple variants of the message, each with their own id, and these all need to be suppressed * message ids are stable (including across languages), but sometimes an existing message is improved in some contexts, and new message ids are introduced in later versions * the underlying validation still happens, even when the messages are suppressed - +* for invariants in profiles, the message id is the profile URL and the invariant id e.g. http://hl7.org/fhir/StructureDefinition/DomainResource#dom-6 ## Controlling what validation runs diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java index 929b97395..cb155d739 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java @@ -920,4 +920,14 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe return null; } } + + @Override + public boolean suppressMessageId(String path, String messageId) { + return false; + } + + @Override + public ReferenceValidationPolicy getReferencePolicy() { + return ReferenceValidationPolicy.IGNORE; + } } \ No newline at end of file diff --git a/pom.xml b/pom.xml index 7679b0e5a..267b57fc7 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ 2.17.0 32.0.1-jre 6.4.1 - 1.5.26 + 1.5.27-SNAPSHOT 2.17.0 5.9.2 1.8.2 From 3b8b2a94c33b179379c410ab2d2d4c5e579466e2 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 14 Oct 2024 09:02:52 +0800 Subject: [PATCH 15/20] Fix validation issue with open-choice questions in R4 questionnaires --- .../conv40_50/Questionnaire40_50Test.java | 37 +++++++++++++ .../src/test/resources/q_open_40.json | 53 +++++++++++++++++++ .../fhir/utilities/i18n/I18nConstants.java | 1 + .../src/main/resources/Messages.properties | 1 + .../instance/type/QuestionnaireValidator.java | 35 ++++++------ 5 files changed, 109 insertions(+), 18 deletions(-) create mode 100644 org.hl7.fhir.convertors/src/test/java/org/hl7/fhir/convertors/conv40_50/Questionnaire40_50Test.java create mode 100644 org.hl7.fhir.convertors/src/test/resources/q_open_40.json diff --git a/org.hl7.fhir.convertors/src/test/java/org/hl7/fhir/convertors/conv40_50/Questionnaire40_50Test.java b/org.hl7.fhir.convertors/src/test/java/org/hl7/fhir/convertors/conv40_50/Questionnaire40_50Test.java new file mode 100644 index 000000000..229cb435b --- /dev/null +++ b/org.hl7.fhir.convertors/src/test/java/org/hl7/fhir/convertors/conv40_50/Questionnaire40_50Test.java @@ -0,0 +1,37 @@ +package org.hl7.fhir.convertors.conv40_50; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50; +import org.hl7.fhir.utilities.TextFile; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class Questionnaire40_50Test { + + @Test + @DisplayName("Test r5 -> r4 Questionnaire conversion.") + public void testR5_R4() throws IOException { + InputStream r4_input = this.getClass().getResourceAsStream("/q_open_40.json"); + String source = TextFile.streamToString(r4_input); + System.out.println(source); + + org.hl7.fhir.r4.model.Questionnaire r4_actual = (org.hl7.fhir.r4.model.Questionnaire) new org.hl7.fhir.r4.formats.JsonParser().parse(source); + org.hl7.fhir.r5.model.Resource r5_conv = VersionConvertorFactory_40_50.convertResource(r4_actual); + + org.hl7.fhir.r5.formats.JsonParser r5_parser = new org.hl7.fhir.r5.formats.JsonParser(); + + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + r5_parser.compose(stream, r5_conv); + + org.hl7.fhir.r5.model.Resource r5_streamed = (org.hl7.fhir.r5.model.Questionnaire) new org.hl7.fhir.r5.formats.JsonParser().parse(new ByteArrayInputStream(stream.toByteArray())); + org.hl7.fhir.r4.model.Resource r4_conv = VersionConvertorFactory_40_50.convertResource(r5_streamed); + + assertTrue(r4_actual.equalsDeep(r4_conv), "should be the same"); + } +} diff --git a/org.hl7.fhir.convertors/src/test/resources/q_open_40.json b/org.hl7.fhir.convertors/src/test/resources/q_open_40.json new file mode 100644 index 000000000..ee9556bf6 --- /dev/null +++ b/org.hl7.fhir.convertors/src/test/resources/q_open_40.json @@ -0,0 +1,53 @@ +{"resourceType": "Questionnaire", + "id": "ed364266b937bb3bd73082b1", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl", + "valueCodeableConcept": { + "coding": [ + { + "code": "editableDropdown" + } + ] + } + } + ], + "id": "specimen-source", + "answerOption": [ + { + "valueCoding": { + "code": "U", + "display": "Urine" + } + }, + { + "valueCoding": { + "code": "B", + "display": "Blood" + } + }, + { + "valueCoding": { + "code": "S", + "display": "Saliva" + } + } + ], + "code": [ + { + "code": "specimen-source" + } + ], + "linkId": "specimen-source", + "text": "Source of specimen", + "type": "open-choice" + } + ], + "name": "Test Open Choice question", + "status": "active", + "subjectType": [ + "Patient" + ] +} \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java index d9efb0e6e..55f05a570 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java @@ -514,6 +514,7 @@ public class I18nConstants { public static final String QUESTIONNAIRE_QR_ITEM_ONLYONEI = "Questionnaire_QR_Item_OnlyOneI"; public static final String QUESTIONNAIRE_QR_ITEM_ORDER = "Questionnaire_QR_Item_Order"; public static final String QUESTIONNAIRE_QR_ITEM_STRINGNOOPTIONS = "Questionnaire_QR_Item_StringNoOptions"; + public static final String QUESTIONNAIRE_QR_ITEM_STRING_IN_CODING = "QUESTIONNAIRE_QR_ITEM_STRING_IN_CODING"; public static final String QUESTIONNAIRE_QR_ITEM_TEXT = "Questionnaire_QR_Item_Text"; public static final String QUESTIONNAIRE_QR_ITEM_TIMENOOPTIONS = "Questionnaire_QR_Item_TimeNoOptions"; public static final String QUESTIONNAIRE_QR_ITEM_WRONGTYPE = "Questionnaire_QR_Item_WrongType"; diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index df16be4b8..298726e66 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -524,6 +524,7 @@ Questionnaire_QR_Item_NoOptionsCoding = Option list has no option values of type Questionnaire_QR_Item_NoOptionsDate = Option list has no option values of type date Questionnaire_QR_Item_NoOptionsInteger = Option list has no option values of type integer Questionnaire_QR_Item_NoOptionsString = Option list has no option values of type string +QUESTIONNAIRE_QR_ITEM_STRING_IN_CODING = The string value ''{0}'' matches an entry in the list of valid Codings, so this is probably an error Questionnaire_QR_Item_NoOptionsTime = Option list has no option values of type time Questionnaire_QR_Item_NoString = The string {0} is not a valid option Questionnaire_QR_Item_NoTime = The time {0} is not a valid option diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/QuestionnaireValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/QuestionnaireValidator.java index be2131a7a..14b71ecc6 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/QuestionnaireValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/QuestionnaireValidator.java @@ -16,6 +16,7 @@ import org.hl7.fhir.r5.model.Coding; import org.hl7.fhir.r5.model.DateType; import org.hl7.fhir.r5.model.IntegerType; import org.hl7.fhir.r5.model.Questionnaire; +import org.hl7.fhir.r5.model.Questionnaire.QuestionnaireAnswerConstraint; import org.hl7.fhir.r5.model.Questionnaire.QuestionnaireItemAnswerOptionComponent; import org.hl7.fhir.r5.model.Questionnaire.QuestionnaireItemComponent; import org.hl7.fhir.r5.model.Questionnaire.QuestionnaireItemType; @@ -812,7 +813,7 @@ public class QuestionnaireValidator extends BaseValidator { } private boolean checkOption(List errors, Element answer, NodeStack stack, QuestionnaireWithContext qSrc, QuestionnaireItemComponent qItem, String type) { - return checkOption(errors, answer, stack, qSrc, qItem, type, false); + return checkOption(errors, answer, stack, qSrc, qItem, type, qItem.getAnswerConstraint() == QuestionnaireAnswerConstraint.OPTIONSORSTRING); } private boolean checkOption(List errors, Element answer, NodeStack stack, QuestionnaireWithContext qSrc, QuestionnaireItemComponent qItem, String type, boolean openChoice) { @@ -928,31 +929,29 @@ public class QuestionnaireValidator extends BaseValidator { Element v = answer.getNamedChild("valueString", false); NodeStack ns = stack.push(v, -1, null, null); if (qItem.getAnswerOption().size() > 0) { - List list = new ArrayList(); + boolean found = false; + boolean empty = true; for (QuestionnaireItemAnswerOptionComponent components : qItem.getAnswerOption()) { - try { - if (components.getValue() != null) { - list.add(components.getValueStringType()); - } - } catch (FHIRException e) { - // If it's the wrong type, just keep going + if (components.getValue() != null && components.hasValueStringType()) { + empty = false; + found = found || components.getValue().primitiveValue().equals((v.primitiveValue())); } } if (!openChoice) { - if (list.isEmpty()) { + if (empty) { ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_NOOPTIONSSTRING) && ok; } else { - boolean found = false; - for (StringType item : list) { - if (item.getValue().equals((v.primitiveValue()))) { - found = true; - break; - } - } - if (!found) { - ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, I18nConstants.QUESTIONNAIRE_QR_ITEM_NOSTRING, v.primitiveValue()) && ok; + ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, I18nConstants.QUESTIONNAIRE_QR_ITEM_NOSTRING, v.primitiveValue()) && ok; + } + } else { + found = false; + for (QuestionnaireItemAnswerOptionComponent components : qItem.getAnswerOption()) { + if (components.getValue() != null && components.hasValueCoding()) { + Coding c = components.getValueCoding(); + found = found || (c.hasDisplay() && c.getDisplay().equalsIgnoreCase(v.primitiveValue())) || (c.hasCode() && c.getCode().equalsIgnoreCase(v.primitiveValue())); } } + ok = warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), !found, I18nConstants.QUESTIONNAIRE_QR_ITEM_STRING_IN_CODING, v.primitiveValue()) && ok; } } else { hint(errors, NO_RULE_DATE, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_STRINGNOOPTIONS); From f19f8f4ed26f9fc368a07495495027754337fb7b Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 14 Oct 2024 12:31:51 +0800 Subject: [PATCH 16/20] rework rules based advisor --- .../validation/IValidationPolicyAdvisor.java | 2 +- .../hl7/fhir/validation/BaseValidator.java | 2 +- .../hl7/fhir/validation/ValidationEngine.java | 4 +- .../services/StandAloneValidatorFetcher.java | 4 +- .../BasePolicyAdvisorForFullValidation.java | 6 +- .../advisor/JsonDrivenPolicyAdvisor.java | 32 +++--- .../advisor/RulesDrivenPolicyAdvisor.java | 102 +++++++++++++----- .../advisor/TextDrivenPolicyAdvisor.java | 19 ++-- .../validation/tests/ValidationTests.java | 2 +- 9 files changed, 112 insertions(+), 61 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/IValidationPolicyAdvisor.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/IValidationPolicyAdvisor.java index c87508dcd..c55a533bd 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/IValidationPolicyAdvisor.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/IValidationPolicyAdvisor.java @@ -29,7 +29,7 @@ public interface IValidationPolicyAdvisor { * @param messageId - the message id (from messages.properties) * @return true if the validator should ignore the message */ - boolean suppressMessageId(String path, String messageId); + boolean isSuppressMessageId(String path, String messageId); /** * diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java index cb6f23fdd..d3cd56aab 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java @@ -278,7 +278,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi if (policyAdvisor == null) { return false; } else { - return policyAdvisor.suppressMessageId(path, theMessage); + return policyAdvisor.isSuppressMessageId(path, theMessage); } } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java index 545670b98..863e02715 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java @@ -1289,8 +1289,8 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP } @Override - public boolean suppressMessageId(String path, String messageId) { - return policyAdvisor.suppressMessageId(path, messageId); + public boolean isSuppressMessageId(String path, String messageId) { + return policyAdvisor.isSuppressMessageId(path, messageId); } @Override diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/StandAloneValidatorFetcher.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/StandAloneValidatorFetcher.java index 20beab9a8..cdf7b1381 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/StandAloneValidatorFetcher.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/StandAloneValidatorFetcher.java @@ -302,8 +302,8 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher, IV } @Override - public boolean suppressMessageId(String path, String messageId) { - return policyAdvisor.suppressMessageId(path, messageId); + public boolean isSuppressMessageId(String path, String messageId) { + return policyAdvisor.isSuppressMessageId(path, messageId); } @Override diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/BasePolicyAdvisorForFullValidation.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/BasePolicyAdvisorForFullValidation.java index 79b63eecb..434612dd7 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/BasePolicyAdvisorForFullValidation.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/BasePolicyAdvisorForFullValidation.java @@ -13,10 +13,6 @@ import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.utils.validation.IMessagingServices; import org.hl7.fhir.r5.utils.validation.IResourceValidator; import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; -import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.AdditionalBindingPurpose; -import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.CodedContentValidationAction; -import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.ElementValidationAction; -import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.ResourceValidationAction; import org.hl7.fhir.r5.utils.validation.constants.BindingKind; import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy; import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; @@ -170,7 +166,7 @@ public class BasePolicyAdvisorForFullValidation implements IValidationPolicyAdvi } @Override - public boolean suppressMessageId(String path, String messageId) { + public boolean isSuppressMessageId(String path, String messageId) { return false; } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/JsonDrivenPolicyAdvisor.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/JsonDrivenPolicyAdvisor.java index 8121f9d62..0118d7674 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/JsonDrivenPolicyAdvisor.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/JsonDrivenPolicyAdvisor.java @@ -1,27 +1,16 @@ package org.hl7.fhir.validation.instance.advisor; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.util.EnumSet; -import java.util.List; -import org.hl7.fhir.r5.elementmodel.Element; -import org.hl7.fhir.r5.elementmodel.Element.SpecialElement; -import org.hl7.fhir.r5.model.ElementDefinition; -import org.hl7.fhir.r5.model.StructureDefinition; -import org.hl7.fhir.r5.model.ValueSet; -import org.hl7.fhir.r5.utils.validation.IMessagingServices; -import org.hl7.fhir.r5.utils.validation.IResourceValidator; +import javax.annotation.Nonnull; + import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; -import org.hl7.fhir.r5.utils.validation.constants.BindingKind; -import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy; import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; import org.hl7.fhir.utilities.json.JsonException; import org.hl7.fhir.utilities.json.model.JsonElement; import org.hl7.fhir.utilities.json.model.JsonObject; import org.hl7.fhir.utilities.json.parser.JsonParser; -import org.hl7.fhir.utilities.validation.ValidationMessage; public class JsonDrivenPolicyAdvisor extends RulesDrivenPolicyAdvisor { @@ -38,14 +27,19 @@ public class JsonDrivenPolicyAdvisor extends RulesDrivenPolicyAdvisor { private void load(File source) throws JsonException, IOException { JsonObject json = JsonParser.parseObject(source); for (JsonElement e : json.forceArray("suppress").getItems()) { - String s = e.asString(); - String id = s; - String path = null; + @Nonnull String s = e.asString(); if (s.contains("@")) { - id = s.substring(0, s.indexOf("@")); - path = s.substring(s.indexOf("@")+1); + String id = s.substring(0, s.indexOf("@")); + String path = s.substring(s.indexOf("@")+1); + boolean regex = false; + if (path.startsWith("^")) { + regex = true; + path = path.substring(1); + } + addSuppressMessageRule(id, path, regex); + } else { + addSuppressMessageRule(s); } - addSuppressMessageRule(id, path); } } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/RulesDrivenPolicyAdvisor.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/RulesDrivenPolicyAdvisor.java index fe5a9f4ad..942081e93 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/RulesDrivenPolicyAdvisor.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/RulesDrivenPolicyAdvisor.java @@ -4,6 +4,8 @@ import java.util.ArrayList; import java.util.EnumSet; import java.util.List; +import javax.annotation.Nonnull; + import org.hl7.fhir.r5.elementmodel.Element; import org.hl7.fhir.r5.model.ElementDefinition; import org.hl7.fhir.r5.model.StructureDefinition; @@ -11,12 +13,7 @@ import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.utils.validation.IMessagingServices; import org.hl7.fhir.r5.utils.validation.IResourceValidator; import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; -import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.AdditionalBindingPurpose; -import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.CodedContentValidationAction; -import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.ElementValidationAction; -import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.ResourceValidationAction; import org.hl7.fhir.r5.utils.validation.constants.BindingKind; -import org.hl7.fhir.r5.utils.validation.constants.CodedContentValidationPolicy; import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy; import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; import org.hl7.fhir.utilities.validation.ValidationMessage; @@ -38,48 +35,105 @@ public class RulesDrivenPolicyAdvisor extends BasePolicyAdvisorForFullValidation private class SuppressMessageRule { private String id; private String path; - protected SuppressMessageRule(String id, String path) { + private String[] pathSegments; + private boolean regex; + + protected SuppressMessageRule(@Nonnull String id, String path, boolean regex) { super(); this.id = id; this.path = path; + this.pathSegments = path.split("\\."); + this.regex = regex; } - public String getId() { - return id; + + protected SuppressMessageRule(@Nonnull String id) { + super(); + this.id = id; } - public String getPath() { - return path; - } - public boolean matches(String mid, String p) { - if (((id == null) || id.equals(mid)) && ((path == null) || path.equals(p))) { - suppressed++; - return true; - } else if (((id == null) || mid.matches(id)) && ((path == null) || p.matches(path))) { - suppressed++; - return true; + + public boolean matches(@Nonnull String mid, @Nonnull String path, String[] p) { + if (regex) { + return stringMatches(id, mid) && regexMatches(path, path); } else { + return stringMatches(id, mid) && pathMatches(pathSegments, p); + } + } + } + + // string matching + + boolean pathMatches(String[] specifier, String[] actual) { + if (specifier == null) { + return true; + } + for (int i = 0; i < specifier.length; i++) { + if (i == actual.length) { + return false; + } else if (!pathSegmentMatches(specifier[i], actual[i])) { return false; } } + if (actual.length > specifier.length) { + return specifier[specifier.length-1].equals("*"); + } else { + return true; + } + } + + boolean pathSegmentMatches(String specifier, String actual) { + if ("*".equals(specifier)) { + return true; + } else if (!specifier.contains("[")) { + if (actual.contains("[")) { + actual = actual.substring(0, actual.indexOf("[")); + } + return specifier.equals(actual); + } else { + return specifier.equals(actual); + } + } + + boolean stringMatches(String specifier, @Nonnull String actual) { + if (specifier == null) { + return true; + } else if (specifier.endsWith("*")) { + return specifier.substring(0, specifier.length()-1).equalsIgnoreCase(actual.substring(0, specifier.length()-1)); + } else { + return specifier.equalsIgnoreCase(actual); + } + } + + boolean regexMatches(String specifier, @Nonnull String actual) { + if (specifier == null) { + return true; + } else { + return actual.matches(specifier); + } } private List suppressMessageRules = new ArrayList<>(); private int suppressed = 0; - protected void addSuppressMessageRule(String id, String path) { - suppressMessageRules.add(new SuppressMessageRule(id, path)); + protected void addSuppressMessageRule(@Nonnull String id, String path, boolean regex) { + suppressMessageRules.add(new SuppressMessageRule(id, path, regex)); + } + + protected void addSuppressMessageRule(@Nonnull String id) { + suppressMessageRules.add(new SuppressMessageRule(id)); } @Override - public boolean suppressMessageId(String path, String messageId) { + public boolean isSuppressMessageId(String path, String messageId) { + String[] p = path.split("\\."); for (SuppressMessageRule rule : suppressMessageRules) { - if (rule.matches(messageId, path)) { + if (rule.matches(messageId, path, p)) { return true; } } if (base != null) { - return base.suppressMessageId(path, messageId); + return base.isSuppressMessageId(path, messageId); } else { - return super.suppressMessageId(path, messageId); + return super.isSuppressMessageId(path, messageId); } } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/TextDrivenPolicyAdvisor.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/TextDrivenPolicyAdvisor.java index 2920d51ae..c078c2eb0 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/TextDrivenPolicyAdvisor.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/TextDrivenPolicyAdvisor.java @@ -5,6 +5,8 @@ import java.io.File; import java.io.FileReader; import java.io.IOException; +import javax.annotation.Nonnull; + import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; import org.hl7.fhir.utilities.Utilities; @@ -38,14 +40,19 @@ public class TextDrivenPolicyAdvisor extends RulesDrivenPolicyAdvisor { return; } if (line.startsWith("-")) { - String s = line.substring(1).trim(); - String id = s; - String path = null; + @Nonnull String s = line.substring(1).trim(); if (s.contains("@")) { - id = s.substring(0, s.indexOf("@")); - path = s.substring(s.indexOf("@")+1); + String id = s.substring(0, s.indexOf("@")); + String path = s.substring(s.indexOf("@")+1); + boolean regex = false; + if (path.startsWith("^")) { + regex = true; + path = path.substring(1); + } + addSuppressMessageRule(id, path, regex); + } else { + addSuppressMessageRule(s); } - addSuppressMessageRule(id, path); } else { // ignore it for now } diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java index cb155d739..e4b712a70 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java @@ -922,7 +922,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe } @Override - public boolean suppressMessageId(String path, String messageId) { + public boolean isSuppressMessageId(String path, String messageId) { return false; } From 85f58655d1c0dc8a41c763168b81e18420fe53d0 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 14 Oct 2024 12:49:05 +0800 Subject: [PATCH 17/20] set up release --- RELEASE_NOTES.md | 9 +++++++-- pom.xml | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 7b06c6ab5..60b4ae546 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,7 +1,12 @@ ## Validator Changes -* no changes +* Fix validation issue with open-choice questions in R4 questionnaires +* Add command line parameter ```-tx-routing``` +* Add command line parameter ```-clear-tx-cache``` +* Add command line parameter ```-advisor-file``` + ## Other code changes -* no changes \ No newline at end of file +* Render extensions on some data types +* Fix rendering of complex data types when doing profile rendering diff --git a/pom.xml b/pom.xml index 267b57fc7..3cee84a43 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ 2.17.0 32.0.1-jre 6.4.1 - 1.5.27-SNAPSHOT + 1.5.27 2.17.0 5.9.2 1.8.2 From 855a0c6a38ce499af380f077931f23b5032599fd Mon Sep 17 00:00:00 2001 From: markiantorno Date: Mon, 14 Oct 2024 05:08:46 +0000 Subject: [PATCH 18/20] Updating i18n-coverage csv and png table ***NO_CI*** --- i18n-coverage-table.png | Bin 30979 -> 31253 bytes i18n-coverage.csv | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n-coverage-table.png b/i18n-coverage-table.png index 89af7e5361422d46646f947ee61545bb46b50a48..3316b4268685b4774b94873047089ba413f6e524 100644 GIT binary patch literal 31253 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 Iqs__x1u`FuLjV8( literal 30979 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 diff --git a/i18n-coverage.csv b/i18n-coverage.csv index c0417100f..c1dce3bbd 100644 --- a/i18n-coverage.csv +++ b/i18n-coverage.csv @@ -2,4 +2,4 @@ Locale,Coverage #,Coverage % de,831,40% es,714,34% ja,902,44% -nl,1988,97% +nl,1988,96% From 54bf319161d4e31b2914623b7f188bdb10abe21e Mon Sep 17 00:00:00 2001 From: markiantorno Date: Mon, 14 Oct 2024 05:18:21 +0000 Subject: [PATCH 19/20] Release: v6.3.32 ## Validator Changes * Fix validation issue with open-choice questions in R4 questionnaires * Add command line parameter ```-tx-routing``` * Add command line parameter ```-clear-tx-cache``` * Add command line parameter ```-advisor-file``` ## Other code changes * Render extensions on some data types * Fix rendering of complex data types when doing profile rendering ***NO_CI*** --- org.hl7.fhir.convertors/pom.xml | 2 +- org.hl7.fhir.dstu2/pom.xml | 2 +- org.hl7.fhir.dstu2016may/pom.xml | 2 +- org.hl7.fhir.dstu3/pom.xml | 2 +- org.hl7.fhir.r4/pom.xml | 2 +- org.hl7.fhir.r4b/pom.xml | 2 +- org.hl7.fhir.r5/pom.xml | 2 +- org.hl7.fhir.report/pom.xml | 2 +- org.hl7.fhir.utilities/pom.xml | 2 +- org.hl7.fhir.validation.cli/pom.xml | 2 +- org.hl7.fhir.validation/pom.xml | 2 +- pom.xml | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/org.hl7.fhir.convertors/pom.xml b/org.hl7.fhir.convertors/pom.xml index 33376a626..68cbb9b67 100644 --- a/org.hl7.fhir.convertors/pom.xml +++ b/org.hl7.fhir.convertors/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.32-SNAPSHOT + 6.3.32 ../pom.xml diff --git a/org.hl7.fhir.dstu2/pom.xml b/org.hl7.fhir.dstu2/pom.xml index 71f09aca7..e59495400 100644 --- a/org.hl7.fhir.dstu2/pom.xml +++ b/org.hl7.fhir.dstu2/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.32-SNAPSHOT + 6.3.32 ../pom.xml diff --git a/org.hl7.fhir.dstu2016may/pom.xml b/org.hl7.fhir.dstu2016may/pom.xml index 7df21feed..eb85e48f2 100644 --- a/org.hl7.fhir.dstu2016may/pom.xml +++ b/org.hl7.fhir.dstu2016may/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.32-SNAPSHOT + 6.3.32 ../pom.xml diff --git a/org.hl7.fhir.dstu3/pom.xml b/org.hl7.fhir.dstu3/pom.xml index 8215581c9..95601d613 100644 --- a/org.hl7.fhir.dstu3/pom.xml +++ b/org.hl7.fhir.dstu3/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.32-SNAPSHOT + 6.3.32 ../pom.xml diff --git a/org.hl7.fhir.r4/pom.xml b/org.hl7.fhir.r4/pom.xml index 3972a04ab..e568066cc 100644 --- a/org.hl7.fhir.r4/pom.xml +++ b/org.hl7.fhir.r4/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.32-SNAPSHOT + 6.3.32 ../pom.xml diff --git a/org.hl7.fhir.r4b/pom.xml b/org.hl7.fhir.r4b/pom.xml index da3cef753..45e710791 100644 --- a/org.hl7.fhir.r4b/pom.xml +++ b/org.hl7.fhir.r4b/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.32-SNAPSHOT + 6.3.32 ../pom.xml diff --git a/org.hl7.fhir.r5/pom.xml b/org.hl7.fhir.r5/pom.xml index 5927b5768..2e8cb27bb 100644 --- a/org.hl7.fhir.r5/pom.xml +++ b/org.hl7.fhir.r5/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.32-SNAPSHOT + 6.3.32 ../pom.xml diff --git a/org.hl7.fhir.report/pom.xml b/org.hl7.fhir.report/pom.xml index 6da6e25ff..f9bef5b8e 100644 --- a/org.hl7.fhir.report/pom.xml +++ b/org.hl7.fhir.report/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.32-SNAPSHOT + 6.3.32 ../pom.xml diff --git a/org.hl7.fhir.utilities/pom.xml b/org.hl7.fhir.utilities/pom.xml index 116ba8175..1f24d6fd9 100644 --- a/org.hl7.fhir.utilities/pom.xml +++ b/org.hl7.fhir.utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.32-SNAPSHOT + 6.3.32 ../pom.xml diff --git a/org.hl7.fhir.validation.cli/pom.xml b/org.hl7.fhir.validation.cli/pom.xml index 2bed28b35..9f5a3946e 100644 --- a/org.hl7.fhir.validation.cli/pom.xml +++ b/org.hl7.fhir.validation.cli/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.32-SNAPSHOT + 6.3.32 ../pom.xml diff --git a/org.hl7.fhir.validation/pom.xml b/org.hl7.fhir.validation/pom.xml index af3059a81..4302987a8 100644 --- a/org.hl7.fhir.validation/pom.xml +++ b/org.hl7.fhir.validation/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.32-SNAPSHOT + 6.3.32 ../pom.xml diff --git a/pom.xml b/pom.xml index 3cee84a43..621a26e8d 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ HAPI FHIR --> org.hl7.fhir.core - 6.3.32-SNAPSHOT + 6.3.32 pom From cabee9a34115d9785be44c46abf94d874bd8df3e Mon Sep 17 00:00:00 2001 From: markiantorno Date: Mon, 14 Oct 2024 06:03:42 +0000 Subject: [PATCH 20/20] Updating version to: 6.3.33-SNAPSHOT and incrementing test cases dependency. --- RELEASE_NOTES.md | 9 ++------- org.hl7.fhir.convertors/pom.xml | 2 +- org.hl7.fhir.dstu2/pom.xml | 2 +- org.hl7.fhir.dstu2016may/pom.xml | 2 +- org.hl7.fhir.dstu3/pom.xml | 2 +- org.hl7.fhir.r4/pom.xml | 2 +- org.hl7.fhir.r4b/pom.xml | 2 +- org.hl7.fhir.r5/pom.xml | 2 +- org.hl7.fhir.report/pom.xml | 2 +- org.hl7.fhir.utilities/pom.xml | 2 +- org.hl7.fhir.validation.cli/pom.xml | 2 +- org.hl7.fhir.validation/pom.xml | 2 +- pom.xml | 2 +- 13 files changed, 14 insertions(+), 19 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 60b4ae546..7b06c6ab5 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,12 +1,7 @@ ## Validator Changes -* Fix validation issue with open-choice questions in R4 questionnaires -* Add command line parameter ```-tx-routing``` -* Add command line parameter ```-clear-tx-cache``` -* Add command line parameter ```-advisor-file``` - +* no changes ## Other code changes -* Render extensions on some data types -* Fix rendering of complex data types when doing profile rendering +* no changes \ No newline at end of file diff --git a/org.hl7.fhir.convertors/pom.xml b/org.hl7.fhir.convertors/pom.xml index 68cbb9b67..18f9c2329 100644 --- a/org.hl7.fhir.convertors/pom.xml +++ b/org.hl7.fhir.convertors/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.32 + 6.3.33-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.dstu2/pom.xml b/org.hl7.fhir.dstu2/pom.xml index e59495400..674c68fec 100644 --- a/org.hl7.fhir.dstu2/pom.xml +++ b/org.hl7.fhir.dstu2/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.32 + 6.3.33-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.dstu2016may/pom.xml b/org.hl7.fhir.dstu2016may/pom.xml index eb85e48f2..24f4ba34c 100644 --- a/org.hl7.fhir.dstu2016may/pom.xml +++ b/org.hl7.fhir.dstu2016may/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.32 + 6.3.33-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.dstu3/pom.xml b/org.hl7.fhir.dstu3/pom.xml index 95601d613..495182651 100644 --- a/org.hl7.fhir.dstu3/pom.xml +++ b/org.hl7.fhir.dstu3/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.32 + 6.3.33-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.r4/pom.xml b/org.hl7.fhir.r4/pom.xml index e568066cc..c433c70bc 100644 --- a/org.hl7.fhir.r4/pom.xml +++ b/org.hl7.fhir.r4/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.32 + 6.3.33-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.r4b/pom.xml b/org.hl7.fhir.r4b/pom.xml index 45e710791..9f49fa24a 100644 --- a/org.hl7.fhir.r4b/pom.xml +++ b/org.hl7.fhir.r4b/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.32 + 6.3.33-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.r5/pom.xml b/org.hl7.fhir.r5/pom.xml index 2e8cb27bb..e0927d747 100644 --- a/org.hl7.fhir.r5/pom.xml +++ b/org.hl7.fhir.r5/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.32 + 6.3.33-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.report/pom.xml b/org.hl7.fhir.report/pom.xml index f9bef5b8e..fca5c8886 100644 --- a/org.hl7.fhir.report/pom.xml +++ b/org.hl7.fhir.report/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.32 + 6.3.33-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.utilities/pom.xml b/org.hl7.fhir.utilities/pom.xml index 1f24d6fd9..cd8fcfcf6 100644 --- a/org.hl7.fhir.utilities/pom.xml +++ b/org.hl7.fhir.utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.32 + 6.3.33-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.validation.cli/pom.xml b/org.hl7.fhir.validation.cli/pom.xml index 9f5a3946e..081ac9e56 100644 --- a/org.hl7.fhir.validation.cli/pom.xml +++ b/org.hl7.fhir.validation.cli/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.32 + 6.3.33-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.validation/pom.xml b/org.hl7.fhir.validation/pom.xml index 4302987a8..8f36b1459 100644 --- a/org.hl7.fhir.validation/pom.xml +++ b/org.hl7.fhir.validation/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.3.32 + 6.3.33-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index 621a26e8d..2ad4f8c0b 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ HAPI FHIR --> org.hl7.fhir.core - 6.3.32 + 6.3.33-SNAPSHOT pom