diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 7b06c6ab5..b13dfbf98 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -4,4 +4,5 @@ ## Other code changes -* no changes \ No newline at end of file +* More comprehensive internationalization phrase coverage reporting. +* Shim interfaces and classes to support clinical reasoning project updates. diff --git a/i18n-coverage-table.png b/i18n-coverage-table.png index d735b184c..7b00d33d6 100644 Binary files a/i18n-coverage-table.png and b/i18n-coverage-table.png differ diff --git a/i18n-coverage.csv b/i18n-coverage.csv index efd03c179..7801f8c0d 100644 --- a/i18n-coverage.csv +++ b/i18n-coverage.csv @@ -1,5 +1,5 @@ Locale,Coverage #,Coverage % de,869,43% -es,740,37% +es,740,36% ja,935,46% nl,873,43% 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 a192597a9..719732142 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 @@ -277,7 +277,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte protected boolean canRunWithoutTerminology; protected boolean noTerminologyServer; private int expandCodesLimit = 1000; - protected ILoggingService logger = new SystemOutLoggingService(); + protected org.hl7.fhir.r5.context.ILoggingService logger = new SystemOutLoggingService(); protected Parameters expParameters; private Map packages = new HashMap<>(); @@ -1991,7 +1991,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte this.canRunWithoutTerminology = canRunWithoutTerminology; } - public void setLogger(@Nonnull ILoggingService logger) { + public void setLogger(@Nonnull org.hl7.fhir.r5.context.ILoggingService logger) { this.logger = logger; } @@ -2816,7 +2816,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte } @Override - public ILoggingService getLogger() { + public org.hl7.fhir.r5.context.ILoggingService getLogger() { return logger; } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java index 3a58fd8ba..256868682 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java @@ -109,6 +109,18 @@ import javax.annotation.Nonnull; public interface IWorkerContext { + /** + @deprecated This interface only exists to provide backward compatibility for the following two projects: + clinical-reasoning + clinical_quality-language + + Due to a circular dependency, they cannot be updated without a release of HAPI, which requires backwards + compatibility with core version 6.1.2.2 + **/ + @Deprecated(forRemoval = true) + public interface ILoggingService extends org.hl7.fhir.r5.context.ILoggingService{ + + } public class OIDDefinitionComparer implements Comparator { @Override @@ -630,8 +642,8 @@ public interface IWorkerContext { // todo: figure these out public Map getNSUrlMap(); - public void setLogger(@Nonnull ILoggingService logger); - public ILoggingService getLogger(); + public void setLogger(@Nonnull org.hl7.fhir.r5.context.ILoggingService logger); + public org.hl7.fhir.r5.context.ILoggingService getLogger(); public boolean isNoTerminologyServer(); public Set getCodeSystemsUsed(); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/SimpleWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/SimpleWorkerContext.java index 66581d063..0b09ee369 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/SimpleWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/SimpleWorkerContext.java @@ -214,7 +214,7 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon private final boolean allowLoadingDuplicates; @With - private final ILoggingService loggingService; + private final org.hl7.fhir.r5.context.ILoggingService loggingService; public SimpleWorkerContextBuilder() { cacheTerminologyClientErrors = false; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ExpressionNode.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ExpressionNode.java new file mode 100644 index 000000000..b37397775 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ExpressionNode.java @@ -0,0 +1,228 @@ +package org.hl7.fhir.r5.model; + +import org.hl7.fhir.r5.fhirpath.TypeDetails; +import org.hl7.fhir.utilities.SourceLocation; + +import java.util.List; + +/** + @deprecated This interface only exists to provide backward compatibility for the following two projects: + clinical-reasoning + clinical_quality-language + + Due to a circular dependency, they cannot be updated without a release of HAPI, which requires backwards + compatibility with core version 6.1.2.2 + **/ +public class ExpressionNode extends org.hl7.fhir.r5.fhirpath.ExpressionNode{ + + private final org.hl7.fhir.r5.fhirpath.ExpressionNode wrappedExpressionNode; + public ExpressionNode(int uniqueId) { + super(0); + wrappedExpressionNode = new org.hl7.fhir.r5.fhirpath.ExpressionNode(uniqueId); + } + + public ExpressionNode(org.hl7.fhir.r5.fhirpath.ExpressionNode wrappedExpressionNode) { + super(0); + this.wrappedExpressionNode = wrappedExpressionNode; + } + + @Override + public String toString() { + return wrappedExpressionNode.toString(); + } + + @Override + public String getName() { + return wrappedExpressionNode.getName(); + } + + @Override + public void setName(String name) { + wrappedExpressionNode.setName(name); + } + + @Override + public Base getConstant() { + return wrappedExpressionNode.getConstant(); + } + + @Override + public void setConstant(Base constant) { + wrappedExpressionNode.setConstant(constant); + } + + @Override + public Function getFunction() { + return wrappedExpressionNode.getFunction(); + } + + @Override + public void setFunction(Function function) { + wrappedExpressionNode.setFunction(function); + } + + @Override + public boolean isProximal() { + return wrappedExpressionNode.isProximal(); + } + + @Override + public void setProximal(boolean proximal) { + wrappedExpressionNode.setProximal(proximal); + } + + @Override + public Operation getOperation() { + return wrappedExpressionNode.getOperation(); + } + + @Override + public void setOperation(Operation operation) { + wrappedExpressionNode.setOperation(operation); + } + + @Override + public org.hl7.fhir.r5.fhirpath.ExpressionNode getInner() { + return wrappedExpressionNode.getInner(); + } + + @Override + public void setInner(org.hl7.fhir.r5.fhirpath.ExpressionNode value) { + wrappedExpressionNode.setInner(value); + } + + @Override + public org.hl7.fhir.r5.fhirpath.ExpressionNode getOpNext() { + return wrappedExpressionNode.getOpNext(); + } + + @Override + public void setOpNext(org.hl7.fhir.r5.fhirpath.ExpressionNode value) { + wrappedExpressionNode.setOpNext(value); + } + + @Override + public List getParameters() { + return wrappedExpressionNode.getParameters(); + } + + @Override + public boolean checkName() { + return wrappedExpressionNode.checkName(); + } + + @Override + public Kind getKind() { + return wrappedExpressionNode.getKind(); + } + + @Override + public void setKind(Kind kind) { + wrappedExpressionNode.setKind(kind); + } + + @Override + public org.hl7.fhir.r5.fhirpath.ExpressionNode getGroup() { + return wrappedExpressionNode.getGroup(); + } + + @Override + public void setGroup(org.hl7.fhir.r5.fhirpath.ExpressionNode group) { + wrappedExpressionNode.setGroup(group); + } + + @Override + public SourceLocation getStart() { + return wrappedExpressionNode.getStart(); + } + + @Override + public void setStart(SourceLocation start) { + wrappedExpressionNode.setStart(start); + } + + @Override + public SourceLocation getEnd() { + return wrappedExpressionNode.getEnd(); + } + + @Override + public void setEnd(SourceLocation end) { + wrappedExpressionNode.setEnd(end); + } + + @Override + public SourceLocation getOpStart() { + return wrappedExpressionNode.getOpStart(); + } + + @Override + public void setOpStart(SourceLocation opStart) { + wrappedExpressionNode.setOpStart(opStart); + } + + @Override + public SourceLocation getOpEnd() { + return wrappedExpressionNode.getOpEnd(); + } + + @Override + public void setOpEnd(SourceLocation opEnd) { + wrappedExpressionNode.setOpEnd(opEnd); + } + + @Override + public String getUniqueId() { + return wrappedExpressionNode.getUniqueId(); + } + + @Override + public int parameterCount() { + return wrappedExpressionNode.parameterCount(); + } + + @Override + public String Canonical() { + return wrappedExpressionNode.Canonical(); + } + + @Override + public String summary() { + return wrappedExpressionNode.summary(); + } + + @Override + public String check() { + return wrappedExpressionNode.check(); + } + + @Override + public TypeDetails getTypes() { + return wrappedExpressionNode.getTypes(); + } + + @Override + public void setTypes(TypeDetails types) { + wrappedExpressionNode.setTypes(types); + } + + @Override + public TypeDetails getOpTypes() { + return wrappedExpressionNode.getOpTypes(); + } + + @Override + public void setOpTypes(TypeDetails opTypes) { + wrappedExpressionNode.setOpTypes(opTypes); + } + + @Override + public List getDistalNames() { + return wrappedExpressionNode.getDistalNames(); + } + + @Override + public boolean isNullSet() { + return wrappedExpressionNode.isNullSet(); + } +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java new file mode 100644 index 000000000..3d6633604 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java @@ -0,0 +1,30 @@ +package org.hl7.fhir.r5.utils; + +import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.model.Base; + +import java.util.List; + +/** + @deprecated This interface only exists to provide backward compatibility for the following two projects: + clinical-reasoning + clinical_quality-language + + Due to a circular dependency, they cannot be updated without a release of HAPI, which requires backwards + compatibility with core version 6.1.2.2 + **/ +public class FHIRPathEngine extends org.hl7.fhir.r5.fhirpath.FHIRPathEngine { + + public interface IEvaluationContext extends org.hl7.fhir.r5.fhirpath.FHIRPathEngine.IEvaluationContext{ } + public FHIRPathEngine(IWorkerContext worker) { + super(worker); + } + + public org.hl7.fhir.r5.model.ExpressionNode parse(String string) { + return new org.hl7.fhir.r5.model.ExpressionNode(super.parse(string)); + } + + public List evaluate(Base base, org.hl7.fhir.r5.model.ExpressionNode expressionNode) { + return super.evaluate(base, expressionNode); + } +} \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/CanonicalPair.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/CanonicalPair.java index 6fe972e01..221b90e55 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/CanonicalPair.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/CanonicalPair.java @@ -1,26 +1,82 @@ package org.hl7.fhir.utilities; +import org.apache.commons.lang3.StringUtils; + +/** + * Abstraction that splits a canonical in form of {@code |} into a URL and a version part. + */ public class CanonicalPair { - private String url; - private String version; + private final String url; + private final String version; + /** + * Static factory method, that invokes the {@link CanonicalPair#CanonicalPair(String) CanonicalPair constructor} + * with the given argument. + * @param target the canonical to be split. May be {@code null}. + * @return new instance of CanonicalPair + */ + public static CanonicalPair of(String target) { + return new CanonicalPair(target); + } + + /** + * Wraps the given canonical and if needed splits off the version part.

+ * The given parameter {@code target} is expected to be a canonical. + * @param target canonical to create a url, version pair from. May be {@code null}. + */ public CanonicalPair(String target) { - if (target != null && target.contains("|")) { - this.url = target.substring(0, target.indexOf("|")); - this.version = target.substring(target.indexOf("|")+1); + int pipeIndex = target != null ? target.indexOf('|') : -1; + if (pipeIndex >= 0) { + this.url = target.substring(0, pipeIndex); + this.version = target.substring(pipeIndex+1); } else { this.url = target; this.version = null; } } + /** + * Returns the URL part of the canonical (everything before the {@code "|"} character, or the complete + * canonical if the character is not present). If the source + * @return URL part of the source canonical. May be {@code null}, if source canonical was {@code null} + */ public String getUrl() { return url; } + /** + * Returns the version part of the source canonical (everything after the {@code "|"} character. + * @return version part of the canonical, may be {@code null}, if canonical was {@code null}, or canonical contains no + * {@code "|"} character. + */ public String getVersion() { return version; } + /** + * Determines if the version part of the canonical is not {@code null} + * @return {@code true} if version is not {@code null}, otherwise {@code false} + */ + public boolean hasVersion() { + return version != null; + } + + /** + * Returns the version of this pair, or the parameter {@code alternative}, + * if the version of this pair is {@code null}. + * @param alternative to be returned from this method if the encapsulated version is {@code null}. + * @return either the held version, or {@code alternative}, if version is {@code null} + */ + public String getVersionOr(String alternative) { + return hasVersion() ? version : alternative; + } + + /** + * Determines if the encapsulated version of this pair is not {@code null} and not an empty string. + * @return {@code true} if the version of this pair is not {@code null} and not an empty string, {@code false} otherwise + */ + public boolean hasNonEmptyVersion() { + return StringUtils.isNotEmpty(version); + } } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/TerminologyServiceOptions.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/TerminologyServiceOptions.java index 2dd989c9a..7666bbed5 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/TerminologyServiceOptions.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/TerminologyServiceOptions.java @@ -1,7 +1,7 @@ -package org.hl7.fhir.utilities; - -import org.hl7.fhir.utilities.validation.ValidationOptions; - +package org.hl7.fhir.utilities; + +import org.hl7.fhir.utilities.validation.ValidationOptions; + /* Copyright (c) 2011+, HL7, Inc. All rights reserved. @@ -30,21 +30,23 @@ import org.hl7.fhir.utilities.validation.ValidationOptions; POSSIBILITY OF SUCH DAMAGE. */ - - -/** - * This class is superceded by TerminologyValidationOptions but retained here for backwards compatibility - * @author graha - * - */ -public class TerminologyServiceOptions extends ValidationOptions { - - public TerminologyServiceOptions(FhirPublication fhirVersion) { - super(fhirVersion); - } - - public TerminologyServiceOptions(FhirPublication fhirVersion, String lang) { - super(fhirVersion, lang); - } - + + +/** + * This class is superceded by TerminologyValidationOptions but retained here for backwards compatibility + * @author graha + * + */ +public class TerminologyServiceOptions extends ValidationOptions { + + public TerminologyServiceOptions() { this(FhirPublication.R5); } + + public TerminologyServiceOptions(FhirPublication fhirVersion) { + super(fhirVersion); + } + + public TerminologyServiceOptions(FhirPublication fhirVersion, String lang) { + super(fhirVersion, lang); + } + } \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/validation/ValidationOptions.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/validation/ValidationOptions.java index 47a32d217..3657e9e8d 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/validation/ValidationOptions.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/validation/ValidationOptions.java @@ -25,6 +25,8 @@ public class ValidationOptions { private boolean exampleOK = false; private FhirPublication fhirVersion; + public ValidationOptions() { this(FhirPublication.R5); } + public ValidationOptions(FhirPublication fhirVersion) { super(); this.fhirVersion = fhirVersion; diff --git a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/CanonicalPairTest.java b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/CanonicalPairTest.java new file mode 100644 index 000000000..3b67bf81b --- /dev/null +++ b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/CanonicalPairTest.java @@ -0,0 +1,90 @@ +package org.hl7.fhir.utilities; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class CanonicalPairTest { + + @Test + void testCanonicalNull() { + var canonical = new CanonicalPair(null); + assertNull(canonical.getUrl()); + assertNull(canonical.getVersion()); + assertFalse(canonical.hasVersion()); + assertFalse(canonical.hasNonEmptyVersion()); + String expectedVer = "1.0"; + String actualVer = canonical.getVersionOr(expectedVer); + assertEquals(expectedVer, actualVer); + } + + @Test + void testCanonicalEmpty() { + var url = ""; + var canonical = new CanonicalPair(url); + assertEquals(url, canonical.getUrl()); + assertFalse(canonical.hasVersion()); + assertFalse(canonical.hasNonEmptyVersion()); + String expectedVer = "1.0"; + String actualVer = canonical.getVersionOr(expectedVer); + assertEquals(expectedVer, actualVer); + } + + @Test + void testCanonicalWithoutVersion() { + var url = "https://www.test.org"; + var canonical = new CanonicalPair(url); + assertEquals(url, canonical.getUrl()); + assertNull(canonical.getVersion()); + assertFalse(canonical.hasVersion()); + assertFalse(canonical.hasNonEmptyVersion()); + String expectedVer = "1.0"; + String actualVer = canonical.getVersionOr(expectedVer); + assertEquals(expectedVer, actualVer); + } + + @Test + void testCanonicalWithEmptyVersion() { + var expectedUrl = "https://www.test.org"; + var url = expectedUrl + "|"; + var canonical = new CanonicalPair(url); + assertEquals(expectedUrl, canonical.getUrl()); + assertEquals("", canonical.getVersion()); + assertTrue(canonical.hasVersion()); + assertFalse(canonical.hasNonEmptyVersion()); + String alternativeVer = "1.0"; + String actualVer = canonical.getVersionOr(alternativeVer); + assertEquals("", actualVer); + } + + @Test + void testCanonicalWithVersion() { + var expectedUrl = "https://www.test.org"; + var expectedVersion = "2.6"; + var url = expectedUrl + "|" + expectedVersion; + var canonical = new CanonicalPair(url); + assertEquals(expectedUrl, canonical.getUrl()); + assertEquals(expectedVersion, canonical.getVersion()); + assertTrue(canonical.hasVersion()); + assertTrue(canonical.hasNonEmptyVersion()); + String alternativeVer = "1.0"; + String actualVer = canonical.getVersionOr(alternativeVer); + assertEquals(expectedVersion, actualVer); + } + + @Test + void testCanonicalWithVersionIncludingPipe() { + var expectedUrl = "https://www.test.org"; + var expectedVersion = "2024|05"; + var url = expectedUrl + "|" + expectedVersion; + var canonical = new CanonicalPair(url); + assertEquals(expectedUrl, canonical.getUrl()); + assertEquals(expectedVersion, canonical.getVersion()); + assertTrue(canonical.hasVersion()); + assertTrue(canonical.hasNonEmptyVersion()); + String alternativeVer = "1.0"; + String actualVer = canonical.getVersionOr(alternativeVer); + assertEquals(expectedVersion, actualVer); + } + +} 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 827710f3b..9e88c6b4c 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 @@ -1428,25 +1428,7 @@ public class BaseValidator implements IValidationContextResourceLoader { return null; } - protected String versionFromCanonical(String system) { - if (system == null) { - return null; - } else if (system.contains("|")) { - return system.substring(0, system.indexOf("|")); - } else { - return system; - } - } - protected String systemFromCanonical(String system) { - if (system == null) { - return null; - } else if (system.contains("|")) { - return system.substring(system.indexOf("|")+1); - } else { - return system; - } - } @Override public Resource loadContainedResource(List errors, String path, Element resource, String id, Class class1) throws FHIRException { 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 a23d559f7..b690d2c31 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 @@ -1456,9 +1456,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat timeTracker.tx(t, "vc "+cc.toString()); } } - } catch (Exception e) { - if (STACK_TRACE) e.printStackTrace(); - warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT, e.getMessage()); + } catch (CheckCodeOnServerException e) { + if (STACK_TRACE) e.getCause().printStackTrace(); + warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT, e.getCause().getMessage()); } } } else if (binding.hasValueSet()) { @@ -1469,12 +1469,17 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } if (!noTerminologyChecks && theElementCntext != null && !checked) { // no binding check, so we just check the CodeableConcept generally - CodeableConcept cc = ObjectConverter.readAsCodeableConcept(element); - if (cc.hasCoding()) { - long t = System.nanoTime(); - ValidationResult vr = checkCodeOnServer(stack, null, cc); - bh.see(processTxIssues(errors, vr, element, path, org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, false, null)); - timeTracker.tx(t, "vc "+cc.toString()); + try { + CodeableConcept cc = ObjectConverter.readAsCodeableConcept(element); + if (cc.hasCoding()) { + long t = System.nanoTime(); + ValidationResult vr = checkCodeOnServer(stack, null, cc); + bh.see(processTxIssues(errors, vr, element, path, org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, false, null)); + timeTracker.tx(t, "vc " + cc.toString()); + } + } catch (CheckCodeOnServerException e) { + if (STACK_TRACE) e.getCause().printStackTrace(); + warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT, e.getCause().getMessage()); } } return checkDisp; @@ -1502,8 +1507,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat boolean ok = true; if (vr != null) { for (OperationOutcomeIssueComponent iss : vr.getIssues()) { - if (!iss.getDetails().hasCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", "not-in-vs") && - !iss.getDetails().hasCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", "this-code-not-in-vs") + if (!iss.getDetails().hasCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", "not-in-vs") + && !iss.getDetails().hasCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", "this-code-not-in-vs") && !(ignoreCantInfer || iss.getDetails().hasCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", "cannot-infer"))) { OperationOutcomeIssueComponent i = iss.copy(); if (notFoundLevel != null && i.getDetails().hasCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", "not-found")) { @@ -1653,9 +1658,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat timeTracker.tx(t, DataRenderer.display(context, cc)); } } - } catch (Exception e) { - if (STACK_TRACE) e.printStackTrace(); - warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT, e.getMessage()); + } catch (CheckCodeOnServerException e) { + if (STACK_TRACE) e.getCause().printStackTrace(); + warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT, e.getCause().getMessage()); } // special case: if the logical model has both CodeableConcept and Coding mappings, we'll also check the first coding. if (getMapping("http://hl7.org/fhir/terminology-pattern", logical, logical.getSnapshot().getElementFirstRep()).contains("Coding")) { @@ -1861,9 +1866,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat else ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_8, describeReference(maxVSUrl, valueset), ccSummary(cc)) && ok; } - } catch (Exception e) { - if (STACK_TRACE) e.printStackTrace(); - warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT_MAX, e.getMessage()); + } catch (CheckCodeOnServerException e) { + if (STACK_TRACE) e.getCause().printStackTrace(); + warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT_MAX, e.getCause().getMessage()); } } return ok; @@ -7474,6 +7479,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return checkForInactive(filterOutSpecials(stack.getLiteralPath(), vs, context.validateCode(options, value, vs)), new CodeType(value)); } + static class CheckCodeOnServerException extends Exception { + public CheckCodeOnServerException(Exception e) { + super(e); + } + } // no delay on this one? public ValidationResult checkCodeOnServer(NodeStack stack, String code, String system, String version, String display, boolean checkDisplay) { @@ -7490,9 +7500,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return checkForInactive(filterOutSpecials(stack.getLiteralPath(), valueset, context.validateCode(baseOptions.withLanguage(stack.getWorkingLang()), c, valueset)), c); } - public ValidationResult checkCodeOnServer(NodeStack stack, ValueSet valueset, CodeableConcept cc) { + public ValidationResult checkCodeOnServer(NodeStack stack, ValueSet valueset, CodeableConcept cc) throws CheckCodeOnServerException { codingObserver.seeCode(stack, cc); - return checkForInactive(filterOutSpecials(stack.getLiteralPath(), valueset, context.validateCode(baseOptions.withLanguage(stack.getWorkingLang()), cc, valueset)), cc); + try { + return checkForInactive(filterOutSpecials(stack.getLiteralPath(), valueset, context.validateCode(baseOptions.withLanguage(stack.getWorkingLang()), cc, valueset)), cc); + } catch (Exception e) { + throw new CheckCodeOnServerException(e); + } } private ValidationResult filterOutSpecials(String path, ValueSet vs, ValidationResult vr) { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/CodeSystemValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/CodeSystemValidator.java index 6363c37b6..e87456b7c 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/CodeSystemValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/CodeSystemValidator.java @@ -13,6 +13,7 @@ import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; +import org.hl7.fhir.utilities.CanonicalPair; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.VersionUtilities; import org.hl7.fhir.utilities.i18n.I18nConstants; @@ -620,7 +621,8 @@ public class CodeSystemValidator extends BaseValidator { private boolean validateSupplementConcept(List errors, Element concept, NodeStack stack, String supp, ValidationOptions options) { String code = concept.getChildValue("code"); if (!Utilities.noString(code)) { - org.hl7.fhir.r5.terminologies.utilities.ValidationResult res = context.validateCode(options, systemFromCanonical(supp), versionFromCanonical(supp), code, null); + var canonical = new CanonicalPair(supp); + org.hl7.fhir.r5.terminologies.utilities.ValidationResult res = context.validateCode(options, canonical.getUrl(), canonical.getVersion(), code, null); return rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), res.isOk(), I18nConstants.CODESYSTEM_CS_SUPP_INVALID_CODE, supp, code); } else { return true; diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/ResourceValidationTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/ResourceValidationTests.java index 3cf91da2e..e49726978 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/ResourceValidationTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/ResourceValidationTests.java @@ -1,5 +1,9 @@ package org.hl7.fhir.validation; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.MatcherAssert.assertThat; + import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; @@ -10,6 +14,7 @@ import org.hl7.fhir.r5.formats.XmlParser; import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.test.utils.TestingUtilities; import org.hl7.fhir.utilities.FhirPublication; +import org.hl7.fhir.utilities.i18n.I18nConstants; import org.hl7.fhir.utilities.settings.FhirSettings; import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.validation.instance.InstanceValidator; @@ -25,7 +30,7 @@ public class ResourceValidationTests { private static InstanceValidator val; - private void runTest(String filename) throws IOException, FileNotFoundException, Exception { + private List runTest(String filename) throws IOException, FileNotFoundException, Exception { TestingUtilities.injectCorePackageLoader(); if (val == null) { ctxt = TestingUtilities.getSharedWorkerContext(); @@ -37,6 +42,7 @@ public class ResourceValidationTests { Resource res = (Resource) new XmlParser().parse(TestingUtilities.loadTestResourceStream("r5", filename)); val.validate(val, errors, res); Assertions.assertNotNull(errors); + return errors; } @@ -123,4 +129,26 @@ public class ResourceValidationTests { } -} + @Test + public void testCodesystemSupplementsVersion() throws Exception { + List errors = runTest("codesystem-example-supplement-version.xml"); + assertNoErrors(errors); + } + + + private void assertNoErrors(List errors) { + List errorMessages = new ArrayList<>(); + for(ValidationMessage message : errors) { + // we will skip the message that WG citation is needed + if(I18nConstants.VALIDATION_HL7_WG_NEEDED.equals(message.getMessageId())) { + continue; + } + if(message.getLevel().isError()) { + errorMessages.add(message.getMessage()); + } + } + assertThat("No error message expected in validation outcome.", errorMessages, is(empty())); + } + + +} \ No newline at end of file