diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java index 64d7a010c..8280ff898 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java @@ -4887,13 +4887,13 @@ public class ProfileUtilities extends TranslatingUtilities { private Piece describeCoded(HierarchicalTableGenerator gen, DataType fixed) { if (fixed instanceof Coding) { Coding c = (Coding) fixed; - ValidationResult vr = context.validateCode(terminologyServiceOptions , c.getSystem(), c.getCode(), c.getDisplay()); + ValidationResult vr = context.validateCode(terminologyServiceOptions , c.getSystem(), c.getVersion(), c.getCode(), c.getDisplay()); if (vr.getDisplay() != null) return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen"); } else if (fixed instanceof CodeableConcept) { CodeableConcept cc = (CodeableConcept) fixed; for (Coding c : cc.getCoding()) { - ValidationResult vr = context.validateCode(terminologyServiceOptions, c.getSystem(), c.getCode(), c.getDisplay()); + ValidationResult vr = context.validateCode(terminologyServiceOptions, c.getSystem(), c.getVersion(), c.getCode(), c.getDisplay()); if (vr.getDisplay() != null) return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen"); } 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 9dfe7e7ee..705d2e4ce 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 @@ -726,16 +726,16 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte // --- validate code ------------------------------------------------------------------------------- @Override - public ValidationResult validateCode(ValidationOptions options, String system, String code, String display) { + public ValidationResult validateCode(ValidationOptions options, String system, String version, String code, String display) { assert options != null; - Coding c = new Coding(system, code, display); + Coding c = new Coding(system, version, code, display); return validateCode(options, c, null); } @Override - public ValidationResult validateCode(ValidationOptions options, String system, String code, String display, ValueSet vs) { + public ValidationResult validateCode(ValidationOptions options, String system, String version, String code, String display, ValueSet vs) { assert options != null; - Coding c = new Coding(system, code, display); + Coding c = new Coding(system, version, code, display); return validateCode(options, c, vs); } @@ -782,9 +782,10 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte for (CodingValidationRequest t : codes) { if (!t.hasResult()) { + String codeKey = t.getCoding().hasVersion() ? t.getCoding().getSystem()+"|"+t.getCoding().getVersion() : t.getCoding().getSystem(); if (!options.isUseServer()) { t.setResult(new ValidationResult(IssueSeverity.WARNING,formatMessage(I18nConstants.UNABLE_TO_VALIDATE_CODE_WITHOUT_USING_SERVER), TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS)); - } else if (unsupportedCodeSystems.contains(t.getCoding().getSystem())) { + } else if (unsupportedCodeSystems.contains(codeKey)) { t.setResult(new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.TERMINOLOGY_TX_SYSTEM_NOTKNOWN, t.getCoding().getSystem()), TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED)); } else if (noTerminologyServer) { t.setResult(new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE)); @@ -882,10 +883,11 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte } } + String codeKey = code.hasVersion() ? code.getSystem()+"|"+code.getVersion() : code.getSystem(); if (!options.isUseServer()) { return new ValidationResult(IssueSeverity.WARNING,formatMessage(I18nConstants.UNABLE_TO_VALIDATE_CODE_WITHOUT_USING_SERVER), TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS); } - if (unsupportedCodeSystems.contains(code.getSystem())) { + if (unsupportedCodeSystems.contains(codeKey)) { return new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.TERMINOLOGY_TX_SYSTEM_NOTKNOWN, code.getSystem()), TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED); } @@ -910,8 +912,8 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte } catch (Exception e) { res = new ValidationResult(IssueSeverity.ERROR, e.getMessage() == null ? e.getClass().getName() : e.getMessage()).setTxLink(txLog == null ? null : txLog.getLastId()).setErrorClass(TerminologyServiceErrorClass.SERVER_ERROR); } - if (res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) { - unsupportedCodeSystems.add(code.getSystem()); + if (res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED && !code.hasVersion()) { + unsupportedCodeSystems.add(codeKey); } else if (txCache != null) { // we never cache unsuppoted code systems - we always keep trying (but only once per run) txCache.cacheValidation(cacheToken, res, TerminologyCache.PERMANENT); } @@ -925,6 +927,9 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte if (options.getValueSetMode() != ValueSetMode.ALL_CHECKS) { pIn.addParameter("valueSetMode", options.getValueSetMode().toString()); } + if (options.versionFlexible()) { + pIn.addParameter("default-to-latest-version", true); + } } @Override @@ -1088,6 +1093,8 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte err = TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED; } else if (it == IssueType.NOTSUPPORTED) { err = TerminologyServiceErrorClass.VALUESET_UNSUPPORTED; + } else { + err = null; } } catch (FHIRException e) { } @@ -1118,6 +1125,10 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte txCache.removeCS(url); } + public void clearTS() { + txCache.clear(); + } + @Override public List findMapsForSource(String url) throws FHIRException { 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 0c189fb18..6dd535d95 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 @@ -544,6 +544,12 @@ public interface IWorkerContext { private TerminologyServiceErrorClass errorClass; private String txLink; + @Override + public String toString() { + return "ValidationResult [definition=" + definition + ", system=" + system + ", severity=" + severity + ", message=" + message + ", errorClass=" + + errorClass + ", txLink=" + txLink + "]"; + } + public ValidationResult(IssueSeverity severity, String message) { this.severity = severity; this.message = message; @@ -583,6 +589,10 @@ public interface IWorkerContext { return definition == null ? null : definition.getCode(); } + public String getDefinition() { + return definition == null ? null : definition.getDefinition(); + } + public ConceptDefinitionComponent asConceptDefinition() { return definition; } @@ -671,7 +681,7 @@ public interface IWorkerContext { * @param display - equals Coding.display (optional) * @return */ - public ValidationResult validateCode(ValidationOptions options, String system, String code, String display); + public ValidationResult validateCode(ValidationOptions options, String system, String version, String code, String display); /** * Validation of a code - consult the terminology infrstructure and/or service @@ -688,7 +698,7 @@ public interface IWorkerContext { * @param vs the applicable valueset (optional) * @return */ - public ValidationResult validateCode(ValidationOptions options, String system, String code, String display, ValueSet vs); + public ValidationResult validateCode(ValidationOptions options, String system, String version, String code, String display, ValueSet vs); /** * Validation of a code - consult the terminology infrstructure and/or service diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/TerminologyCache.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/TerminologyCache.java index 1f0184dd1..2f926ce3f 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/TerminologyCache.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/TerminologyCache.java @@ -124,6 +124,9 @@ public class TerminologyCache { load(); } + public void clear() { + caches.clear(); + } public CacheToken generateValidationToken(ValidationOptions options, Coding code, ValueSet vs) { CacheToken ct = new CacheToken(); if (code.hasSystem()) @@ -337,11 +340,36 @@ public class TerminologyCache { sw.write(" \"error\" : \""+Utilities.escapeJson(ce.e.getError()).trim()+"\"\r\n}\r\n"); } else { sw.write("v: {\r\n"); - sw.write(" \"display\" : \""+Utilities.escapeJson(ce.v.getDisplay()).trim()+"\",\r\n"); - sw.write(" \"code\" : \""+Utilities.escapeJson(ce.v.getCode()).trim()+"\",\r\n"); - sw.write(" \"system\" : \""+Utilities.escapeJson(ce.v.getSystem()).trim()+"\",\r\n"); - sw.write(" \"severity\" : "+(ce.v.getSeverity() == null ? "null" : "\""+ce.v.getSeverity().toCode().trim()+"\"")+",\r\n"); - sw.write(" \"error\" : \""+Utilities.escapeJson(ce.v.getMessage()).trim()+"\"\r\n}\r\n"); + boolean first = true; + if (ce.v.getDisplay() != null) { + if (first) first = false; else sw.write(",\r\n"); + sw.write(" \"display\" : \""+Utilities.escapeJson(ce.v.getDisplay()).trim()+"\""); + } + if (ce.v.getCode() != null) { + if (first) first = false; else sw.write(",\r\n"); + sw.write(" \"code\" : \""+Utilities.escapeJson(ce.v.getCode()).trim()+"\""); + } + if (ce.v.getSystem() != null) { + if (first) first = false; else sw.write(",\r\n"); + sw.write(" \"system\" : \""+Utilities.escapeJson(ce.v.getSystem()).trim()+"\""); + } + if (ce.v.getSeverity() != null) { + if (first) first = false; else sw.write(",\r\n"); + sw.write(" \"severity\" : "+"\""+ce.v.getSeverity().toCode().trim()+"\""+""); + } + if (ce.v.getMessage() != null) { + if (first) first = false; else sw.write(",\r\n"); + sw.write(" \"error\" : \""+Utilities.escapeJson(ce.v.getMessage()).trim()+"\""); + } + if (ce.v.getErrorClass() != null) { + if (first) first = false; else sw.write(",\r\n"); + sw.write(" \"class\" : \""+Utilities.escapeJson(ce.v.getErrorClass().toString())+"\""); + } + if (ce.v.getDefinition() != null) { + if (first) first = false; else sw.write(",\r\n"); + sw.write(" \"definition\" : \""+Utilities.escapeJson(ce.v.getDefinition()).trim()+"\""); + } + sw.write("\r\n}\r\n"); } sw.write(ENTRY_MARKER+"\r\n"); } @@ -386,11 +414,15 @@ public class TerminologyCache { else ce.e = new ValueSetExpansionOutcome(error, TerminologyServiceErrorClass.UNKNOWN); } else { - IssueSeverity severity = o.get("severity") instanceof JsonNull ? null : IssueSeverity.fromCode(o.get("severity").getAsString()); + String t = loadJS(o.get("severity")); + IssueSeverity severity = t == null ? null : IssueSeverity.fromCode(t); String display = loadJS(o.get("display")); String code = loadJS(o.get("code")); String system = loadJS(o.get("system")); - ce.v = new ValidationResult(severity, error, system, new ConceptDefinitionComponent().setDisplay(display).setCode(code)); + String definition = loadJS(o.get("definition")); + t = loadJS(o.get("class")); + TerminologyServiceErrorClass errorClass = t == null ? null : TerminologyServiceErrorClass.valueOf(t) ; + ce.v = new ValidationResult(severity, error, system, new ConceptDefinitionComponent().setDisplay(display).setDefinition(definition).setCode(code)).setErrorClass(errorClass); } nc.map.put(String.valueOf(hashNWS(ce.request)), ce); nc.list.add(ce); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Coding.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Coding.java index d66066010..fe57dc5db 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Coding.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Coding.java @@ -605,6 +605,12 @@ public class Coding extends DataType implements IBaseCoding, ICompositeType, ICo return res; } + public Coding(String theSystem, String theVersion, String theCode, String theDisplay) { + setSystem(theSystem); + setVersion(theVersion); + setCode(theCode); + setDisplay(theDisplay); + } // end addition } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ConceptMapRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ConceptMapRenderer.java index 50f7d0502..47f214735 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ConceptMapRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ConceptMapRenderer.java @@ -136,7 +136,7 @@ public class ConceptMapRenderer extends TerminologyRenderer { tr = tbl.tr(); XhtmlNode td = tr.td(); td.addText(ccl.getCode()); - display = getDisplayForConcept(grp.getSource(), ccl.getCode()); + display = getDisplayForConcept(systemFromCanonical(grp.getSource()), versionFromCanonical(grp.getSource()), ccl.getCode()); if (display != null && !isSameCodeAndDisplay(ccl.getCode(), display)) td.tx(" ("+display+")"); TargetElementComponent ccm = ccl.getTarget().get(0); @@ -152,7 +152,7 @@ public class ConceptMapRenderer extends TerminologyRenderer { } td = tr.td(); td.addText(ccm.getCode()); - display = getDisplayForConcept(grp.getTarget(), ccm.getCode()); + display = getDisplayForConcept(systemFromCanonical(grp.getTarget()), versionFromCanonical(grp.getTarget()), ccm.getCode()); if (display != null && !isSameCodeAndDisplay(ccm.getCode(), display)) td.tx(" ("+display+")"); if (comment) @@ -217,7 +217,7 @@ public class ConceptMapRenderer extends TerminologyRenderer { td.addText(ccl.getCode()); else td.addText(grp.getSource()+" / "+ccl.getCode()); - display = getDisplayForConcept(grp.getSource(), ccl.getCode()); + display = getDisplayForConcept(systemFromCanonical(grp.getSource()), versionFromCanonical(grp.getSource()), ccl.getCode()); tr.td().style("border-left-width: 0px").tx(display == null ? "" : display); tr.td().colspan("4").style("background-color: #efefef").tx("(not mapped)"); @@ -238,7 +238,7 @@ public class ConceptMapRenderer extends TerminologyRenderer { td.addText(ccl.getCode()); else td.addText(grp.getSource()+" / "+ccl.getCode()); - display = getDisplayForConcept(grp.getSource(), ccl.getCode()); + display = getDisplayForConcept(systemFromCanonical(grp.getSource()), versionFromCanonical(grp.getSource()), ccl.getCode()); td = tr.td(); if (!first) td.style("border-left-width: 0px; border-top-style: none"); @@ -283,7 +283,7 @@ public class ConceptMapRenderer extends TerminologyRenderer { td.addText(ccm.getCode()); else td.addText(grp.getTarget()+" / "+ccm.getCode()); - display = getDisplayForConcept(grp.getTarget(), ccm.getCode()); + display = getDisplayForConcept(systemFromCanonical(grp.getTarget()), versionFromCanonical(grp.getTarget()), ccm.getCode()); tr.td().style("border-left-width: 0px").tx(display == null ? "" : display); for (String s : targets.keySet()) { @@ -408,11 +408,9 @@ public class ConceptMapRenderer extends TerminologyRenderer { private String getDisplay(List list, String s) { for (OtherElementComponent c : list) { if (s.equals(c.getProperty())) - return getDisplayForConcept(c.getSystem(), c.getValue()); + return getDisplayForConcept(systemFromCanonical(c.getSystem()), versionFromCanonical(c.getSystem()), c.getValue()); } return null; } - - } \ No newline at end of file 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 c5f15dfc3..a35c5d640 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 @@ -191,8 +191,8 @@ public class DataRenderer extends Renderer { return b.toString(); } - private String lookupCode(String system, String code) { - ValidationResult t = getContext().getWorker().validateCode(getContext().getTerminologyServiceOptions(), system, code, null); + private String lookupCode(String system, String version, String code) { + ValidationResult t = getContext().getWorker().validateCode(getContext().getTerminologyServiceOptions().setVersionFlexible(true), system, version, code, null); if (t != null && t.getDisplay() != null) return t.getDisplay(); @@ -484,7 +484,7 @@ public class DataRenderer extends Renderer { if (c.hasDisplayElement()) return c.getDisplay(); if (Utilities.noString(s)) - s = lookupCode(c.getSystem(), c.getCode()); + s = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); if (Utilities.noString(s)) s = c.getCode(); return s; @@ -507,7 +507,7 @@ public class DataRenderer extends Renderer { if (c.hasDisplayElement()) s = c.getDisplay(); if (Utilities.noString(s)) - s = lookupCode(c.getSystem(), c.getCode()); + s = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); String sn = describeSystem(c.getSystem()); @@ -539,13 +539,13 @@ public class DataRenderer extends Renderer { if (c.hasDisplayElement()) s = c.getDisplay(); if (Utilities.noString(s)) - s = lookupCode(c.getSystem(), c.getCode()); + s = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); if (Utilities.noString(s)) s = c.getCode(); if (showCodeDetails) { - x.addText(s+" (Details: "+TerminologyRenderer.describeSystem(c.getSystem())+" code "+c.getCode()+" = '"+lookupCode(c.getSystem(), c.getCode())+"', stated as '"+c.getDisplay()+"')"); + x.addText(s+" (Details: "+TerminologyRenderer.describeSystem(c.getSystem())+" code "+c.getCode()+" = '"+lookupCode(c.getSystem(), c.getVersion(), c.getCode())+"', stated as '"+c.getDisplay()+"')"); } else x.span(null, "{"+c.getSystem()+" "+c.getCode()+"}").addText(s); } @@ -564,7 +564,7 @@ public class DataRenderer extends Renderer { // still? ok, let's try looking it up for (Coding c : cc.getCoding()) { if (c.hasCode() && c.hasSystem()) { - s = lookupCode(c.getSystem(), c.getCode()); + s = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); if (!Utilities.noString(s)) break; } @@ -611,7 +611,7 @@ public class DataRenderer extends Renderer { // still? ok, let's try looking it up for (Coding c : cc.getCoding()) { if (c.hasCodeElement() && c.hasSystemElement()) { - s = lookupCode(c.getSystem(), c.getCode()); + s = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); if (!Utilities.noString(s)) break; } @@ -636,7 +636,7 @@ public class DataRenderer extends Renderer { first = false; } else sp.tx("; "); - sp.tx("{"+TerminologyRenderer.describeSystem(c.getSystem())+" code '"+c.getCode()+"' = '"+lookupCode(c.getSystem(), c.getCode())+(c.hasDisplay() ? "', given as '"+c.getDisplay()+"'}" : "")); + sp.tx("{"+TerminologyRenderer.describeSystem(c.getSystem())+" code '"+c.getCode()+"' = '"+lookupCode(c.getSystem(), c.getVersion(), c.getCode())+(c.hasDisplay() ? "', given as '"+c.getDisplay()+"'}" : "")); } sp.tx(")"); } else { @@ -661,7 +661,7 @@ public class DataRenderer extends Renderer { else if (ii.getType().hasCoding() && ii.getType().getCoding().get(0).hasDisplay()) s = ii.getType().getCoding().get(0).getDisplay()+": "+s; else if (ii.getType().hasCoding() && ii.getType().getCoding().get(0).hasCode()) - s = lookupCode(ii.getType().getCoding().get(0).getSystem(), ii.getType().getCoding().get(0).getCode())+": "+s; + s = lookupCode(ii.getType().getCoding().get(0).getSystem(), ii.getType().getCoding().get(0).getVersion(), ii.getType().getCoding().get(0).getCode())+": "+s; } else { s = "id: "+s; } @@ -838,7 +838,7 @@ public class DataRenderer extends Renderer { s.append("(system = '").append(TerminologyRenderer.describeSystem(q.getSystem())) .append("' code ").append(q.getCode()) - .append(" = '").append(lookupCode(q.getSystem(), q.getCode())).append("')"); + .append(" = '").append(lookupCode(q.getSystem(), null, q.getCode())).append("')"); return s.toString(); } @@ -858,7 +858,7 @@ public class DataRenderer extends Renderer { else if (q.hasCode()) x.tx(" "+q.getCode()); if (showCodeDetails && q.hasCode()) { - x.span("background: LightGoldenRodYellow", null).tx(" (Details: "+TerminologyRenderer.describeSystem(q.getSystem())+" code "+q.getCode()+" = '"+lookupCode(q.getSystem(), q.getCode())+"')"); + x.span("background: LightGoldenRodYellow", null).tx(" (Details: "+TerminologyRenderer.describeSystem(q.getSystem())+" code "+q.getCode()+" = '"+lookupCode(q.getSystem(), null, q.getCode())+"')"); } } @@ -1160,4 +1160,25 @@ public class DataRenderer extends Renderer { return b.toString(); } + 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; + } + } + + } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/TerminologyRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/TerminologyRenderer.java index b9fc4a258..5794b3c4c 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/TerminologyRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/TerminologyRenderer.java @@ -309,10 +309,10 @@ public abstract class TerminologyRenderer extends ResourceRenderer { - protected String getDisplayForConcept(String system, String value) { + protected String getDisplayForConcept(String system, String version, String value) { if (value == null || system == null) return null; - ValidationResult cl = getContext().getWorker().validateCode(getContext().getTerminologyServiceOptions(), system, value, null); + ValidationResult cl = getContext().getWorker().validateCode(getContext().getTerminologyServiceOptions().setVersionFlexible(true), system, version, value, null); return cl == null ? null : cl.getDisplay(); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java index 9136875b7..816249717 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java @@ -896,7 +896,7 @@ public class ValueSetRenderer extends TerminologyRenderer { li.ah(href).addText(f.getValue()); } else if ("concept".equals(f.getProperty()) && inc.hasSystem()) { li.addText(f.getValue()); - ValidationResult vr = getContext().getWorker().validateCode(getContext().getTerminologyServiceOptions(), inc.getSystem(), f.getValue(), null); + ValidationResult vr = getContext().getWorker().validateCode(getContext().getTerminologyServiceOptions(), inc.getSystem(), inc.getVersion(), f.getValue(), null); if (vr.isOk()) { li.tx(" ("+vr.getDisplay()+")"); } 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 index 4d1157eb6..6872e5e63 100644 --- 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 @@ -25,9 +25,11 @@ import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.exceptions.PathEngineException; import org.hl7.fhir.r5.conformance.ProfileUtilities; import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult; import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.BaseDateTimeType; import org.hl7.fhir.r5.model.BooleanType; +import org.hl7.fhir.r5.model.CodeableConcept; import org.hl7.fhir.r5.model.Constants; import org.hl7.fhir.r5.model.DateTimeType; import org.hl7.fhir.r5.model.DateType; @@ -54,6 +56,7 @@ import org.hl7.fhir.r5.model.TypeConvertor; import org.hl7.fhir.r5.model.TypeDetails; import org.hl7.fhir.r5.model.TypeDetails.ProfiledType; import org.hl7.fhir.r5.model.ValueSet; +import org.hl7.fhir.r5.renderers.DataRenderer; import org.hl7.fhir.r5.utils.FHIRLexer.FHIRLexerException; import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext.FunctionDetails; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; @@ -2329,7 +2332,8 @@ public class FHIRPathEngine { private List opMemberOf(ExecutionContext context, List left, List right, ExpressionNode expr) throws FHIRException { boolean ans = false; - ValueSet vs = hostServices != null ? hostServices.resolveValueSet(context.appInfo, right.get(0).primitiveValue()) : worker.fetchResource(ValueSet.class, right.get(0).primitiveValue()); + String url = right.get(0).primitiveValue(); + ValueSet vs = hostServices != null ? hostServices.resolveValueSet(context.appInfo, url) : worker.fetchResource(ValueSet.class, url); if (vs != null) { for (Base l : left) { if (Utilities.existsInList(l.fhirType(), "code", "string", "uri")) { @@ -2341,7 +2345,10 @@ public class FHIRPathEngine { ans = true; } } else if (l.fhirType().equals("CodeableConcept")) { - if (worker.validateCode(terminologyServiceOptions, TypeConvertor.castToCodeableConcept(l), vs).isOk()) { + CodeableConcept cc = TypeConvertor.castToCodeableConcept(l); + ValidationResult vr = worker.validateCode(terminologyServiceOptions, cc, vs); + // System.out.println("~~~ "+DataRenderer.display(worker, cc)+ " memberOf "+url+": "+vr.toString()); + if (vr.isOk()) { ans = true; } } else { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/NPMPackageGenerator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/NPMPackageGenerator.java index 8a7596a80..7744b4f8a 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/NPMPackageGenerator.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/NPMPackageGenerator.java @@ -322,16 +322,9 @@ public class NPMPackageGenerator { public void addFile(Category cat, String name, byte[] content) throws IOException { String path = cat.getDirectory()+name; - if (!path.startsWith("package/")) { - path = "package/" +path; - } if (path.length() > 100) { name = name.substring(0, name.indexOf("-"))+"-"+UUID.randomUUID().toString(); - path = cat.getDirectory()+name; - if (!path.startsWith("package/")) { - path = "package/" +path; - } - + path = cat.getDirectory()+name; } if (created.contains(path)) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java index 1fc539ece..2c6552e00 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java @@ -1752,6 +1752,7 @@ public class StructureMapUtilities { // if we can get this as a valueSet, we will String system = null; String display = null; + String version = null; ValueSet vs = Utilities.noString(uri) ? null : worker.fetchResourceWithException(ValueSet.class, uri); if (vs != null) { ValueSetExpansionOutcome vse = worker.expandVS(vs, true, false); @@ -1763,20 +1764,23 @@ public class StructureMapUtilities { b.append(t.getCode()); if (code.equals(t.getCode()) && t.hasSystem()) { system = t.getSystem(); + version = t.getVersion(); display = t.getDisplay(); break; } if (code.equalsIgnoreCase(t.getDisplay()) && t.hasSystem()) { system = t.getSystem(); + version = t.getVersion(); display = t.getDisplay(); break; } } if (system == null) throw new FHIRException("The code '" + code + "' is not in the value set '" + uri + "' (valid codes: " + b.toString() + "; also checked displays)"); - } else + } else { system = uri; - ValidationResult vr = worker.validateCode(terminologyServiceOptions, system, code, null); + } + ValidationResult vr = worker.validateCode(terminologyServiceOptions.setVersionFlexible(true), system, version, code, null); if (vr != null && vr.getDisplay() != null) display = vr.getDisplay(); return new Coding().setSystem(system).setCode(code).setDisplay(display); diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java index 9fce7c0f0..1237d0157 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java @@ -1,5 +1,37 @@ package org.hl7.fhir.utilities; +import static org.apache.commons.lang3.StringUtils.isBlank; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + /* Copyright (c) 2011+, HL7, Inc. All rights reserved. @@ -33,41 +65,9 @@ package org.hl7.fhir.utilities; import org.apache.commons.io.FileUtils; import org.hl7.fhir.exceptions.FHIRException; -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.FilenameFilter; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.nio.channels.FileChannel; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -import static org.apache.commons.lang3.StringUtils.isBlank; - public class Utilities { + private static final String UUID_REGEX = "[0-9a-f]{8}\\-[0-9a-f]{4}\\-[0-9a-f]{4}\\-[0-9a-f]{4}\\-[0-9a-f]{12}"; private static final String OID_REGEX = "[0-2](\\.(0|[1-9][0-9]*))+"; /** @@ -1476,7 +1476,7 @@ public class Utilities { } final static int[] illegalChars = {34, 60, 62, 124, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 58, 42, 63, 92, 47}; - + static { Arrays.sort(illegalChars); } @@ -1493,5 +1493,13 @@ public class Utilities { return cleanName.toString(); } + public static boolean isValidUUID(String uuid) { + return uuid.matches(UUID_REGEX); + } + + public static boolean isValidOID(String oid) { + return oid.matches(OID_REGEX); + } + } \ 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 f35a7b8f8..03e40d5c6 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 @@ -105,8 +105,10 @@ public class I18nConstants { public static final String ERROR_READING__FROM_PACKAGE__ = "Error_reading__from_package__"; public static final String ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES = "Error_validating_code_running_without_terminology_services"; public static final String ERROR_WRITING_NUMBER__TO_JSON = "error_writing_number__to_JSON"; - public static final String EXTENSION_EXT_CONTEXT_WRONG = "Extension_EXT_Context_Wrong"; - public static final String EXTENSION_EXT_CONTEXT_WRONG_XVER = "EXTENSION_EXT_CONTEXT_WRONG_XVER"; + public static final String EXTENSION_EXTP_CONTEXT_WRONG = "Extension_EXTP_Context_Wrong"; + public static final String EXTENSION_EXTP_CONTEXT_WRONG_XVER = "EXTENSION_EXTP_CONTEXT_WRONG_XVER"; + public static final String EXTENSION_EXTM_CONTEXT_WRONG = "Extension_EXTM_Context_Wrong"; + public static final String EXTENSION_EXTM_CONTEXT_WRONG_XVER = "EXTENSION_EXTM_CONTEXT_WRONG_XVER"; public static final String EXTENSION_EXT_COUNT_MISMATCH = "Extension_EXT_Count_Mismatch"; public static final String EXTENSION_EXT_COUNT_NOTFOUND = "Extension_EXT_Count_NotFound"; public static final String EXTENSION_EXT_FIXED_BANNED = "Extension_EXT_Fixed_Banned"; @@ -459,6 +461,7 @@ public class I18nConstants { public static final String TYPE_ON_FIRST_DIFFERENTIAL_ELEMENT = "type_on_first_differential_element"; public static final String TYPE_ON_FIRST_SNAPSHOT_ELEMENT_FOR__IN__FROM_ = "type_on_first_snapshot_element_for__in__from_"; public static final String TYPE_SPECIFIC_CHECKS_CANONICAL_ABSOLUTE = "TYPE_SPECIFIC_CHECKS_CANONICAL_ABSOLUTE"; + public static final String TYPE_SPECIFIC_CHECKS_CANONICAL_CONTAINED = "TYPE_SPECIFIC_CHECKS_CANONICAL_CONTAINED"; public static final String TYPE_SPECIFIC_CHECKS_DT_ATT_NO_CONTENT = "TYPE_SPECIFIC_CHECKS_DT_ATT_NO_CONTENT"; public static final String TYPE_SPECIFIC_CHECKS_DT_ATT_NO_FETCHER = "TYPE_SPECIFIC_CHECKS_DT_ATT_NO_FETCHER"; public static final String TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_CORRECT = "TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_CORRECT"; 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 1da961eb1..5b41b758f 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 @@ -12,6 +12,7 @@ public class ValidationOptions { private boolean guessSystem = false; private ValueSetMode valueSetMode = ValueSetMode.ALL_CHECKS; private boolean vsAsUrl; + private boolean versionFlexible = true; public ValidationOptions() { super(); @@ -45,6 +46,7 @@ public class ValidationOptions { n.useClient = useClient; n.guessSystem = guessSystem; n.vsAsUrl = vsAsUrl; + n.versionFlexible = versionFlexible; return n; } @@ -75,7 +77,8 @@ public class ValidationOptions { public String toJson() { - return "\"lang\":\""+language+"\", \"useServer\":\""+Boolean.toString(useServer)+"\", \"useClient\":\""+Boolean.toString(useClient)+"\", \"guessSystem\":\""+Boolean.toString(guessSystem)+"\", \"valueSetMode\":\""+valueSetMode.toString()+"\""; + return "\"lang\":\""+language+"\", \"useServer\":\""+Boolean.toString(useServer)+"\", \"useClient\":\""+Boolean.toString(useClient)+"\", "+ + "\"guessSystem\":\""+Boolean.toString(guessSystem)+"\", \"valueSetMode\":\""+valueSetMode.toString()+"\", \"versionFlexible\":\""+Boolean.toString(versionFlexible)+"\""; } public static ValidationOptions defaults() { @@ -107,5 +110,15 @@ public class ValidationOptions { return vsAsUrl; } + public boolean versionFlexible() { + return versionFlexible; + } + + public ValidationOptions setVersionFlexible(boolean value) { + ValidationOptions n = this.copy(); + n.versionFlexible = value; + return n; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index a537bd170..edcc34f0b 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -26,7 +26,8 @@ CodeSystem_CS_VS_IncludeDetails = CodeSystem {0} has an ''all system'' value set CodeSystem_CS_VS_Invalid = CodeSystem {0} has an ''all system'' value set of {1}, but doesn''t have a single include CODESYSTEM_CS_VS_EXP_MISMATCH = CodeSystem {0} has an ''all system'' value set of {1}, but it is an expansion with the wrong number of concepts (found {2}, expected {3}) CodeSystem_CS_VS_WrongSystem = CodeSystem {0} has an ''all system'' value set of {1}, but doesn''t have a matching system ({2}) -Extension_EXT_Context_Wrong = The extension {0} is not allowed to be used at this point (allowed = {1}; this element is [{2}) +Extension_EXTP_Context_Wrong = The extension {0} is not allowed to be used at this point (allowed = {1}; this element is [{2}) +Extension_EXTM_Context_Wrong = The modifier extension {0} is not allowed to be used at this point (allowed = {1}; this element is [{2}) Extension_EXT_Count_Mismatch = Extensions count mismatch: expected {0} but found {1} Extension_EXT_Count_NotFound = Extension count mismatch: unable to find extension: {0} Extension_EXT_Fixed_Banned = No extensions allowed, as the specified fixed value doesn''t contain any extensions @@ -207,7 +208,7 @@ Type_Specific_Checks_DT_URI_UUID = URI values cannot start with uuid: Type_Specific_Checks_DT_URI_WS = URI values cannot have whitespace(''{0}'') Type_Specific_Checks_DT_URL_Resolve = URL value ''{0}'' does not resolve Type_Specific_Checks_DT_UUID_Strat = UUIDs must start with urn:uuid: -Type_Specific_Checks_DT_UUID_Vaid = UUIDs must be valid ({0}) +Type_Specific_Checks_DT_UUID_Vaid = UUIDs must be valid Validation_BUNDLE_Message = The first entry in a message must be a MessageHeader Validation_VAL_Content_Unknown = Unrecognised Content {0} Validation_VAL_NoType = Unknown type {0} @@ -468,7 +469,8 @@ MEASURE_M_GROUP_POP_NO_CODE = A measure group population should have a code when MEASURE_M_GROUP_STRATA_NO_CODE = A measure group stratifier should have a code when there is more than one population MEASURE_M_GROUP_STRATA_COMP_NO_CODE = A measure group stratifier component should have a code when there is more than one population MEASURE_M_LIB_UNKNOWN = The Library {0} could not be resolved, so expression validation may not be correct -TYPE_SPECIFIC_CHECKS_CANONICAL_ABSOLUTE = Canonical URLs must be absolute URLs if they are not fragment references ({0}) +TYPE_SPECIFIC_CHECKS_CANONICAL_ABSOLUTE = Canonical URLs must be absolute URLs if they are not fragment references ({0}) +TYPE_SPECIFIC_CHECKS_CANONICAL_CONTAINED = Canonical URLs in contained resources must be absolute URLs if present ({0}) MEASURE_MR_SCORE_PROHIBITED_RT = No measureScore when the type of the report is ''data-collection'' MEASURE_MR_SCORE_PROHIBITED_MS = No measureScore when the scoring of the mesage is ''cohort'' MEASURE_MR_SCORE_REQUIRED = A measureScore is required when the Measure.scoring={0} @@ -505,7 +507,8 @@ TYPE_SPECIFIC_CHECKS_DT_DECIMAL_CHARS = Found {0} decimal places which exceeds t Validation_VAL_Profile_WrongType = Specified profile type was ''{0}'' in profile ''{2}'', but found type ''{1}'' Validation_VAL_Profile_WrongType2 = Type mismatch processing profile {0} at path {1}: The element type is {4}, but the profile {3} is for a different type {2} VALIDATION_VAL_ILLEGAL_TYPE_CONSTRAINT = Illegal constraint in profile {0} at path {1} - cannot constrain to type {2} from base types {3} -EXTENSION_EXT_CONTEXT_WRONG_XVER = The extension {0} from FHIR version {3} is not allowed to be used at this point (allowed = {1}; this element is [{2}; this is a warning since contexts may be renamed between FHIR versions) +EXTENSION_EXTP_CONTEXT_WRONG_XVER = The extension {0} from FHIR version {3} is not allowed to be used at this point (allowed = {1}; this element is [{2}; this is a warning since contexts may be renamed between FHIR versions) +EXTENSION_EXTM_CONTEXT_WRONG_XVER = The modifier extension {0} from FHIR version {3} is not allowed to be used at this point (allowed = {1}; this element is [{2}; this is a warning since contexts may be renamed between FHIR versions) SECURITY_STRING_CONTENT_ERROR = The string value contains text that looks like embedded HTML tags, which are not allowed for security reasons in this context SECURITY_STRING_CONTENT_WARNING = The string value contains text that looks like embedded HTML tags. If this content is rendered to HTML without appropriate post-processing, it may be a security risk ALL_OK = All OK 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 82d2b48a6..d33363999 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 @@ -1007,4 +1007,24 @@ public class BaseValidator { } 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; + } + } } \ No newline at end of file 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 192c9c902..c289d1a66 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 @@ -396,6 +396,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private List bundleValidationRules = new ArrayList<>(); private boolean validateValueSetCodesOnTxServer = true; private QuestionnaireMode questionnaireMode; + private ValidationOptions baseOptions = new ValidationOptions(); public InstanceValidator(IWorkerContext theContext, IEvaluationContext hostServices, XVerExtensionManager xverManager) { super(theContext, xverManager); @@ -836,13 +837,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } // public API - private boolean checkCode(List errors, Element element, String path, String code, String system, String display, boolean checkDisplay, NodeStack stack) throws TerminologyServiceException { + private boolean checkCode(List errors, Element element, String path, String code, String system, String version, String display, boolean checkDisplay, NodeStack stack) throws TerminologyServiceException { long t = System.nanoTime(); boolean ss = context.supportsSystem(system); timeTracker.tx(t, "ss "+system); if (ss) { t = System.nanoTime(); - ValidationResult s = checkCodeOnServer(stack, code, system, display, checkDisplay); + ValidationResult s = checkCodeOnServer(stack, code, system, version, display, checkDisplay); timeTracker.tx(t, "vc "+system+"#"+code+" '"+display+"'"); if (s == null) return true; @@ -1251,8 +1252,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat for (Coding nextCoding : cc.getCoding()) { String nextCode = nextCoding.getCode(); String nextSystem = nextCoding.getSystem(); + String nextVersion = nextCoding.getVersion(); if (isNotBlank(nextCode) && isNotBlank(nextSystem) && context.supportsSystem(nextSystem)) { - ValidationResult vr = checkCodeOnServer(stack, nextCode, nextSystem, null, false); + ValidationResult vr = checkCodeOnServer(stack, nextCode, nextSystem, nextVersion, null, false); if (!vr.isOk()) { txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_NOTVALID, nextCode, nextSystem); } @@ -1286,12 +1288,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat String code = c.getCode(); String system = c.getSystem(); String display = c.getDisplay(); + String version = c.getVersion(); rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isAbsolute(system), I18nConstants.TERMINOLOGY_TX_SYSTEM_RELATIVE); if (system != null && code != null && !noTerminologyChecks) { rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, !isValueSet(system), I18nConstants.TERMINOLOGY_TX_SYSTEM_VALUESET2, system); try { - if (checkCode(errors, element, path, code, system, display, checkDisplay, stack)) + if (checkCode(errors, element, path, code, system, version, display, checkDisplay, stack)) if (theElementCntext != null && theElementCntext.hasBinding()) { ElementDefinitionBindingComponent binding = theElementCntext.getBinding(); if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, I18nConstants.TERMINOLOGY_TX_BINDING_MISSING2, path)) { @@ -1473,7 +1476,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(maxVSUrl))) { try { long t = System.nanoTime(); - ValidationResult vr = checkCodeOnServer(stack, valueset, value, new ValidationOptions(stack.getWorkingLang())); + ValidationResult vr = checkCodeOnServer(stack, valueset, value, baseOptions.setLanguage(stack.getWorkingLang())); timeTracker.tx(t, "vc "+value); if (!vr.isOk()) { if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) @@ -1506,19 +1509,20 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private void checkCoding(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept, boolean checkDisplay, NodeStack stack) { String code = element.getNamedChildValue("code"); String system = element.getNamedChildValue("system"); + String version = element.getNamedChildValue("version"); String display = element.getNamedChildValue("display"); - checkCodedElement(errors, path, element, profile, theElementCntext, inCodeableConcept, checkDisplay, stack, code, system, display); + checkCodedElement(errors, path, element, profile, theElementCntext, inCodeableConcept, checkDisplay, stack, code, system, version, display); } private void checkCodedElement(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept, boolean checkDisplay, NodeStack stack, - String theCode, String theSystem, String theDisplay) { + String theCode, String theSystem, String theVersion, String theDisplay) { rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isAbsolute(theSystem), I18nConstants.TERMINOLOGY_TX_SYSTEM_RELATIVE); warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, Utilities.noString(theCode) || !Utilities.noString(theSystem), I18nConstants.TERMINOLOGY_TX_SYSTEM_NO_CODE); if (theSystem != null && theCode != null && !noTerminologyChecks) { rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, !isValueSet(theSystem), I18nConstants.TERMINOLOGY_TX_SYSTEM_VALUESET2, theSystem); try { - if (checkCode(errors, element, path, theCode, theSystem, theDisplay, checkDisplay, stack)) + if (checkCode(errors, element, path, theCode, theSystem, theVersion, theDisplay, checkDisplay, stack)) if (theElementCntext != null && theElementCntext.hasBinding()) { ElementDefinitionBindingComponent binding = theElementCntext.getBinding(); if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, I18nConstants.TERMINOLOGY_TX_BINDING_MISSING2, path)) { @@ -1633,7 +1637,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } // two questions // 1. can this extension be used here? - checkExtensionContext(errors, resource, container, ex, containerStack, hostContext); + checkExtensionContext(errors, resource, container, ex, containerStack, hostContext, isModifier); if (isModifier) rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", ex.getSnapshot().getElement().get(0).getIsModifier(), I18nConstants.EXTENSION_EXT_MODIFIER_Y, url); @@ -1697,7 +1701,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return res; } - private boolean checkExtensionContext(List errors, Element resource, Element container, StructureDefinition definition, NodeStack stack, ValidatorHostContext hostContext) { + private boolean checkExtensionContext(List errors, Element resource, Element container, StructureDefinition definition, NodeStack stack, ValidatorHostContext hostContext, boolean modifier) { String extUrl = definition.getUrl(); boolean ok = false; CommaSeparatedStringBuilder contexts = new CommaSeparatedStringBuilder(); @@ -1774,9 +1778,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } if (!ok) { if (definition.hasUserData(XVerExtensionManager.XVER_EXT_MARKER)) { - warning(errors, IssueType.STRUCTURE, container.line(), container.col(), stack.getLiteralPath(), false, I18nConstants.EXTENSION_EXT_CONTEXT_WRONG_XVER, extUrl, contexts.toString(), plist.toString()); + warning(errors, IssueType.STRUCTURE, container.line(), container.col(), stack.getLiteralPath(), false, + modifier ? I18nConstants.EXTENSION_EXTM_CONTEXT_WRONG_XVER : I18nConstants.EXTENSION_EXTP_CONTEXT_WRONG_XVER, extUrl, contexts.toString(), plist.toString()); } else { - rule(errors, IssueType.STRUCTURE, container.line(), container.col(), stack.getLiteralPath(), false, I18nConstants.EXTENSION_EXT_CONTEXT_WRONG, extUrl, contexts.toString(), plist.toString()); + rule(errors, IssueType.STRUCTURE, container.line(), container.col(), stack.getLiteralPath(), false, + modifier ? I18nConstants.EXTENSION_EXTP_CONTEXT_WRONG : I18nConstants.EXTENSION_EXTM_CONTEXT_WRONG, extUrl, contexts.toString(), plist.toString()); } return false; } else { @@ -2019,23 +2025,27 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()); if (type.equals("oid")) { - if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("urn:oid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_OID_START)) - rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isOid(url.substring(8)), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_OID_VALID); + rule(errors, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("urn:oid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_OID_START); } if (type.equals("uuid")) { rule(errors, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("urn:uuid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_UUID_STRAT); - try { - UUID.fromString(url.substring(8)); - } catch (Exception ex) { - rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_UUID_VAID, ex.getMessage()); - } } if (type.equals("canonical")) { rule(errors, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("#") || Utilities.isAbsoluteUrl(url), I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_ABSOLUTE, url); } + if (url != null && url.startsWith("urn:uuid:")) { + rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isValidUUID(url.substring(9)), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_UUID_VAID); + } + if (url != null && url.startsWith("urn:oid:")) { + rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isOid(url.substring(8)), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_OID_VALID); + } + if (isCanonicalURLElement(e)) { - // for now, no validation. Need to think about authority. + // we get to here if this is a defining canonical URL (e.g. CodeSystem.url) + // the URL must be an IRI if present + rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isAbsoluteUrl(url), + node.isContained() ? I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_CONTAINED : I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_ABSOLUTE, url); } else { // now, do we check the URI target? if (fetcher != null && !type.equals("uuid")) { @@ -2484,7 +2494,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat long t = System.nanoTime(); ValidationResult vr = null; if (binding.getStrength() != BindingStrength.EXAMPLE) { - ValidationOptions options = new ValidationOptions(stack.getWorkingLang()).guessSystem(); + ValidationOptions options = baseOptions.setLanguage(stack.getWorkingLang()).guessSystem(); vr = checkCodeOnServer(stack, vs, value, options); } timeTracker.tx(t, "vc "+value+""); @@ -2543,7 +2553,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } if (system != null || code != null ) { - checkCodedElement(theErrors, thePath, element, theProfile, definition, false, false, theStack, code, system, unit); + checkCodedElement(theErrors, thePath, element, theProfile, definition, false, false, theStack, code, system, null, unit); } if (code != null && "http://unitsofmeasure.org".equals(system)) { @@ -4188,7 +4198,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } else if (element.getType().equals("CapabilityStatement")) { validateCapabilityStatement(errors, element, stack); } else if (element.getType().equals("CodeSystem")) { - new CodeSystemValidator(context, timeTracker, xverManager).validateCodeSystem(errors, element, stack, new ValidationOptions(stack.getWorkingLang())); + new CodeSystemValidator(context, timeTracker, xverManager).validateCodeSystem(errors, element, stack, baseOptions.setLanguage(stack.getWorkingLang())); } else if (element.getType().equals("SearchParameter")) { new SearchParameterValidator(context, timeTracker, fpe, xverManager).validateSearchParameter(errors, element, stack); } else if (element.getType().equals("StructureDefinition")) { @@ -4311,6 +4321,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat idstatus = IdStatus.OPTIONAL; break; case CONTAINED: + stack.setContained(true); idstatus = IdStatus.REQUIRED; break; case PARAMETER: @@ -4405,7 +4416,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } if (definition.getPath().equals("StructureDefinition.snapshot")) { - // work around a known issue in the spec, that idsa are duplicated in snapshot and differential + // work around a known issue in the spec, that ids are duplicated in snapshot and differential stack.resetIds(); } @@ -4993,6 +5004,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } else { if (nameMatches(ei.getName(), tail(ed.getPath()))) try { +// System.out.println("match slices for "+stack.getLiteralPath()+": "+slicer.getId()+" = "+slicingSummary(slicer.getSlicing())); match = sliceMatches(hostContext, ei.getElement(), ei.getPath(), slicer, ed, profile, errors, sliceInfo, stack, profile); if (match) { ei.slice = slicer; @@ -5026,6 +5038,22 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return unsupportedSlicing; } + private String slicingSummary(ElementDefinitionSlicingComponent slicing) { + StringBuilder b = new StringBuilder(); + b.append('['); + boolean first = true; + for (ElementDefinitionSlicingDiscriminatorComponent t : slicing.getDiscriminator()) { + if (first) first = false; else b.append(","); + b.append(t.getType().toCode()); + b.append(":"); + b.append(t.getPath()); + } + b.append(']'); + b.append(slicing.getOrdered() ? ";ordered" : ""); + b.append(slicing.getRules().toString()); + return b.toString(); + } + private ElementDefinition getElementByTail(StructureDefinition p, String tail) throws DefinitionException { if (tail == null) return p.getSnapshot().getElement().get(0); @@ -5349,6 +5377,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if ("probability is decimal implies (probability as decimal) <= 100".equals(expr)) { return "probablility.empty() or ((probability is decimal) implies ((probability as decimal) <= 100))"; } + if ("enableWhen.count() > 2 implies enableBehavior.exists()".equals(expr)) { return "enableWhen.count() >= 2 implies enableBehavior.exists()"; } @@ -5451,23 +5480,23 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } // no delay on this one? - public ValidationResult checkCodeOnServer(NodeStack stack, String code, String system, String display, boolean checkDisplay) { - return context.validateCode(new ValidationOptions(stack.getWorkingLang()), system, code, checkDisplay ? display : null); + public ValidationResult checkCodeOnServer(NodeStack stack, String code, String system, String version, String display, boolean checkDisplay) { + return context.validateCode(baseOptions.setLanguage(stack.getWorkingLang()), system, version, code, checkDisplay ? display : null); } public ValidationResult checkCodeOnServer(NodeStack stack, ValueSet valueset, Coding c, boolean checkMembership) { if (checkMembership) { - return context.validateCode(new ValidationOptions(stack.getWorkingLang()).checkValueSetOnly(), c, valueset); + return context.validateCode(baseOptions.setLanguage(stack.getWorkingLang()).checkValueSetOnly(), c, valueset); } else { - return context.validateCode(new ValidationOptions(stack.getWorkingLang()).noCheckValueSetMembership(), c, valueset); + return context.validateCode(baseOptions.setLanguage(stack.getWorkingLang()).noCheckValueSetMembership(), c, valueset); } } public ValidationResult checkCodeOnServer(NodeStack stack, ValueSet valueset, CodeableConcept cc, boolean vsOnly) { if (vsOnly) { - return context.validateCode(new ValidationOptions(stack.getWorkingLang()).checkValueSetOnly(), cc, valueset); + return context.validateCode(baseOptions.setLanguage(stack.getWorkingLang()).checkValueSetOnly(), cc, valueset); } else { - return context.validateCode(new ValidationOptions(stack.getWorkingLang()), cc, valueset); + return context.validateCode(baseOptions.setLanguage(stack.getWorkingLang()), cc, valueset); } } @@ -5534,4 +5563,12 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat this.wantCheckSnapshotUnchanged = wantCheckSnapshotUnchanged; } + public ValidationOptions getBaseOptions() { + return baseOptions; + } + + public void setBaseOptions(ValidationOptions baseOptions) { + this.baseOptions = baseOptions; + } + } \ No newline at end of file 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 48974d24c..04896f54a 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 @@ -77,7 +77,7 @@ public class CodeSystemValidator extends BaseValidator { private void validateSupplementConcept(List errors, Element concept, NodeStack stack, String supp, ValidationOptions options) { String code = concept.getChildValue("code"); if (!Utilities.noString(code)) { - org.hl7.fhir.r5.context.IWorkerContext.ValidationResult res = context.validateCode(options, supp, code, null); + org.hl7.fhir.r5.context.IWorkerContext.ValidationResult res = context.validateCode(options, systemFromCanonical(supp), versionFromCanonical(supp), code, null); rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), res.isOk(), I18nConstants.CODESYSTEM_CS_SUPP_INVALID_CODE, supp, code); } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/NodeStack.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/NodeStack.java index e753da6d7..f1d0cc8f4 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/NodeStack.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/NodeStack.java @@ -25,6 +25,7 @@ public class NodeStack { private String workingLang; private Map ids; private boolean resetPoint = false; + private boolean contained = false; public NodeStack(IWorkerContext context) { this.context = context; @@ -101,6 +102,7 @@ public class NodeStack { res.workingLang = this.workingLang; res.element = element; res.definition = definition; + res.contained = contained; res.literalPath = getLiteralPath() + sep + element.getName(); if (count > -1) res.literalPath = res.literalPath + "[" + Integer.toString(count) + "]"; @@ -195,5 +197,13 @@ public class NodeStack { } } + public boolean isContained() { + return contained; + } + + public void setContained(boolean contained) { + this.contained = contained; + } + } \ No newline at end of file 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 5dbdb0028..482719191 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 @@ -173,6 +173,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe val.setWantCheckSnapshotUnchanged(true); val.getContext().setClientRetryCount(4); val.setDebug(false); + if (content.has("fetcher") && "standalone".equals(JSONUtil.str(content, "fetcher"))) { val.setFetcher(vCurr); vCurr.setFetcher(new StandAloneValidatorFetcher(vCurr.getPcm(), vCurr.getContext(), vCurr)); @@ -188,6 +189,11 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe val.setValidationLanguage(content.get("language").getAsString()); else val.setValidationLanguage(null); + if (content.has("default-version")) { + val.setBaseOptions(val.getBaseOptions().setVersionFlexible(content.get("default-version").getAsBoolean())); + } else { + val.setBaseOptions(val.getBaseOptions().setVersionFlexible(false)); + } if (content.has("packages")) { for (JsonElement e : content.getAsJsonArray("packages")) { String n = e.getAsString(); diff --git a/pom.xml b/pom.xml index cb9753b60..340d9d98f 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 5.1.0 - 1.1.69 + 1.1.70-SNAPSHOT 5.7.1 1.7.1 3.0.0-M4