diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/OIDBasedValueSetImporter.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/OIDBasedValueSetImporter.java index 9a57654c8..0d321de76 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/OIDBasedValueSetImporter.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/OIDBasedValueSetImporter.java @@ -17,7 +17,7 @@ public class OIDBasedValueSetImporter { protected void init() throws FHIRException, IOException { FilesystemPackageCacheManager pcm = new FilesystemPackageCacheManager(org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager.FilesystemPackageCacheMode.USER); - NpmPackage npm = pcm.loadPackage("hl7.fhir.r5.core", "current"); + NpmPackage npm = pcm.loadPackage("hl7.fhir.r5.core", "5.0.0"); SimpleWorkerContext context = new SimpleWorkerContext.SimpleWorkerContextBuilder().withAllowLoadingDuplicates(true).fromPackage(npm); context.loadFromPackage(pcm.loadPackage("hl7.terminology"), null); this.context = context; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfilePathProcessor.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfilePathProcessor.java index ea546a25b..e216982f7 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfilePathProcessor.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfilePathProcessor.java @@ -304,7 +304,7 @@ public class ProfilePathProcessor { if (profileUtilities.hasInnerDiffMatches(getDifferential(), currentBasePath, cursors.diffCursor, getDiffLimit(), cursors.base.getElement(), false)) { if (baseHasChildren(cursors.base, currentBase)) { // not a new type here if (cursors.diffCursor == 0) { - throw new DefinitionException("Error: The profile has slicing at the root, which is illegal"); + throw new DefinitionException("Error: The profile has slicing at the root ('"+currentBase.getPath()+"'), which is illegal"); } else { throw new Error("This situation is not yet handled (constrain slicing to 1..1 and fix base slice for inline structure - please report issue to grahame@fhir.org along with a test case that reproduces this error (@ " + currentBasePath + " | " + currentBase.getPath() + ")"); } @@ -766,6 +766,7 @@ public class ProfilePathProcessor { outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource())); profileUtilities.updateFromBase(outcome, currentBase, getSourceStructureDefinition().getUrl()); profileUtilities.updateConstraintSources(outcome, getSourceStructureDefinition().getUrl()); + profileUtilities.updateFromObligationProfiles(outcome); profileUtilities.updateURLs(url, webUrl, outcome); profileUtilities.markDerived(outcome); if (cursors.resultPathBase == null) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java index 0b447a699..3c7ac187d 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java @@ -61,6 +61,7 @@ import org.hl7.fhir.r5.model.Element; import org.hl7.fhir.r5.model.ElementDefinition; import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType; import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBaseComponent; +import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingAdditionalComponent; import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent; import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent; import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionExampleComponent; @@ -349,7 +350,8 @@ public class ProfileUtilities extends TranslatingUtilities { private AllowUnknownProfile allowUnknownProfile = AllowUnknownProfile.ALL_TYPES; private MappingMergeModeOption mappingMergeMode = MappingMergeModeOption.APPEND; private boolean forPublication; - + private List obligationProfiles = new ArrayList<>(); + public ProfileUtilities(IWorkerContext context, List messages, ProfileKnowledgeProvider pkp, FHIRPathEngine fpe) { super(); this.context = context; @@ -627,6 +629,8 @@ public class ProfileUtilities extends TranslatingUtilities { checkDifferentialBaseType(derived); copyInheritedExtensions(base, derived); + + findInheritedObligationProfiles(derived); // so we have two lists - the base list, and the differential list // the differential list is only allowed to include things that are in the base list, but // is allowed to include them multiple times - thereby slicing them @@ -853,6 +857,17 @@ public class ProfileUtilities extends TranslatingUtilities { } } + private void findInheritedObligationProfiles(StructureDefinition derived) { + for (Extension ext : derived.getExtensionsByUrl(ToolingExtensions.EXT_OBLIGATION_INHERITS)) { + StructureDefinition op = context.fetchResource(StructureDefinition.class, ext.getValueCanonicalType().primitiveValue()); + if (op != null && ToolingExtensions.readBoolExtension(op, ToolingExtensions.EXT_OBLIGATION_PROFILE_FLAG)) { + if (derived.getBaseDefinition().equals(op.getBaseDefinition())) { + obligationProfiles.add(op); + } + } + } + } + private void handleError(String url, String msg) { if (exception) throw new DefinitionException(msg); @@ -2065,6 +2080,63 @@ public class ProfileUtilities extends TranslatingUtilities { return true; } + + public void updateFromObligationProfiles(ElementDefinition base) { + List obligationProfileElements = new ArrayList<>(); + for (StructureDefinition sd : obligationProfiles) { + ElementDefinition ed = sd.getSnapshot().getElementById(base.getId()); + if (ed != null) { + obligationProfileElements.add(ed); + } + } + for (ElementDefinition ed : obligationProfileElements) { + for (Extension ext : ed.getExtension()) { + if (ToolingExtensions.EXT_OBLIGATION.equals(ext.getUrl())) { + base.getExtension().add(ext.copy()); + } + } + } + boolean hasMustSupport = false; + for (ElementDefinition ed : obligationProfileElements) { + hasMustSupport = hasMustSupport || ed.hasMustSupportElement(); + } + if (hasMustSupport) { + for (ElementDefinition ed : obligationProfileElements) { + mergeExtensions(base.getMustSupportElement(), ed.getMustSupportElement()); + if (ed.getMustSupport()) { + base.setMustSupport(true); + } + } + } + boolean hasBinding = false; + for (ElementDefinition ed : obligationProfileElements) { + hasBinding = hasBinding || ed.hasBinding(); + } + if (hasBinding) { + ElementDefinitionBindingComponent binding = base.getBinding(); + for (ElementDefinition ed : obligationProfileElements) { + for (Extension ext : ed.getBinding().getExtension()) { + if (ToolingExtensions.EXT_BINDING_ADDITIONAL.equals(ext.getUrl())) { + String p = ext.getExtensionString("purpose"); + if (!Utilities.existsInList(p, "maximum", "required", "extensible")) { + if (!binding.hasExtension(ext)) { + binding.getExtension().add(ext.copy()); + } + } + } + } + for (ElementDefinitionBindingAdditionalComponent ab : ed.getBinding().getAdditional()) { + if (!Utilities.existsInList(ab.getPurpose().toCode(), "maximum", "required", "extensible")) { + if (!binding.hasAdditional(ab)) { + binding.getAdditional().add(ab.copy()); + } + } + } + } + } + } + + protected void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn, boolean trimDifferential, String purl, StructureDefinition srcSD, StructureDefinition derivedSrc) throws DefinitionException, FHIRException { source.setUserData(UD_GENERATED_IN_SNAPSHOT, dest); // we start with a clone of the base profile ('dest') and we copy from the profile ('source') @@ -2073,11 +2145,29 @@ public class ProfileUtilities extends TranslatingUtilities { ElementDefinition derived = source; derived.setUserData(UD_DERIVATION_POINTER, base); boolean isExtension = checkExtensionDoco(base); - + List obligationProfileElements = new ArrayList<>(); + for (StructureDefinition sd : obligationProfiles) { + ElementDefinition ed = sd.getSnapshot().getElementById(base.getId()); + if (ed != null) { + obligationProfileElements.add(ed); + } + } for (Extension ext : source.getExtension()) { if (Utilities.existsInList(ext.getUrl(), INHERITED_ED_URLS) && !dest.hasExtension(ext.getUrl())) { dest.getExtension().add(ext.copy()); + } + } + for (Extension ext : source.getExtension()) { + if (ToolingExtensions.EXT_OBLIGATION.equals(ext.getUrl())) { + dest.getExtension().add(ext.copy()); + } + } + for (ElementDefinition ed : obligationProfileElements) { + for (Extension ext : ed.getExtension()) { + if (ToolingExtensions.EXT_OBLIGATION.equals(ext.getUrl())) { + dest.getExtension().add(ext.copy()); + } } } // Before applying changes, apply them to what's in the profile @@ -2282,12 +2372,23 @@ public class ProfileUtilities extends TranslatingUtilities { // todo: what to do about conditions? // condition : id 0..* - if (derived.hasMustSupportElement()) { - if (!(base.hasMustSupportElement() && Base.compareDeep(derived.getMustSupportElement(), base.getMustSupportElement(), false))) { + boolean hasMustSupport = derived.hasMustSupportElement(); + for (ElementDefinition ed : obligationProfileElements) { + hasMustSupport = hasMustSupport || ed.hasMustSupportElement(); + } + if (hasMustSupport) { + BooleanType mse = derived.getMustSupportElement().copy(); + for (ElementDefinition ed : obligationProfileElements) { + mergeExtensions(mse, ed.getMustSupportElement()); + if (ed.getMustSupport()) { + mse.setValue(true); + } + } + if (!(base.hasMustSupportElement() && Base.compareDeep(base.getMustSupportElement(), mse, false))) { if (base.hasMustSupport() && base.getMustSupport() && !derived.getMustSupport()) { messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Illegal constraint [must-support = false] when [must-support = true] in the base profile", ValidationMessage.IssueSeverity.ERROR)); } - base.setMustSupportElement(derived.getMustSupportElement().copy()); + base.setMustSupportElement(mse); } else if (trimDifferential) derived.setMustSupportElement(null); else @@ -2324,7 +2425,32 @@ public class ProfileUtilities extends TranslatingUtilities { derived.getIsModifierReasonElement().setUserData(UD_DERIVATION_EQUALS, true); } - if (derived.hasBinding()) { + boolean hasBinding = derived.hasBinding(); + for (ElementDefinition ed : obligationProfileElements) { + hasBinding = hasBinding || ed.hasBinding(); + } + if (hasBinding) { + ElementDefinitionBindingComponent binding = derived.getBinding(); + for (ElementDefinition ed : obligationProfileElements) { + for (Extension ext : ed.getBinding().getExtension()) { + if (ToolingExtensions.EXT_BINDING_ADDITIONAL.equals(ext.getUrl())) { + String p = ext.getExtensionString("purpose"); + if (!Utilities.existsInList(p, "maximum", "required", "extensible")) { + if (!binding.hasExtension(ext)) { + binding.getExtension().add(ext.copy()); + } + } + } + } + for (ElementDefinitionBindingAdditionalComponent ab : ed.getBinding().getAdditional()) { + if (!Utilities.existsInList(ab.getPurpose().toCode(), "maximum", "required", "extensible")) { + if (binding.hasAdditional(ab)) { + binding.getAdditional().add(ab.copy()); + } + } + } + } + if (!base.hasBinding() || !Base.compareDeep(derived.getBinding(), base.getBinding(), false)) { if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && derived.getBinding().getStrength() != BindingStrength.REQUIRED) messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "illegal attempt to change the binding on "+derived.getPath()+" from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode(), ValidationMessage.IssueSeverity.ERROR)); @@ -2365,7 +2491,7 @@ public class ProfileUtilities extends TranslatingUtilities { if (d.hasValueSet()) { nb.setValueSet(d.getValueSet()); } - base.setBinding(nb); + base.setBinding(nb); } else if (trimDifferential) derived.setBinding(null); else @@ -2462,6 +2588,10 @@ public class ProfileUtilities extends TranslatingUtilities { //updateURLs(url, webUrl, dest); } + private void mergeExtensions(Element tgt, Element src) { + tgt.getExtension().addAll(src.getExtension()); + } + private void addMappings(List destination, List source) { for (ElementDefinitionMappingComponent s : source) { boolean found = false; 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 9babd89fd..c244a8aa0 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 @@ -322,6 +322,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte locator = other.locator; userAgent = other.userAgent; tcc.copy(other.tcc); + cachingAllowed = other.cachingAllowed; } } @@ -1050,9 +1051,9 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte codeSystemsUsed.add(code.getSystem()); } - final CacheToken cacheToken = txCache != null ? txCache.generateValidationToken(options, code, vs, expParameters) : null; + final CacheToken cacheToken = cachingAllowed && txCache != null ? txCache.generateValidationToken(options, code, vs, expParameters) : null; ValidationResult res = null; - if (txCache != null) { + if (cachingAllowed && txCache != null) { res = txCache.getValidation(cacheToken); } if (res != null) { @@ -1072,7 +1073,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte vsc.setThrowToServer(options.isUseServer() && tcc.getClient() != null); if (!ValueSetUtilities.isServerSide(code.getSystem())) { res = vsc.validateCode(path, code); - if (txCache != null) { + if (txCache != null && cachingAllowed) { txCache.cacheValidation(cacheToken, res, TerminologyCache.TRANSIENT); } return res; @@ -1107,8 +1108,8 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte if (noTerminologyServer) { return new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE, issues); } - String csumm = txCache != null ? txCache.summary(code) : null; - if (txCache != null) { + String csumm =cachingAllowed && txCache != null ? txCache.summary(code) : null; + if (cachingAllowed && txCache != null) { txLog("$validate "+csumm+" for "+ txCache.summary(vs)); } else { txLog("$validate "+csumm+" before cache exists"); @@ -1123,7 +1124,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte res.setDiagnostics("Local Error: "+localError.trim()+". Server Error: "+res.getMessage()); } updateUnsupportedCodeSystems(res, code, codeKey); - if (txCache != null) { // we never cache unsupported code systems - we always keep trying (but only once per run) + if (cachingAllowed && txCache != null) { // we never cache unsupported code systems - we always keep trying (but only once per run) txCache.cacheValidation(cacheToken, res, TerminologyCache.PERMANENT); } return res; @@ -1204,9 +1205,12 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte @Override public ValidationResult validateCode(ValidationOptions options, CodeableConcept code, ValueSet vs) { CacheToken cacheToken = txCache.generateValidationToken(options, code, vs, expParameters); - ValidationResult res = txCache.getValidation(cacheToken); - if (res != null) { - return res; + ValidationResult res = null; + if (cachingAllowed) { + res = txCache.getValidation(cacheToken); + if (res != null) { + return res; + } } for (Coding c : code.getCoding()) { if (c.hasSystem()) { @@ -1222,7 +1226,9 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte vsc.setUnknownSystems(unknownSystems); vsc.setThrowToServer(options.isUseServer() && tcc.getClient() != null); res = vsc.validateCode("CodeableConcept", code); - txCache.cacheValidation(cacheToken, res, TerminologyCache.TRANSIENT); + if (cachingAllowed) { + txCache.cacheValidation(cacheToken, res, TerminologyCache.TRANSIENT); + } return res; } catch (Exception e) { e.printStackTrace(); @@ -1247,7 +1253,9 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte } catch (Exception e) { res = new ValidationResult(IssueSeverity.ERROR, e.getMessage() == null ? e.getClass().getName() : e.getMessage(), null).setTxLink(txLog == null ? null : txLog.getLastId()); } - txCache.cacheValidation(cacheToken, res, TerminologyCache.PERMANENT); + if (cachingAllowed) { + txCache.cacheValidation(cacheToken, res, TerminologyCache.PERMANENT); + } return res; } @@ -1919,6 +1927,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte protected IWorkerContextManager.IPackageLoadingTracker packageTracker; private boolean forPublication; + private boolean cachingAllowed = true; @Override public Resource fetchResourceById(String type, String uri) { @@ -2478,4 +2487,12 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte forPublication = value; } + public boolean isCachingAllowed() { + return cachingAllowed; + } + + public void setCachingAllowed(boolean cachingAllowed) { + this.cachingAllowed = cachingAllowed; + } + } 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 b54fd07a9..f5d794cc9 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 @@ -304,6 +304,17 @@ public interface IWorkerContext { return this; } + public String unknownSystems() { + if (unknownSystems == null) { + return null; + } + if (unknownSystems.size() == 1) { + return unknownSystems.iterator().next(); + } else { + return String.join(",", unknownSystems); + } + } + } public class CodingValidationRequest { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/XmlParser.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/XmlParser.java index c7ec10769..32b49d22c 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/XmlParser.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/XmlParser.java @@ -441,8 +441,9 @@ public class XmlParser extends ParserBase { parseChildren(npath, (org.w3c.dom.Element) child, n); } } - } else + } else { logError(ValidationMessage.NO_RULE_DATE, line(child, false), col(child, false), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNDEFINED_ELEMENT_, child.getLocalName(), path), IssueSeverity.ERROR); + } } else if (child.getNodeType() == Node.CDATA_SECTION_NODE){ logError(ValidationMessage.NO_RULE_DATE, line(child, false), col(child, false), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.CDATA_IS_NOT_ALLOWED), IssueSeverity.ERROR); } else if (!Utilities.existsInList(child.getNodeType(), 3, 8)) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Element.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Element.java index 106750506..955a9cd90 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Element.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Element.java @@ -457,6 +457,17 @@ public abstract class Element extends Base implements IBaseHasExtensions, IBaseE ToolingExtensions.setStandardsStatus(this, status, null); } + public boolean hasExtension(Extension ext) { + if (hasExtension()) { + for (Extension t : getExtension()) { + if (Base.compareDeep(t, ext, false)) { + return true; + } + } + } + return false; + } + // end addition } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ElementDefinition.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ElementDefinition.java index a9dc7ec36..13b3f316d 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ElementDefinition.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ElementDefinition.java @@ -4919,6 +4919,17 @@ public boolean hasTarget() { } + public boolean hasAdditional(ElementDefinitionBindingAdditionalComponent ab) { + if (hasAdditional()) { + for (ElementDefinitionBindingAdditionalComponent t : getAdditional()) { + if (Base.compareDeep(t, ab, false)) { + return true; + } + } + } + return false; + } + } @Block() diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetUtilities.java index c6e1f23e6..7edcff541 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetUtilities.java @@ -385,5 +385,21 @@ public class ValueSetUtilities { return code; } + public static int countExpansion(ValueSet valueset) { + int i = valueset.getExpansion().getContains().size(); + for (ValueSetExpansionContainsComponent t : valueset.getExpansion().getContains()) { + i = i + countExpansion(t); + } + return i; + } + + private static int countExpansion(ValueSetExpansionContainsComponent c) { + int i = c.getContains().size(); + for (ValueSetExpansionContainsComponent t : c.getContains()) { + i = i + countExpansion(t); + } + return i; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/expansion/ValueSetExpansionOutcome.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/expansion/ValueSetExpansionOutcome.java index 79a5cd50d..8625a101f 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/expansion/ValueSetExpansionOutcome.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/expansion/ValueSetExpansionOutcome.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.List; import org.hl7.fhir.r5.model.ValueSet; +import org.hl7.fhir.r5.terminologies.ValueSetUtilities; import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass; /** @@ -74,4 +75,10 @@ public class ValueSetExpansionOutcome { public boolean isOk() { return (allErrors.isEmpty() || (allErrors.size() == 1 && allErrors.get(0) == null)) && error == null; } + public int count() { + if (valueset == null) { + return 0; + } + return ValueSetUtilities.countExpansion(valueset); + } } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValueSetValidator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValueSetValidator.java index c9c9e5e70..6e32be61f 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValueSetValidator.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValueSetValidator.java @@ -493,6 +493,7 @@ public class ValueSetValidator { res.getIssues().addAll(makeIssue(IssueSeverity.WARNING, IssueType.NOTFOUND, path, m)); res.setUnknownSystems(unknownSystems); res.setSeverity(IssueSeverity.ERROR); // back patching for display logic issue + res.setErrorClass(TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED); } else if (!inExpansion && !inInclude) { // if (!info.getIssues().isEmpty()) { // res.setMessage("Not in value set "+valueset.getUrl()+": "+info.summary()).setSeverity(IssueSeverity.ERROR); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java index 21285e639..5f4f4e4b6 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java @@ -1067,4 +1067,7 @@ public class ToolingExtensions { return res; } + public static final String EXT_OBLIGATION_PROFILE_FLAG = "http://hl7.org/fhir/tools/StructureDefinition/obligation-profile"; + public static final String EXT_OBLIGATION_INHERITS = "http://hl7.org/fhir/tools/StructureDefinition/inherit-obligations"; + } \ 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 c2439259f..7c15d616d 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 @@ -439,6 +439,7 @@ public class I18nConstants { public static final String TERMINOLOGY_TX_NOVALID_13 = "Terminology_TX_NoValid_13"; public static final String TERMINOLOGY_TX_NOVALID_14 = "Terminology_TX_NoValid_14"; public static final String TERMINOLOGY_TX_NOVALID_15 = "Terminology_TX_NoValid_15"; + public static final String TERMINOLOGY_TX_NOVALID_15A = "Terminology_TX_NoValid_15A"; public static final String TERMINOLOGY_TX_NOVALID_16 = "Terminology_TX_NoValid_16"; public static final String TERMINOLOGY_TX_NOVALID_17 = "Terminology_TX_NoValid_17"; public static final String TERMINOLOGY_TX_NOVALID_18 = "Terminology_TX_NoValid_18"; @@ -870,6 +871,19 @@ public class I18nConstants { public static final String ATTEMPT_TO_CHANGE_SLICING = "ATTEMPT_TO_CHANGE_SLICING"; public static final String REPEAT_SLICING_IGNORED = "REPEAT_SLICING_IGNORED"; public static final String SD_ELEMENT_NOT_IN_CONSTRAINT = "SD_ELEMENT_NOT_IN_CONSTRAINT"; + public static final String SD_OBGLIGATION_PROFILE_UKNOWN = "SD_OBGLIGATION_PROFILE_UKNOWN"; + public static final String SD_OBGLIGATION_PROFILE_DERIVATION = "SD_OBGLIGATION_PROFILE_DERIVATION"; + public static final String SD_OBGLIGATION_PROFILE_UNMATCHED = "SD_OBGLIGATION_PROFILE_UNMATCHED"; + public static final String SD_OBGLIGATION_PROFILE_PATH_WRONG = "SD_OBGLIGATION_PROFILE_PATH_WRONG"; + public static final String SD_OBGLIGATION_PROFILE_ILLEGAL = "SD_OBGLIGATION_PROFILE_ILLEGAL"; + public static final String SD_OBGLIGATION_PROFILE_ILLEGAL_ON_BINDING = "SD_OBGLIGATION_PROFILE_ILLEGAL_ON_BINDING"; + public static final String SD_OBGLIGATION_PROFILE_INVALID_BINDING_CODE = "SD_OBGLIGATION_PROFILE_INVALID_BINDING_CODE"; + public static final String SD_OBGLIGATION_PROFILE_INVALID_BINDING_STRENGTH = "SD_OBGLIGATION_PROFILE_INVALID_BINDING_STRENGTH"; + public static final String SD_OBGLIGATION_PROFILE_ILLEGAL_BINDING = "SD_OBGLIGATION_PROFILE_ILLEGAL_BINDING"; + public static final String SD_OBGLIGATION_INHERITS_PROFILE_NO_TARGET = "SD_OBGLIGATION_INHERITS_PROFILE_NO_TARGET"; + public static final String SD_OBGLIGATION_INHERITS_PROFILE_TARGET_NOT_FOUND = "SD_OBGLIGATION_INHERITS_PROFILE_TARGET_NOT_FOUND"; + public static final String SD_OBGLIGATION_INHERITS_PROFILE_NOT_RIGHT_TYPE = "SD_OBGLIGATION_INHERITS_PROFILE_NOT_RIGHT_TYPE"; + public static final String SD_OBGLIGATION_INHERITS_PROFILE_NOT_RIGHT_BASE = "SD_OBGLIGATION_INHERITS_PROFILE_NOT_RIGHT_BASE"; } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonNumber.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonNumber.java index 6bf55ad87..bb552e634 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonNumber.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonNumber.java @@ -14,6 +14,10 @@ public class JsonNumber extends JsonPrimitive { this.value = Integer.toString(value); } + public JsonNumber(long value) { + this.value = Long.toString(value); + } + private JsonNumber() { } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonObject.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonObject.java index d39bc6261..ff24e28c8 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonObject.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonObject.java @@ -66,6 +66,13 @@ public class JsonObject extends JsonElement { return add(name, new JsonNumber(value)); } + + public JsonObject add(String name, long value) throws JsonException { + check(name != null, "Name is null"); + return add(name, new JsonNumber(value)); + } + + public JsonObject set(String name, JsonElement value) throws JsonException { check(name != null, "Name is null"); check(value != null, "Value is null"); diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xml/XmlEscaper.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xml/XmlEscaper.java new file mode 100644 index 000000000..378642886 --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xml/XmlEscaper.java @@ -0,0 +1,76 @@ +package org.hl7.fhir.utilities.xml; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +public class XmlEscaper { + + private InputStream source; + private OutputStream target; + + + protected XmlEscaper(InputStream source, OutputStream target) { + super(); + this.source = source; + this.target = target; + } + + public static void convert(InputStream source, OutputStream target) throws IOException { + XmlEscaper self = new XmlEscaper(source, target); + self.process(); + } + + public static void convert(String source, String target) throws IOException { + convertAndClose(new FileInputStream(source), new FileOutputStream(target)); + } + + public static void convertAndClose(InputStream source, OutputStream target) throws IOException { + XmlEscaper self = new XmlEscaper(source, target); + self.process(); + source.close(); + target.close(); + } + + public InputStream getSource() { + return source; + } + public void setSource(InputStream source) { + this.source = source; + } + public OutputStream getTarget() { + return target; + } + public void setTarget(OutputStream target) { + this.target = target; + } + + public void process() throws IOException { + BufferedReader buffer = new BufferedReader(new InputStreamReader(source)); + int i = 0; + while ((i = buffer.read()) != -1) { + char c = (char) i; + if (c == '<') + write("<"); + else if (c == '>') + write(">"); + else if (c == '&') + write("&"); + else if (c == '"') + write("""); + else + target.write((byte) i); + } + } + + private void write(String s) throws IOException { + target.write(s.getBytes(StandardCharsets.UTF_8)); + } + +} + diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index 9bd8f16e8..8f9e00bc2 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -139,7 +139,7 @@ Terminology_PassThrough_TX_Message = {0} for ''{1}#{2}'' Terminology_TX_Binding_CantCheck = Binding by URI reference cannot be checked Terminology_TX_Binding_Missing = Binding for CodeableConcept {0} missing Terminology_TX_Binding_Missing2 = Binding for Coding {0} missing -Terminology_TX_Binding_NoServer = The value provided could not be validated in the absence of a terminology server +Terminology_TX_Binding_NoServer = The value provided ([{0}]) could not be validated in the absence of a terminology server Terminology_TX_Binding_NoSource = Binding for path {0} has no source, so can''t be checked Terminology_TX_Binding_NoSource2 = Binding has no source, so can''t be checked Terminology_TX_Code_NotValid = Code {0} is not a valid code in code system {1} @@ -168,6 +168,7 @@ Terminology_TX_NoValid_12 = The Coding provided ({2}) is not in the value set {0 Terminology_TX_NoValid_13 = The Coding provided ({2}) is not in the value set {0}, and a code should come from this value set unless it has no suitable code (note that the validator cannot judge what is suitable). {1} Terminology_TX_NoValid_14 = The Coding provided ({2}) is not in the value set {0}, and a code is recommended to come from this value set. {1} Terminology_TX_NoValid_15 = The value provided (''{0}'') could not be validated in the absence of a terminology server +Terminology_TX_NoValid_15A = The value provided (''{0}'') could not be validated because the code system {1} is not known Terminology_TX_NoValid_16 = The value provided (''{0}'') is not in the value set {1}, and a code is required from this value set){2} Terminology_TX_NoValid_17 = The value provided (''{0}'') is not in the value set {1}, and a code should come from this value set unless it has no suitable code (note that the validator cannot judge what is suitable) {2} Terminology_TX_NoValid_18 = The value provided (''{0}'') is not in the value set {1}, and a code is recommended to come from this value set){2} @@ -178,7 +179,7 @@ Terminology_TX_NoValid_5 = The Coding provided ({2}) is not in the value set {0} Terminology_TX_NoValid_6 = The Coding provided ({2}) is not in the value set {0}, and a code is recommended to come from this value set {1} Terminology_TX_NoValid_7 = None of the codes provided could be validated against the maximum value set {0}, (error = {2}) Terminology_TX_NoValid_8 = None of the codes provided are in the maximum value set {0}, and a code from this value set is required) (codes = {1}) -Terminology_TX_NoValid_9 = The code provided could not be validated against the maximum value set {0}, (error = {1}) +Terminology_TX_NoValid_9 = The code provided ({2}) could not be validated against the maximum value set {0}, (error = {1}) Terminology_TX_System_Invalid = Invalid System URI: {0} Terminology_TX_System_NotKnown = Code System URI ''{0}'' is unknown so the code cannot be validated Terminology_TX_System_Relative = Coding.system must be an absolute reference, not a local reference @@ -560,8 +561,8 @@ SEARCHPARAMETER_BASE_WRONG = The resource type {1} is not listed as a base in th SEARCHPARAMETER_TYPE_WRONG = The type {1} is different to the type {0} in the derivedFrom SearchParameter SEARCHPARAMETER_EXP_WRONG = The expression ''{2}'' is not compatible with the expression ''{1}'' in the derivedFrom SearchParameter {0}, and this likely indicates that the derivation relationship is not valid VALUESET_NO_SYSTEM_WARNING = No System specified, so Concepts and Filters can't be checked -VALUESET_INCLUDE_INVALID_CONCEPT_CODE = The code {1} is not valid in the system {0} -VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER = The code {2} is not valid in the system {0} version {1} +VALUESET_INCLUDE_INVALID_CONCEPT_CODE = The code ''{1}'' is not valid in the system {0} +VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER = The code ''{2}'' is not valid in the system {0} version {1} VALUESET_EXAMPLE_SYSTEM_HINT = Example System ''{0}'' specified, so Concepts and Filters can''t be checked VALUESET_EXAMPLE_SYSTEM_ERROR = Example System ''{0}'' specified, which is illegal. Concepts and Filters can''t be checked VALUESET_UNC_SYSTEM_WARNING = Unknown System ''{0}'' specified, so Concepts and Filters can''t be checked (Details: {1}) @@ -924,3 +925,17 @@ ED_PATH_WRONG_TYPE_MATCH = The path must be ''{0}'' not ''{1}'' when the type li ATTEMPT_TO_CHANGE_SLICING = The element at {0} defines the slicing {1} but then an element in the slicing {2} tries to redefine the slicing to {3} REPEAT_SLICING_IGNORED = The element at {0} defines the slicing but then an element in the slicing {2} repeats it, which is ignored SD_ELEMENT_NOT_IN_CONSTRAINT = The element definition for {1} has a property {0} which is not allowed in a profile +SD_OBGLIGATION_PROFILE_UKNOWN = The profile is marked as an obligation profile, but it's correctness cannot be checked since the base profile ''{0}'' is not known +SD_OBGLIGATION_PROFILE_DERIVATION = Only profiles that constrain another profile can be marked as an obligation profile +SD_OBGLIGATION_PROFILE_UNMATCHED = The element ''{0}'' has no equivalent in the profile ''{1}'' on which this Obligation Profile is based +SD_OBGLIGATION_PROFILE_PATH_WRONG = The element ''{0}'' path value of ''{1}'' doesn't match the base path ''{2}'' +SD_OBGLIGATION_PROFILE_ILLEGAL = The element ''{0}'' has a property ''{1}'' which is not allowed in Obligation profiles +SD_OBGLIGATION_PROFILE_ILLEGAL_ON_BINDING = The element ''{0}'' has a binding property ''{1}'' which is not allowed in Obligation profiles +SD_OBGLIGATION_PROFILE_ILLEGAL_BINDING = The element ''{0}'' has a binding when the base element does not, and this is not allowed in Obligation profiles +SD_OBGLIGATION_PROFILE_INVALID_BINDING_CODE = The element ''{0}'' has an additional binding purpose of ''{1}'' which is not allowed in Obligation profiles +SD_OBGLIGATION_PROFILE_INVALID_BINDING_STRENGTH =The element ''{0}'' has a different binding strength (''{1}'') from the base (''{2}'') which is not allowed in Obligation profiles +SD_OBGLIGATION_INHERITS_PROFILE_NO_TARGET = Unable to read a value from this extension +SD_OBGLIGATION_INHERITS_PROFILE_TARGET_NOT_FOUND = The profile ''{0}'' could not be found +SD_OBGLIGATION_INHERITS_PROFILE_NOT_RIGHT_TYPE = The profile ''{0}'' is not marked as an obligation profile +SD_OBGLIGATION_INHERITS_PROFILE_NOT_RIGHT_BASE = The profile ''{0}'' has a different base ''{1}'' from that expected ''{2}'' + 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 92176065a..f108d5ce6 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 @@ -1507,7 +1507,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat timeTracker.tx(t, "vc "+system+"#"+code+" '"+display+"'"); if (vr != null && !vr.isOk()) { if (vr.IsNoService()) - txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSERVER); + txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSERVER, system+"#"+code); else if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) { if (binding.getStrength() == BindingStrength.REQUIRED) txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_4a, describeReference(binding.getValueSet(), valueset), vr.getMessage(), system+"#"+code); @@ -1680,7 +1680,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat timeTracker.tx(t, "vc "+c.getSystem()+"#"+c.getCode()+" '"+c.getDisplay()+"'"); if (!vr.isOk()) { if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) - txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_9, describeReference(maxVSUrl, valueset), vr.getMessage()); + txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_9, describeReference(maxVSUrl, valueset), vr.getMessage(), c.getSystem()+"#"+c.getCode()); else ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_10, describeReference(maxVSUrl, valueset), c.getSystem(), c.getCode()) && ok; } @@ -1709,7 +1709,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat timeTracker.tx(t, "vc "+value); if (!vr.isOk()) { if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) - txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_9, describeReference(maxVSUrl, valueset), vr.getMessage()); + txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_9, describeReference(maxVSUrl, valueset), vr.getMessage(), value); else { ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_11, describeReference(maxVSUrl, valueset), vr.getMessage()) && ok; } @@ -1785,7 +1785,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (vr != null && !vr.isOk()) { if (vr.IsNoService()) - txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSERVER); + txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSERVER, theSystem+"#"+theCode); else if (vr.getErrorClass() != null && !vr.getErrorClass().isInfrastructure()) { if (binding.getStrength() == BindingStrength.REQUIRED) ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_4a, describeReference(binding.getValueSet(), valueset), vr.getMessage(), theSystem+"#"+theCode) && ok; @@ -3020,11 +3020,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat removeTrackedMessagesForLocation(errors, element, path); } if (vr != null && !vr.isOk()) { - if (vr.IsNoService()) + if (vr.IsNoService()) { txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_15, value); - else if (binding.getStrength() == BindingStrength.REQUIRED) + } else if (vr.getErrorClass() != null && vr.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) { + txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_15A, value, vr.unknownSystems(), describeReference(binding.getValueSet())); + } else if (binding.getStrength() == BindingStrength.REQUIRED) { ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_16, value, describeReference(binding.getValueSet(), vs), getErrorMessage(vr.getMessage())) && ok; - else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { + } else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) ok = checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), value, stack) && ok; else if (!noExtensibleWarnings && !isOkExtension(value, vs)) diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java index 6afb3ea5f..643771e34 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java @@ -123,24 +123,178 @@ public class StructureDefinitionValidator extends BaseValidator { I18nConstants.SD_DERIVATION_KIND_MISMATCH, base.getKindElement().primitiveValue(), src.getChildValue("kind")) && ok; } } + + List differentials = src.getChildrenByName("differential"); + List snapshots = src.getChildrenByName("snapshot"); + boolean logical = "logical".equals(src.getNamedChildValue("kind")); + boolean constraint = "constraint".equals(src.getNamedChildValue("derivation")); + for (Element differential : differentials) { + ok = validateElementList(errors, differential, stack.push(differential, -1, null, null), false, snapshots.size() > 0, sd, typeName, logical, constraint) && ok; + } + for (Element snapshotE : snapshots) { + ok = validateElementList(errors, snapshotE, stack.push(snapshotE, -1, null, null), true, true, sd, typeName, logical, constraint) && ok; + } + + // obligation profile support + if (src.hasExtension(ToolingExtensions.EXT_OBLIGATION_PROFILE_FLAG)) { + Element ext = src.getExtension(ToolingExtensions.EXT_OBLIGATION_PROFILE_FLAG); + Element value = ext.getNamedChild("value"); + if (value != null && "true".equals(value.primitiveValue())) { + if (rule(errors, "2023-05-27", IssueType.INVALID, stack.getLiteralPath(), "constraint".equals(src.getNamedChildValue("derivation")), I18nConstants.SD_OBGLIGATION_PROFILE_DERIVATION)) { + if (warning(errors, "2023-05-27", IssueType.NOTFOUND, stack.getLiteralPath(), base != null, I18nConstants.SD_OBGLIGATION_PROFILE_UKNOWN, src.getNamedChildValue("baseDefinition"))) { + for (Element differential : differentials) { + ok = validateObligationProfile(errors, differential, stack.push(differential, -1, null, null), base) && ok; + } + } + } + } + } + List extensions = src.getChildren("extension"); + int c = 0; + for (Element extension : extensions) { + if (ToolingExtensions.EXT_OBLIGATION_INHERITS.equals(extension.getNamedChildValue("url"))) { + ok = validateInheritsObligationProfile(errors, extension, stack.push(extension, c, null, null), src) && ok; + } + c++; + } } catch (Exception e) { rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.ERROR_GENERATING_SNAPSHOT, e.getMessage()); ok = false; } - - List differentials = src.getChildrenByName("differential"); - List snapshots = src.getChildrenByName("snapshot"); - boolean logical = "logical".equals(src.getNamedChildValue("kind")); - boolean constraint = "constraint".equals(src.getNamedChildValue("derivation")); - for (Element differential : differentials) { - ok = validateElementList(errors, differential, stack.push(differential, -1, null, null), false, snapshots.size() > 0, sd, typeName, logical, constraint) && ok; - } - for (Element snapshot : snapshots) { - ok = validateElementList(errors, snapshot, stack.push(snapshot, -1, null, null), true, true, sd, typeName, logical, constraint) && ok; - } return ok; } + + private boolean validateInheritsObligationProfile(List errors, Element extension, NodeStack stack, Element src) { + String tgt = extension.getNamedChildValue("value"); + if (rule(errors, "2023-05-27", IssueType.INVALID, stack.getLiteralPath(), tgt != null, + I18nConstants.SD_OBGLIGATION_INHERITS_PROFILE_NO_TARGET)) { + StructureDefinition sd = context.fetchResource(StructureDefinition.class, tgt); + if (rule(errors, "2023-05-27", IssueType.INVALID, stack.getLiteralPath(), src != null, + I18nConstants.SD_OBGLIGATION_INHERITS_PROFILE_TARGET_NOT_FOUND, tgt)) { + if (rule(errors, "2023-05-27", IssueType.INVALID, stack.getLiteralPath(), ToolingExtensions.readBoolExtension(sd, ToolingExtensions.EXT_OBLIGATION_PROFILE_FLAG), + I18nConstants.SD_OBGLIGATION_INHERITS_PROFILE_NOT_RIGHT_TYPE, tgt)) { + String base = src.getNamedChildValue("baseDefinition"); + if (rule(errors, "2023-05-27", IssueType.INVALID, stack.getLiteralPath(), base != null && base.equals(sd.getBaseDefinition()), + I18nConstants.SD_OBGLIGATION_INHERITS_PROFILE_NOT_RIGHT_BASE, tgt, sd.getBaseDefinition(), base)) { + return true; + } + } + } + } + return false; + } + + private boolean validateObligationProfile(List errors, Element elementList, NodeStack stack, StructureDefinition base) { + boolean ok = true; + List elements = elementList.getChildrenByName("element"); + int cc = 0; + for (Element element : elements) { + ok = validateObligationProfileElement(errors, element, stack.push(element, cc, null, null), base) && ok; + cc++; + } + return ok; + } + + private boolean validateObligationProfileElement(List errors, Element element, NodeStack push, StructureDefinition base) { + // rules: it must exist in the base snapshot + // it must only add must-support, obligations and extra bindings + String id = element.getNamedChildValue("id"); + ElementDefinition bd = base.getSnapshot().getElementById(id); + if (rule(errors, "2023-05-27", IssueType.INVALID, push.getLiteralPath(), bd != null, I18nConstants.SD_OBGLIGATION_PROFILE_UNMATCHED, id, base.getVersionedUrl())) { + boolean ok = true; + String name = null; + int c = 0; + for (Element child : element.getChildren()) { + if (child.getName().equals(name)) { + c++; + } else { + name = child.getName(); + c = 0; + } + NodeStack stack = push.push(child, c, null, null); + if (child.getName().equals("extension")) { + String url = child.getNamedChildValue("url"); + if ("http://hl7.org/fhir/tools/StructureDefinition/obligation".equals(url)) { + // this is ok, and it doesn't matter what's in the obligation + } else { + ok = false; + rule(errors, "2023-05-27", IssueType.INVALID, stack.getLiteralPath(), false, + I18nConstants.SD_OBGLIGATION_PROFILE_ILLEGAL, id, child.getName()+"#"+url); + } + } else if (child.getName().equals("mustSupport")) { + // this is ok, and there's nothing to check + } else if (child.getName().equals("id")) { + // this is ok (it must have this), and there's nothing to check + } else if (child.getName().equals("binding")) { + if (rule(errors, "2023-05-27", IssueType.INVALID, stack.getLiteralPath(), bd.hasBinding(), + I18nConstants.SD_OBGLIGATION_PROFILE_ILLEGAL_BINDING, id)) { + ok = validateObligationProfileElementBinding(errors, child, stack, id, bd) && ok; + } else { + ok = false; + } + } else if (child.getName().equals("path")) { + ok = rule(errors, "2023-05-27", IssueType.INVALID, stack.getLiteralPath(), child.primitiveValue().equals(bd.getPath()), + I18nConstants.SD_OBGLIGATION_PROFILE_PATH_WRONG, id, child.primitiveValue(), bd.getPath()) && ok; + } else { + ok = false; + rule(errors, "2023-05-27", IssueType.INVALID, stack.getLiteralPath(), false, + I18nConstants.SD_OBGLIGATION_PROFILE_ILLEGAL, id, child.getName()); + } + } + return ok; + } else { + return false; + } + } + + private boolean validateObligationProfileElementBinding(List errors, Element element, NodeStack nstack, String id, ElementDefinition bd) { + // rules can only have additional bindings + boolean ok = true; + String name = null; + int c = 0; + for (Element child : element.getChildren()) { + if (child.getName().equals(name)) { + c++; + } else { + name = child.getName(); + c = 0; + } + NodeStack stack = nstack.push(child, c, null, null); + if (child.getName().equals("extension")) { + String url = child.getNamedChildValue("url"); + if ("http://hl7.org/fhir/tools/StructureDefinition/additional-binding".equals(url) && !VersionUtilities.isR5Plus(context.getVersion())) { + Element purpose = child.getExtension("purpose"); + if (purpose != null) { // should be an error elsewhere + String code = purpose.getNamedChildValue("value"); + + ok = rule(errors, "2023-05-27", IssueType.INVALID, stack.getLiteralPath(), !Utilities.existsInList(code, "maximum", "required", "extensible"), + I18nConstants.SD_OBGLIGATION_PROFILE_INVALID_BINDING_CODE, id, code) && ok; + } + } else { + ok = false; + rule(errors, "2023-05-27", IssueType.INVALID, stack.getLiteralPath(), false, + I18nConstants.SD_OBGLIGATION_PROFILE_ILLEGAL, id, child.getName()+"#"+url); + } + } else if (child.getName().equals("additional") && VersionUtilities.isR5Plus(context.getVersion())) { + String code = child.getNamedChildValue("purpose"); + ok = rule(errors, "2023-05-27", IssueType.INVALID, stack.getLiteralPath(), !Utilities.existsInList(code, "maximum", "required", "extensible"), + I18nConstants.SD_OBGLIGATION_PROFILE_INVALID_BINDING_CODE, id, code) && ok; + } else if (child.getName().equals("strength")) { + // this has to be repeated, and has to be the same as the derivation + String strengthBase = bd.getBinding().getStrengthElement().asStringValue(); + String strengthDerived = child.primitiveValue(); + ok = rule(errors, "2023-05-27", IssueType.INVALID, stack.getLiteralPath(), strengthBase != null && strengthBase.equals(strengthDerived), + I18nConstants.SD_OBGLIGATION_PROFILE_INVALID_BINDING_STRENGTH, id, strengthDerived, strengthBase) && ok; + } else { + ok = false; + rule(errors, "2023-05-27", IssueType.INVALID, stack.getLiteralPath(), false, + I18nConstants.SD_OBGLIGATION_PROFILE_ILLEGAL_ON_BINDING, id, child.getName()); + } + } + return ok; + } + private void checkExtensionContext(List errors, Element src, NodeStack stack) { String type = src.getNamedChildValue("type"); List eclist = src.getChildren("context"); @@ -194,7 +348,6 @@ public class StructureDefinitionValidator extends BaseValidator { rule(errors, "2022-11-02", IssueType.NOTFOUND, stack.getLiteralPath(), typeName == null || path == null || path.equals(typeName) || path.startsWith(typeName+"."), I18nConstants.SD_PATH_TYPE_MISMATCH, typeName, path); if (!snapshot) { rule(errors, "2023-01-17", IssueType.INVALID, stack.getLiteralPath(), path.contains(".") || !element.hasChild("slicing"), I18nConstants.SD_NO_SLICING_ON_ROOT, path); - } rule(errors, "2023-05-22", IssueType.NOTFOUND, stack.getLiteralPath(), snapshot || !constraint || !element.hasChild("meaningWhenMissing") || meaningWhenMissingAllowed(element), I18nConstants.SD_ELEMENT_NOT_IN_CONSTRAINT, "meaningWhenMissing", path); 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 760fd7c70..9b3f96ddc 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 @@ -36,6 +36,7 @@ import org.hl7.fhir.exceptions.PathEngineException; import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; import org.hl7.fhir.r5.context.ContextUtilities; import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.context.SimpleWorkerContext; import org.hl7.fhir.r5.elementmodel.Element; import org.hl7.fhir.r5.elementmodel.Manager; import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat; @@ -245,6 +246,13 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe } else { val.setBaseOptions(val.getBaseOptions().withVersionFlexible(false)); } + if (content.has("no-tx")) { + boolean notx = "true".equals(content.get("no-tx").getAsString()); + ((SimpleWorkerContext) val.getContext()).setCanRunWithoutTerminology(notx); + ((SimpleWorkerContext) val.getContext()).setNoTerminologyServer(notx); + ((SimpleWorkerContext) val.getContext()).setCachingAllowed(!notx); + + } if (content.has("packages")) { for (JsonElement e : content.getAsJsonArray("packages")) { String n = e.getAsString(); diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/all-systems.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/all-systems.cache index ff532930b..08886d5b3 100644 --- a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/all-systems.cache +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/all-systems.cache @@ -1152,3 +1152,19 @@ v: { "version" : "http://snomed.info/sct/900000000000207008/version/20230131" } ------------------------------------------------------------------------------------- +{"code" : { + "code" : "kg" +}, "url": "http://fhir.de/ValueSet/VitalSignDE_Body_Weigth_UCUM", "version": "0.9.13", "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"true", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "kilogram", + "code" : "kg", + "system" : "http://unitsofmeasure.org", + "version" : "2.0.1" +} +------------------------------------------------------------------------------------- diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/loinc.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/loinc.cache index fdb128699..9f3b074b2 100644 --- a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/loinc.cache +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/loinc.cache @@ -3462,3 +3462,20 @@ v: { "version" : "2.74" } ------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://loinc.org", + "code" : "29463-7" +}, "url": "http://fhir.de/ValueSet/VitalSignDE_Body_Weight_Loinc--0", "version": "0.9.13", "langs":"[en]", "useServer":"true", "useClient":"false", "guessSystem":"false", "valueSetMode":"CHECK_MEMERSHIP_ONLY", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Body weight", + "code" : "29463-7", + "system" : "http://loinc.org", + "version" : "2.74" +} +------------------------------------------------------------------------------------- diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/snomed.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/snomed.cache index 07ca7028f..adf122be6 100644 --- a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/snomed.cache +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/snomed.cache @@ -2188,3 +2188,36 @@ v: { "version" : "http://snomed.info/sct/900000000000207008/version/20210731" } ------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://snomed.info/sct", + "code" : "27113001" +}, "url": "https://fhir.kbv.de/ValueSet/KBV_VS_Base_Body_Weight_Snomed--0", "version": "1.2.1", "langs":"[en]", "useServer":"true", "useClient":"false", "guessSystem":"false", "valueSetMode":"CHECK_MEMERSHIP_ONLY", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Body weight (observable entity)", + "code" : "27113001", + "system" : "http://snomed.info/sct", + "version" : "http://snomed.info/sct/900000000000207008/version/20210731" +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://snomed.info/sct", + "code" : "27113001" +}, "url": "https://fhir.kbv.de/ValueSet/KBV_VS_Base_Body_Weight_Snomed--1", "version": "1.2.1", "langs":"[en]", "useServer":"true", "useClient":"false", "guessSystem":"false", "valueSetMode":"CHECK_MEMERSHIP_ONLY", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "severity" : "error", + "error" : "The provided code http://snomed.info/sct#27113001 is not in the value set 'https://fhir.kbv.de/ValueSet/KBV_VS_Base_Body_Weight_Snomed--1|1.2.1' (from Tx-Server)", + "class" : "UNKNOWN" +} +------------------------------------------------------------------------------------- diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/ucum.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/ucum.cache index 16d6dc302..e7a6f7e27 100644 --- a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/ucum.cache +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/ucum.cache @@ -436,3 +436,20 @@ v: { "class" : "UNKNOWN" } ------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://unitsofmeasure.org", + "code" : "kg" +}, "url": "http://fhir.de/ValueSet/UcumVitalsCommonDE--0", "version": "0.9.13", "langs":"[en]", "useServer":"true", "useClient":"false", "guessSystem":"false", "valueSetMode":"CHECK_MEMERSHIP_ONLY", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "kilogram", + "code" : "kg", + "system" : "http://unitsofmeasure.org", + "version" : "2.0.1" +} +------------------------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index f9a3822ce..06e64df80 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 6.4.1 - 1.3.7 + 1.3.8-SNAPSHOT 2.14.0 5.9.2 1.8.2