001package org.hl7.fhir.validation.instance.type; 002 003import java.io.ByteArrayOutputStream; 004import java.io.IOException; 005import java.util.ArrayList; 006import java.util.Comparator; 007import java.util.HashSet; 008import java.util.List; 009import java.util.Set; 010 011import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50; 012import org.hl7.fhir.convertors.factory.VersionConvertorFactory_14_50; 013import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_50; 014import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50; 015import org.hl7.fhir.exceptions.FHIRException; 016import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; 017import org.hl7.fhir.r5.context.IWorkerContext; 018import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult; 019import org.hl7.fhir.r5.elementmodel.Element; 020import org.hl7.fhir.r5.elementmodel.Manager; 021import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat; 022import org.hl7.fhir.r5.formats.IParser.OutputStyle; 023import org.hl7.fhir.r5.model.Coding; 024import org.hl7.fhir.r5.model.ElementDefinition; 025import org.hl7.fhir.r5.model.ExpressionNode; 026import org.hl7.fhir.r5.model.Extension; 027import org.hl7.fhir.r5.model.Resource; 028import org.hl7.fhir.r5.model.StructureDefinition; 029import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 030import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; 031import org.hl7.fhir.r5.model.ValueSet; 032import org.hl7.fhir.r5.utils.FHIRPathEngine; 033import org.hl7.fhir.r5.utils.ToolingExtensions; 034import org.hl7.fhir.r5.utils.XVerExtensionManager; 035import org.hl7.fhir.utilities.Utilities; 036import org.hl7.fhir.utilities.VersionUtilities; 037import org.hl7.fhir.utilities.i18n.I18nConstants; 038import org.hl7.fhir.utilities.validation.ValidationMessage; 039import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 040import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 041import org.hl7.fhir.utilities.validation.ValidationOptions; 042import org.hl7.fhir.validation.BaseValidator; 043import org.hl7.fhir.validation.TimeTracker; 044import org.hl7.fhir.validation.instance.utils.NodeStack; 045 046public class StructureDefinitionValidator extends BaseValidator { 047 048 public class FhirPathSorter implements Comparator<ExpressionNode> { 049 050 @Override 051 public int compare(ExpressionNode arg0, ExpressionNode arg1) { 052 return arg0.toString().compareTo(arg1.toString()); 053 } 054 055 } 056 057 private FHIRPathEngine fpe; 058 private boolean wantCheckSnapshotUnchanged; 059 060 public StructureDefinitionValidator(IWorkerContext context, TimeTracker timeTracker, FHIRPathEngine fpe, boolean wantCheckSnapshotUnchanged, XVerExtensionManager xverManager, Coding jurisdiction) { 061 super(context, xverManager); 062 source = Source.InstanceValidator; 063 this.fpe = fpe; 064 this.timeTracker = timeTracker; 065 this.wantCheckSnapshotUnchanged = wantCheckSnapshotUnchanged; 066 this.jurisdiction = jurisdiction; 067 } 068 069 public boolean validateStructureDefinition(List<ValidationMessage> errors, Element src, NodeStack stack) { 070 boolean ok = true; 071 StructureDefinition sd = null; 072 String typeName = null; 073 try { 074 sd = loadAsSD(src); 075 List<ElementDefinition> snapshot = sd.getSnapshot().getElement(); 076 sd.setSnapshot(null); 077 typeName = sd.getTypeName(); 078 StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 079 if (warning(errors, NO_RULE_DATE, IssueType.NOTFOUND, stack.getLiteralPath(), base != null, I18nConstants.UNABLE_TO_FIND_BASE__FOR_, sd.getBaseDefinition(), "StructureDefinition, so can't check the differential")) { 080 if (rule(errors, NO_RULE_DATE, IssueType.NOTFOUND, stack.getLiteralPath(), sd.hasDerivation(), I18nConstants.SD_MUST_HAVE_DERIVATION, sd.getUrl())) { 081 boolean bok = base.getAbstract() || sd.hasKind() && sd.getKind() == base.getKind(); 082 rule(errors, "2022-11-02", IssueType.NOTFOUND, stack.getLiteralPath(), bok, I18nConstants.SD_CONSTRAINED_KIND_NO_MATCH, sd.getKind().toCode(), base.getKind().toCode(), base.getType(), base.getUrl()); 083 if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) { 084 rule(errors, "2022-11-02", IssueType.NOTFOUND, stack.getLiteralPath(), sd.hasType() && sd.getType().equals(base.getType()), I18nConstants.SD_CONSTRAINED_TYPE_NO_MATCH, sd.getType(), base.getType()); 085 List<ValidationMessage> msgs = new ArrayList<>(); 086 ProfileUtilities pu = new ProfileUtilities(context, msgs, null); 087 pu.setXver(xverManager); 088 pu.setNewSlicingProcessing(!sd.hasFhirVersion() || VersionUtilities.isR4Plus(sd.getFhirVersion().toCode())); 089 pu.generateSnapshot(base, sd, sd.getUrl(), "http://hl7.org/fhir/R4/", sd.getName()); 090 if (msgs.size() > 0) { 091 for (ValidationMessage msg : msgs) { 092 // we need to set the location for the context 093 String loc = msg.getLocation(); 094 if (loc.contains("#")) { 095 msg.setLocation(stack.getLiteralPath()+".differential.element.where(path = '"+loc.substring(loc.indexOf("#")+1)+"')"); 096 } else { 097 msg.setLocation(stack.getLiteralPath()); 098 } 099 errors.add(msg); 100 ok = false; 101 } 102 } 103 if (!snapshot.isEmpty() && wantCheckSnapshotUnchanged) { 104 int was = snapshot.size(); 105 int is = sd.getSnapshot().getElement().size(); 106 ok = rule(errors, NO_RULE_DATE, IssueType.NOTFOUND, stack.getLiteralPath(), was == is, I18nConstants.SNAPSHOT_EXISTING_PROBLEM, was, is) && ok; 107 } 108 } else { 109 rule(errors, NO_RULE_DATE, IssueType.NOTFOUND, stack.getLiteralPath(), sd.hasType() && !sd.getType().equals(base.getType()), I18nConstants.SD_SPECIALIZED_TYPE_MATCHES, sd.getType(), base.getType()); 110 } 111 } else { 112 ok = false; 113 } 114 if ("constraint".equals(src.getChildValue("derivation"))) { 115 ok = rule(errors, "2022-11-02", IssueType.NOTFOUND, stack.getLiteralPath(), base.getKindElement().primitiveValue().equals(src.getChildValue("kind")), 116 I18nConstants.SD_DERIVATION_KIND_MISMATCH, base.getKindElement().primitiveValue(), src.getChildValue("kind")) && ok; 117 } 118 } 119 } catch (FHIRException | IOException e) { 120 rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.ERROR_GENERATING_SNAPSHOT, e.getMessage()); 121 ok = false; 122 } 123 List<Element> differentials = src.getChildrenByName("differential"); 124 List<Element> snapshots = src.getChildrenByName("snapshot"); 125 for (Element differential : differentials) { 126 ok = validateElementList(errors, differential, stack.push(differential, -1, null, null), false, snapshots.size() > 0, sd, typeName) && ok; 127 } 128 for (Element snapshot : snapshots) { 129 ok = validateElementList(errors, snapshot, stack.push(snapshot, -1, null, null), true, true, sd, typeName) && ok; 130 } 131 return ok; 132 } 133 134 private boolean validateElementList(List<ValidationMessage> errors, Element elementList, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName) { 135 boolean ok = true; 136 List<Element> elements = elementList.getChildrenByName("element"); 137 int cc = 0; 138 for (Element element : elements) { 139 ok = validateElementDefinition(errors, element, stack.push(element, cc, null, null), snapshot, hasSnapshot, sd, typeName) && ok; 140 cc++; 141 } 142 return ok; 143 } 144 145 private boolean validateElementDefinition(List<ValidationMessage> errors, Element element, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName) { 146 boolean ok = true; 147 boolean typeMustSupport = false; 148 String path = element.getNamedChildValue("path"); 149 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); 150 if (!snapshot) { 151 rule(errors, "2023-01-17", IssueType.INVALID, stack.getLiteralPath(), path.contains(".") || !element.hasChild("slicing"), I18nConstants.SD_NO_SLICING_ON_ROOT, path); 152 153 } 154 List<Element> types = element.getChildrenByName("type"); 155 Set<String> typeCodes = new HashSet<>(); 156 Set<String> characteristics = new HashSet<>(); 157 158 for (Element type : types) { 159 if (hasMustSupportExtension(type)) { 160 typeMustSupport = true; 161 } 162 String tc = type.getChildValue("code"); 163 if (type.hasExtension(ToolingExtensions.EXT_FHIR_TYPE)) { 164 tc = type.getExtensionValue(ToolingExtensions.EXT_FHIR_TYPE).primitiveValue(); 165 } 166 if (Utilities.noString(tc) && type.hasChild("code")) { 167 if (VersionUtilities.isR4Plus(context.getVersion())) { 168 throw new Error("Snapshot for " + sd.getId() +" element " + path + " has type.code without a value "); 169 } 170 } 171 if (!Utilities.noString(tc)) { 172 typeCodes.add(tc); 173 Set<String> tcharacteristics = new HashSet<>(); 174 StructureDefinition tsd = context.fetchTypeDefinition(tc); 175 if (tsd != null && tsd.hasExtension(ToolingExtensions.EXT_TYPE_CHARACTERISTICS)) { 176 for (Extension ext : tsd.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_CHARACTERISTICS)) { 177 tcharacteristics.add(ext.getValue().primitiveValue()); 178 } 179 } else { 180 // nothing specified, so infer from known types 181 addCharacteristics(tcharacteristics, tc); 182 } 183 characteristics.addAll(tcharacteristics); 184 if (type.hasChildren("targetProfile")) { 185 ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), tcharacteristics.contains("has-target") , I18nConstants.SD_ILLEGAL_CHARACTERISTICS, "targetProfile", tc) && ok; 186 } 187 // check the stated profile - must be a constraint on the type 188 if (snapshot || sd != null) { 189 ok = validateElementType(errors, type, stack.push(type, -1, null, null), sd, path) && ok; 190 } 191 } 192 } 193 if (typeMustSupport) { 194 if (snapshot) { 195 ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), "true".equals(element.getChildValue("mustSupport")), I18nConstants.SD_NESTED_MUST_SUPPORT_SNAPSHOT, path) && ok; 196 } else { 197 hint(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), hasSnapshot || "true".equals(element.getChildValue("mustSupport")), I18nConstants.SD_NESTED_MUST_SUPPORT_DIFF, path); 198 } 199 } 200 if (element.hasChild("binding")) { 201 if (!typeCodes.isEmpty()) { 202 ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), characteristics.contains("can-bind") , I18nConstants.SD_ILLEGAL_CHARACTERISTICS, "Binding", typeCodes) && ok; 203 } 204 Element binding = element.getNamedChild("binding"); 205 ok = validateBinding(errors, binding, stack.push(binding, -1, null, null), typeCodes, snapshot, path) && ok; 206 } else { 207 // this is a good idea but there's plenty of cases where the rule isn't met; maybe one day it's worth investing the time to exclude these cases and bring this rule back 208// String bt = boundType(typeCodes); 209// hint(errors, UNKNOWN_DATE_TIME, IssueType.BUSINESSRULE, stack.getLiteralPath(), !snapshot || bt == null, I18nConstants.SD_ED_SHOULD_BIND, element.getNamedChildValue("path"), bt); 210 } 211 if (!typeCodes.isEmpty()) { 212 if (element.hasChild("maxLength")) { 213 ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), characteristics.contains("has-length") , I18nConstants.SD_ILLEGAL_CHARACTERISTICS, "MaxLength", typeCodes) && ok; 214 } 215 if (element.hasExtension(ToolingExtensions.EXT_MIN_LENGTH)) { 216 ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), characteristics.contains("has-length") , I18nConstants.SD_ILLEGAL_CHARACTERISTICS, "MinLength Extension", typeCodes) && ok; 217 } 218 if (element.hasChild("minValue")) { 219 ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), characteristics.contains("has-range") , I18nConstants.SD_ILLEGAL_CHARACTERISTICS, "MinValue", typeCodes) && ok; 220 } 221 if (element.hasChild("maxValue")) { 222 ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), characteristics.contains("has-range") , I18nConstants.SD_ILLEGAL_CHARACTERISTICS, "MaxValue", typeCodes) && ok; 223 } 224 if (element.hasExtension(ToolingExtensions.EXT_MAX_DECIMALS)) { 225 ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), characteristics.contains("is-continuous") , I18nConstants.SD_ILLEGAL_CHARACTERISTICS, "Max Decimal Places Extension", typeCodes) && ok; 226 } 227 if (element.hasExtension(ToolingExtensions.EXT_MAX_SIZE)) { 228 ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), characteristics.contains("has-size") , I18nConstants.SD_ILLEGAL_CHARACTERISTICS, "Max Size", typeCodes) && ok; 229 } 230 } 231 // in a snapshot, we validate that fixedValue, pattern, and defaultValue, if present, are all of the right type 232 if (snapshot && (element.getIdBase() != null) && (element.getIdBase().contains("."))) { 233 if (rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), !typeCodes.isEmpty() || element.hasChild("contentReference"), I18nConstants.SD_NO_TYPES_OR_CONTENTREF, element.getIdBase())) { 234 // if we see fixed[x] or pattern[x] applied to a repeating element, we'll give the user a hint 235 boolean repeating = !Utilities.existsInList(element.getChildValue("max"), "0", "1"); 236 237 Element v = element.getNamedChild("defaultValue"); 238 if (v != null) { 239 ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.push(v, -1, null, null).getLiteralPath(), typeCodes.contains(v.fhirType()), I18nConstants.SD_VALUE_TYPE_IILEGAL, element.getIdBase(), "defaultValue", v.fhirType(), typeCodes) && ok; 240 } 241 v = element.getNamedChild("fixed"); 242 if (v != null) { 243 ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.push(v, -1, null, null).getLiteralPath(), typeCodes.contains(v.fhirType()), I18nConstants.SD_VALUE_TYPE_IILEGAL, element.getIdBase(), "fixed", v.fhirType(), typeCodes) && ok; 244 hint(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.push(v, -1, null, null).getLiteralPath(), !repeating, I18nConstants.SD_VALUE_TYPE_REPEAT_HINT, element.getIdBase(), "fixed"); 245 if (isPrimitiveType(v.fhirType())) { 246 warning(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.push(v, -1, null, null).getLiteralPath(), !repeating, I18nConstants.SD_VALUE_TYPE_REPEAT_WARNING_DOTNET, element.getIdBase(), "fixed"); 247 } else { 248 warning(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.push(v, -1, null, null).getLiteralPath(), false, I18nConstants.SD_VALUE_COMPLEX_FIXED, v.fhirType()); 249 } 250 } 251 v = element.getNamedChild("pattern"); 252 if (v != null) { 253 ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.push(v, -1, null, null).getLiteralPath(), typeCodes.contains(v.fhirType()), I18nConstants.SD_VALUE_TYPE_IILEGAL, element.getIdBase(), "pattern", v.fhirType(), typeCodes) && ok; 254 hint(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.push(v, -1, null, null).getLiteralPath(), !repeating, I18nConstants.SD_VALUE_TYPE_REPEAT_HINT, element.getIdBase(), "pattern"); 255 if (isPrimitiveType(v.fhirType())) { 256 warning(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.push(v, -1, null, null).getLiteralPath(), !repeating, I18nConstants.SD_VALUE_TYPE_REPEAT_WARNING_DOTNET, element.getIdBase(), "pattern"); 257 } 258 } 259 } 260 // if we see fixed[x] or pattern[x] applied to a repeating element, we'll give the user a hint 261 } 262 return ok; 263 } 264 265 private boolean addCharacteristics(Set<String> set, String tc) { 266 switch (tc) { 267 case "boolean" : return addCharacteristicsForType(set); 268 case "integer" : return addCharacteristicsForType(set, "has-range", "has-length"); 269 case "integer64" : return addCharacteristicsForType(set, "has-range", "has-length"); 270 case "decimal" :return addCharacteristicsForType(set, "has-range", "is-continuous", "has-length"); 271 case "base64Binary" : return addCharacteristicsForType(set, "has-size"); 272 case "instant" : return addCharacteristicsForType(set, "has-range", "is-continuous", "has-length"); 273 case "string" : return addCharacteristicsForType(set, "has-length", "do-translations", "can-bind"); 274 case "uri" : return addCharacteristicsForType(set, "has-length", "can-bind"); 275 case "date" :return addCharacteristicsForType(set, "has-range", "has-length"); 276 case "dateTime" : return addCharacteristicsForType(set, "has-range", "is-continuous", "has-length"); 277 case "time" :return addCharacteristicsForType(set, "has-range", "is-continuous", "has-length"); 278 case "canonical" :return addCharacteristicsForType(set, "has-target", "has-length"); 279 case "code" :return addCharacteristicsForType(set, "has-length", "can-bind"); 280 case "id" :return addCharacteristicsForType(set, "has-length"); 281 case "markdown" :return addCharacteristicsForType(set, "do-translations", "has-length"); 282 case "oid" :return addCharacteristicsForType(set, "has-length", "can-bind"); 283 case "positiveInt" :return addCharacteristicsForType(set, "has-range", "has-length"); 284 case "unsignedInt" :return addCharacteristicsForType(set, "has-range", "has-length"); 285 case "url" :return addCharacteristicsForType(set, "has-length", "can-bind"); 286 case "uuid" :return addCharacteristicsForType(set, "has-length", "can-bind"); 287 case "xhtml" :return addCharacteristicsForType(set); 288 case "Address" :return addCharacteristicsForType(set, "do-translations"); 289 case "Age" : return addCharacteristicsForType(set, "has-range", "is-continuous", "can-bind", "has-units"); 290 case "Annotation" :return addCharacteristicsForType(set); 291 case "Attachment" :return addCharacteristicsForType(set, "has-size", "do-translations"); 292 case "CodeableConcept" :return addCharacteristicsForType(set, "can-bind", "do-translations"); 293 case "CodeableReference" : return addCharacteristicsForType(set, "has-target", "can-bind", "do-translations"); 294 case "Coding" : return addCharacteristicsForType(set, "can-bind", "do-translations"); 295 case "ContactPoint" :return addCharacteristicsForType(set); 296 case "Count" :return addCharacteristicsForType(set, "has-range"); 297 case "Distance" :return addCharacteristicsForType(set, "has-range", "is-continuous", "can-bind", "has-units"); 298 case "Duration" : return addCharacteristicsForType(set, "has-range", "is-continuous", "can-bind", "has-units"); 299 case "HumanName" :return addCharacteristicsForType(set); 300 case "Identifier" : return addCharacteristicsForType(set); 301 case "Money" : return addCharacteristicsForType(set, "has-range", "is-continuous"); 302 case "Period" : return addCharacteristicsForType(set); 303 case "Quantity" :return addCharacteristicsForType(set, "has-range", "is-continuous", "can-bind", "has-units"); 304 case "Range" :return addCharacteristicsForType(set, "has-units", "can-bind", "has-units"); 305 case "Ratio" :return addCharacteristicsForType(set, "has-units"); 306 case "RatioRange" : return addCharacteristicsForType(set, "has-units"); 307 case "Reference" : return addCharacteristicsForType(set, "has-target"); 308 case "SampledData" :return addCharacteristicsForType(set); 309 case "Signature" : return addCharacteristicsForType(set); 310 case "Timing" : return addCharacteristicsForType(set); 311 case "ContactDetail" :return addCharacteristicsForType(set); 312 case "Contributor" :return addCharacteristicsForType(set); 313 case "DataRequirement" :return addCharacteristicsForType(set); 314 case "Expression" : return addCharacteristicsForType(set); 315 case "ParameterDefinition" : return addCharacteristicsForType(set); 316 case "RelatedArtifact" :return addCharacteristicsForType(set); 317 case "TriggerDefinition" :return addCharacteristicsForType(set); 318 case "UsageContext" :return addCharacteristicsForType(set); 319 case "Dosage" : return addCharacteristicsForType(set); 320 case "Meta" :return addCharacteristicsForType(set); 321 case "Resource" :return addCharacteristicsForType(set); 322 case "Extension" :return addCharacteristicsForType(set, "can-bind"); 323 case "Narrative" :return addCharacteristicsForType(set); 324 case "MoneyQuantity" :return addCharacteristicsForType(set, "has-range", "is-continuous", "can-bind", "has-units"); 325 case "SimpleQuantity" :return addCharacteristicsForType(set, "has-range", "is-continuous", "can-bind", "has-units"); 326 case "MarketingStatus" :return addCharacteristicsForType(set); 327 case "ExtendedContactDetail" :return addCharacteristicsForType(set); 328 case "VirtualServiceDetail" :return addCharacteristicsForType(set); 329 case "Availability" :return addCharacteristicsForType(set); 330 case "MonetaryComponent" :return addCharacteristicsForType(set); 331 case "ElementDefinition" :return addCharacteristicsForType(set); 332 333 case "BackboneElement" :return addCharacteristicsForType(set); 334 case "Element" :return addCharacteristicsForType(set); 335 case "Base" :return addCharacteristicsForType(set); 336 default: 337// if (!context.getResourceNames().contains(tc)) { 338// System.out.println("Unhandled data type in addCharacteristics: "+tc); 339// } 340 return addCharacteristicsForType(set); 341 } 342 } 343 344 345 private boolean addCharacteristicsForType(Set<String> set, String... cl) { 346 for (String c : cl) { 347 set.add(c); 348 } 349 return true; 350 } 351 352 private boolean isPrimitiveType(String fhirType) { 353 StructureDefinition sd = context.fetchTypeDefinition(fhirType); 354 return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 355 } 356 357 private String boundType(Set<String> typeCodes) { 358 for (String tc : typeCodes) { 359 if (Utilities.existsInList(tc, "code", "Coding", "CodeableConcept", "Quantity", "CodeableReference")) { 360 return tc; 361 } 362 } 363 return null; 364 } 365 366 private String bindableType(Set<String> typeCodes) { 367 String ret = boundType(typeCodes); 368 if (ret != null) { 369 return ret; 370 } 371 for (String tc : typeCodes) { 372 if (Utilities.existsInList(tc, "string", "uri", "CodeableConcept", "Quantity", "CodeableReference")) { 373 return tc; 374 } 375 StructureDefinition sd = context.fetchTypeDefinition(tc); 376 if (sd != null) { 377 if (sd.hasExtension(ToolingExtensions.EXT_BINDING_STYLE)) { 378 return tc; 379 } 380 for (Extension ext : sd.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_CHARACTERISTICS)) { 381 if ("can-bind".equals(ext.getValue().primitiveValue())) { 382 return tc; 383 } 384 } 385 } 386 } 387 return null; 388 } 389 390 private boolean validateBinding(List<ValidationMessage> errors, Element binding, NodeStack stack, Set<String> typeCodes, boolean snapshot, String path) { 391 boolean ok = true; 392 if (bindableType(typeCodes) == null) { 393 ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), !snapshot, I18nConstants.SD_ED_BIND_NO_BINDABLE, path, typeCodes.toString()) && ok; 394 } 395 if (!snapshot) { 396 Set<String> bindables = getListofBindableTypes(typeCodes); 397 hint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), bindables.size() <= 1, I18nConstants.SD_ED_BIND_MULTIPLE_TYPES, path, typeCodes.toString()); 398 } 399 400 if (binding.hasChild("valueSet")) { 401 Element valueSet = binding.getNamedChild("valueSet"); 402 String ref = valueSet.hasPrimitiveValue() ? valueSet.primitiveValue() : valueSet.getNamedChildValue("reference"); 403 if (warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), !snapshot || ref != null, I18nConstants.SD_ED_SHOULD_BIND_WITH_VS, path)) { 404 Resource vs = context.fetchResource(Resource.class, ref); 405 406 // just because we can't resolve it directly doesn't mean that terminology server can't. Check with it 407 408 if (warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs != null || serverSupportsValueSet(ref), I18nConstants.SD_ED_BIND_UNKNOWN_VS, path, ref)) { 409 if (vs != null) { 410 ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs instanceof ValueSet, I18nConstants.SD_ED_BIND_NOT_VS, path, ref, vs.fhirType()) && ok; 411 } 412 } 413 } 414 } 415 return ok; 416 } 417 418 private Set<String> getListofBindableTypes(Set<String> types) { 419 Set<String> res = new HashSet<>(); 420 for (String s : types) { 421 if (Utilities.existsInList(s, "code", "string", "url", "uri", "Coding", "CodeableConcept", "Quantity", "CodeableReference")) { 422 res.add(s); 423 } 424 } 425 return res; 426 } 427 428 private boolean serverSupportsValueSet(String ref) { 429 ValidationResult vr = context.validateCode(new ValidationOptions().checkValueSetOnly().setVsAsUrl().noClient(), new Coding("http://loinc.org", "5792-7", null), new ValueSet().setUrl(ref)); 430 return vr.getErrorClass() == null; 431 } 432 433 private boolean validateElementType(List<ValidationMessage> errors, Element type, NodeStack stack, StructureDefinition sd, String path) { 434 boolean ok = true; 435 String code = type.getNamedChildValue("code"); 436 if (code == null && path != null) { 437 code = getTypeCodeFromSD(sd, path); 438 } 439 if (code != null) { 440 List<Element> profiles = type.getChildrenByName("profile"); 441 if (VersionUtilities.isR2Ver(context.getVersion()) || VersionUtilities.isR2BVer(context.getVersion()) ) { 442 for (Element profile : profiles) { 443 ok = validateProfileTypeOrTarget(errors, profile, code, stack.push(profile, -1, null, null), path) && ok; 444 } 445 446 } else { 447 for (Element profile : profiles) { 448 ok = validateTypeProfile(errors, profile, code, stack.push(profile, -1, null, null), path) && ok; 449 } 450 profiles = type.getChildrenByName("targetProfile"); 451 for (Element profile : profiles) { 452 ok = validateTargetProfile(errors, profile, code, stack.push(profile, -1, null, null), path) && ok; 453 } 454 } 455 } 456 return true; 457 } 458 459 private boolean validateProfileTypeOrTarget(List<ValidationMessage> errors, Element profile, String code, NodeStack stack, String path) { 460 boolean ok = true; 461 String p = profile.primitiveValue(); 462 StructureDefinition sd = context.fetchResource(StructureDefinition.class, p); 463 if (code.equals("Reference")) { 464 if (warning(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), sd != null, I18nConstants.SD_ED_TYPE_PROFILE_UNKNOWN, p)) { 465 StructureDefinition t = determineBaseType(sd); 466 if (t == null) { 467 ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.SD_ED_TYPE_PROFILE_NOTYPE, p) && ok; 468 } else { 469 ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), sd.getKind() == StructureDefinitionKind.RESOURCE, I18nConstants.SD_ED_TYPE_PROFILE_WRONG, p, t, code, path) && ok; 470 } 471 } 472 } else { 473 if (sd == null ) { 474 sd = getXverExt(errors, stack.getLiteralPath(), profile, p); 475 } 476 if (warning(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), sd != null, I18nConstants.SD_ED_TYPE_PROFILE_UNKNOWN, p)) { 477 StructureDefinition t = determineBaseType(sd); 478 if (t == null) { 479 ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.SD_ED_TYPE_PROFILE_NOTYPE, p) && ok; 480 } else { 481 ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), isInstanceOf(t, code), I18nConstants.SD_ED_TYPE_PROFILE_WRONG, p, t, code, path) && ok; 482 if (t.getType().equals("Extension")) { 483 boolean isModifierDefinition = checkIsModifierExtension(sd); 484 boolean isModifierContext = path.endsWith(".modifierExtension"); 485 if (isModifierDefinition) { 486 ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), isModifierContext, I18nConstants.SD_ED_TYPE_PROFILE_NOT_MODIFIER, p, t, code, path) && ok; 487 } else { 488 ok =rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), !isModifierContext, I18nConstants.SD_ED_TYPE_PROFILE_IS_MODIFIER, p, t, code, path) && ok; 489 } 490 } 491 } 492 } 493 } 494 return ok; 495 } 496 497 private String getTypeCodeFromSD(StructureDefinition sd, String path) { 498 ElementDefinition ed = null; 499 for (ElementDefinition t : sd.getSnapshot().getElement()) { 500 if (t.hasPath() && t.getPath().equals(path)) { 501 if (ed == null) { 502 ed = t; 503 } else { 504 return null; // more than one match, we don't know which is which 505 } 506 } 507 } 508 return ed != null && ed.getType().size() == 1 ? ed.getTypeFirstRep().getCode() : null; 509 } 510 511 private boolean validateTypeProfile(List<ValidationMessage> errors, Element profile, String code, NodeStack stack, String path) { 512 boolean ok = true; 513 String p = profile.primitiveValue(); 514 StructureDefinition sd = context.fetchResource(StructureDefinition.class, p); 515 if (sd == null ) { 516 sd = getXverExt(errors, stack.getLiteralPath(), profile, p); 517 } 518 if (warning(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), sd != null, I18nConstants.SD_ED_TYPE_PROFILE_UNKNOWN, p)) { 519 StructureDefinition t = determineBaseType(sd); 520 if (t == null) { 521 ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.SD_ED_TYPE_PROFILE_NOTYPE, p) && ok; 522 } else if (!isInstanceOf(t, code)) { 523 ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.SD_ED_TYPE_PROFILE_WRONG, p, t, code, path) && ok; 524 } else { 525 if (t.getType().equals("Extension")) { 526 boolean isModifierDefinition = checkIsModifierExtension(sd); 527 boolean isModifierContext = path.endsWith(".modifierExtension"); 528 if (isModifierDefinition) { 529 ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), isModifierContext, I18nConstants.SD_ED_TYPE_PROFILE_NOT_MODIFIER, p, t, code, path) && ok; 530 } else { 531 ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), !isModifierContext, I18nConstants.SD_ED_TYPE_PROFILE_IS_MODIFIER, p, t, code, path) && ok; 532 } 533 } 534 } 535 } 536 return ok; 537 } 538 539 private boolean checkIsModifierExtension(StructureDefinition t) { 540 return t.getSnapshot().getElementFirstRep().getIsModifier(); 541 } 542 543 private boolean validateTargetProfile(List<ValidationMessage> errors, Element profile, String code, NodeStack stack, String path) { 544 boolean ok = true; 545 String p = profile.primitiveValue(); 546 StructureDefinition sd = context.fetchResource(StructureDefinition.class, p); 547 if (code.equals("Reference") || code.equals("CodeableReference")) { 548 if (warning(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), sd != null, I18nConstants.SD_ED_TYPE_PROFILE_UNKNOWN, p)) { 549 StructureDefinition t = determineBaseType(sd); 550 if (t == null) { 551 ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.SD_ED_TYPE_PROFILE_NOTYPE, p) && ok; 552 } else { 553 ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), sd.getKind() == StructureDefinitionKind.RESOURCE, I18nConstants.SD_ED_TYPE_PROFILE_WRONG_TARGET, p, t, code, path, "Resource") && ok; 554 } 555 } 556 } else if (code.equals("canonical")) { 557 if (warning(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), sd != null, I18nConstants.SD_ED_TYPE_PROFILE_UNKNOWN, p)) { 558 StructureDefinition t = determineBaseType(sd); 559 if (t == null) { 560 ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.SD_ED_TYPE_PROFILE_NOTYPE, p) && ok; 561 } else if (!VersionUtilities.isR5Ver(context.getVersion())) { 562 ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), VersionUtilities.getCanonicalResourceNames(context.getVersion()).contains(t.getType()) || "Resource".equals(t.getType()), I18nConstants.SD_ED_TYPE_PROFILE_WRONG_TARGET, p, t, code, path, "Canonical Resource") && ok; 563 } else { 564 ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), "CanonicalResource".equals(t.getType()) || VersionUtilities.getCanonicalResourceNames(context.getVersion()).contains(t.getType()), I18nConstants.SD_ED_TYPE_PROFILE_WRONG_TARGET, p, t, code, path, "Canonical Resource") && ok; 565 } 566 } 567 } else { 568 ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.SD_ED_TYPE_NO_TARGET_PROFILE, code) && ok; 569 } 570 return ok; 571 } 572 573 private boolean isInstanceOf(StructureDefinition sd, String code) { 574 while (sd != null) { 575 if (sd.getType().equals(code)) { 576 return true; 577 } 578 if (sd.getUrl().equals(code)) { 579 return true; 580 } 581 sd = sd.hasBaseDefinition() ? context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()) : null; 582 if (!(VersionUtilities.isR2Ver(context.getVersion()) || VersionUtilities.isR2BVer(context.getVersion())) && sd != null && !sd.getAbstract() && sd.getKind() != StructureDefinitionKind.LOGICAL) { 583 sd = null; 584 } 585 } 586 587 return false; 588 } 589 590 private StructureDefinition determineBaseType(StructureDefinition sd) { 591 while (sd != null && sd.getDerivation() == TypeDerivationRule.CONSTRAINT) { 592 sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 593 } 594 return sd; 595 } 596 597 private boolean hasMustSupportExtension(Element type) { 598 if ("true".equals(getExtensionValue(type, ToolingExtensions.EXT_MUST_SUPPORT))) { 599 return true; 600 } 601 List<Element> profiles = type.getChildrenByName("profile"); 602 for (Element profile : profiles) { 603 if ("true".equals(getExtensionValue(profile, ToolingExtensions.EXT_MUST_SUPPORT))) { 604 return true; 605 } 606 } 607 profiles = type.getChildrenByName("targetProfile"); 608 for (Element profile : profiles) { 609 if ("true".equals(getExtensionValue(profile, ToolingExtensions.EXT_MUST_SUPPORT))) { 610 return true; 611 } 612 } 613 return false; 614 } 615 616 private String getExtensionValue(Element element, String url) { 617 List<Element> extensions = element.getChildrenByName("extension"); 618 for (Element extension : extensions) { 619 if (url.equals(extension.getNamedChildValue("url"))) { 620 return extension.getNamedChildValue("value"); 621 } 622 } 623 return null; 624 } 625 626 private StructureDefinition loadAsSD(Element src) throws FHIRException, IOException { 627 ByteArrayOutputStream bs = new ByteArrayOutputStream(); 628 Manager.compose(context, src, bs, FhirFormat.JSON, OutputStyle.NORMAL, null); 629 if (VersionUtilities.isR2Ver(context.getVersion())) { 630 org.hl7.fhir.dstu2.model.Resource r2 = new org.hl7.fhir.dstu2.formats.JsonParser().parse(bs.toByteArray()); 631 return (StructureDefinition) VersionConvertorFactory_10_50.convertResource(r2); 632 } 633 if (VersionUtilities.isR2BVer(context.getVersion())) { 634 org.hl7.fhir.dstu2016may.model.Resource r2b = new org.hl7.fhir.dstu2016may.formats.JsonParser().parse(bs.toByteArray()); 635 return (StructureDefinition) VersionConvertorFactory_14_50.convertResource(r2b); 636 } 637 if (VersionUtilities.isR3Ver(context.getVersion())) { 638 org.hl7.fhir.dstu3.model.Resource r3 = new org.hl7.fhir.dstu3.formats.JsonParser().parse(bs.toByteArray()); 639 return (StructureDefinition) VersionConvertorFactory_30_50.convertResource(r3); 640 } 641 if (VersionUtilities.isR4Ver(context.getVersion())) { 642 org.hl7.fhir.r4.model.Resource r4 = new org.hl7.fhir.r4.formats.JsonParser().parse(bs.toByteArray()); 643 return (StructureDefinition) VersionConvertorFactory_40_50.convertResource(r4); 644 } 645 return (StructureDefinition) new org.hl7.fhir.r5.formats.JsonParser().parse(bs.toByteArray()); 646 } 647 648}