001package org.hl7.fhir.dstu3.validation; 002 003import static org.apache.commons.lang3.StringUtils.isBlank; 004import static org.apache.commons.lang3.StringUtils.isNotBlank; 005 006import java.io.IOException; 007import java.io.InputStream; 008import java.util.ArrayList; 009import java.util.HashMap; 010import java.util.HashSet; 011import java.util.List; 012import java.util.Map; 013import java.util.Set; 014import java.util.regex.Matcher; 015import java.util.regex.Pattern; 016 017import org.apache.commons.lang3.StringUtils; 018import org.hl7.fhir.dstu3.conformance.ProfileUtilities; 019import org.hl7.fhir.dstu3.context.IWorkerContext; 020import org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult; 021import org.hl7.fhir.dstu3.elementmodel.Element; 022import org.hl7.fhir.dstu3.elementmodel.Element.SpecialElement; 023import org.hl7.fhir.dstu3.elementmodel.JsonParser; 024import org.hl7.fhir.dstu3.elementmodel.Manager; 025import org.hl7.fhir.dstu3.elementmodel.Manager.FhirFormat; 026import org.hl7.fhir.dstu3.elementmodel.ObjectConverter; 027import org.hl7.fhir.dstu3.elementmodel.ParserBase; 028import org.hl7.fhir.dstu3.elementmodel.ParserBase.ValidationPolicy; 029import org.hl7.fhir.dstu3.elementmodel.XmlParser; 030import org.hl7.fhir.dstu3.formats.FormatUtilities; 031import org.hl7.fhir.dstu3.model.*; 032import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; 033import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent; 034import org.hl7.fhir.dstu3.model.ElementDefinition.AggregationMode; 035import org.hl7.fhir.dstu3.model.ElementDefinition.ConstraintSeverity; 036import org.hl7.fhir.dstu3.model.ElementDefinition.DiscriminatorType; 037import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionBindingComponent; 038import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionConstraintComponent; 039import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent; 040import org.hl7.fhir.dstu3.model.ElementDefinition.PropertyRepresentation; 041import org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent; 042import org.hl7.fhir.dstu3.model.Enumerations.BindingStrength; 043import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemComponent; 044import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemOptionComponent; 045import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType; 046import org.hl7.fhir.dstu3.model.StructureDefinition.ExtensionContext; 047import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionKind; 048import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionSnapshotComponent; 049import org.hl7.fhir.dstu3.model.StructureDefinition.TypeDerivationRule; 050import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent; 051import org.hl7.fhir.dstu3.utils.FHIRLexer.FHIRLexerException; 052import org.hl7.fhir.dstu3.utils.FHIRPathEngine; 053import org.hl7.fhir.dstu3.utils.FHIRPathEngine.IEvaluationContext; 054import org.hl7.fhir.dstu3.utils.IResourceValidator; 055import org.hl7.fhir.dstu3.utils.ToolingExtensions; 056import org.hl7.fhir.dstu3.utils.ValidationProfileSet; 057import org.hl7.fhir.dstu3.utils.ValidationProfileSet.ProfileRegistration; 058import org.hl7.fhir.exceptions.DefinitionException; 059import org.hl7.fhir.exceptions.FHIRException; 060import org.hl7.fhir.exceptions.FHIRFormatError; 061import org.hl7.fhir.exceptions.PathEngineException; 062import org.hl7.fhir.exceptions.TerminologyServiceException; 063import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 064import org.hl7.fhir.utilities.Utilities; 065import org.hl7.fhir.utilities.validation.ValidationMessage; 066import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 067import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 068import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 069import org.hl7.fhir.utilities.xhtml.NodeType; 070import org.hl7.fhir.utilities.xhtml.XhtmlNode; 071import org.w3c.dom.Document; 072import org.w3c.dom.Node; 073 074import com.google.gson.Gson; 075import com.google.gson.JsonObject; 076import com.google.gson.stream.JsonWriter; 077 078import ca.uhn.fhir.util.ObjectUtil; 079 080 081/** 082 * Thinking of using this in a java program? Don't! 083 * You should use on of the wrappers instead. Either in HAPI, or use ValidationEngine 084 * 085 * @author Grahame Grieve 086 * 087 */ 088/* 089 * todo: 090 * check urn's don't start oid: or uuid: 091 */ 092public class InstanceValidator extends BaseValidator implements IResourceValidator { 093 094 095 private IWorkerContext context; 096 private FHIRPathEngine fpe; 097 098 // configuration items 099 private CheckDisplayOption checkDisplay; 100 private boolean anyExtensionsAllowed; 101 private boolean errorForUnknownProfiles; 102 private boolean noInvariantChecks; 103 private boolean noTerminologyChecks; 104 private BestPracticeWarningLevel bpWarnings; 105 106 private List<String> extensionDomains = new ArrayList<String>(); 107 108 private IdStatus resourceIdRule; 109 private boolean allowXsiLocation; 110 111 // used during the build process to keep the overall volume of messages down 112 private boolean suppressLoincSnomedMessages; 113 114 private Bundle logical; 115 116 // time tracking 117 private long overall = 0; 118 private long txTime = 0; 119 private long sdTime = 0; 120 private long loadTime = 0; 121 private long fpeTime = 0; 122 123 private boolean noBindingMsgSuppressed; 124 private HashMap<Element, ResourceProfiles> resourceProfilesMap; 125 private IValidatorResourceFetcher fetcher; 126 long time = 0; 127 128 /* 129 * Keeps track of whether a particular profile has been checked or not yet 130 */ 131 private class ProfileUsage { 132 private StructureDefinition profile; 133 private boolean checked; 134 135 public ProfileUsage(StructureDefinition profile) { 136 this.profile = profile; 137 this.checked = false; 138 } 139 140 public boolean isChecked() { 141 return checked; 142 } 143 144 public void setChecked() { 145 this.checked = true; 146 } 147 148 public StructureDefinition getProfile() { 149 return profile; 150 } 151 } 152 153 /* 154 * Keeps track of all profiles associated with a resource element and whether the resource has been checked against those profiles yet 155 */ 156 public class ResourceProfiles { 157 private Element resource; 158 private Element owner; 159 private NodeStack stack; 160 private HashMap<StructureDefinition, ProfileUsage> profiles; 161 private boolean processed; 162 163 public ResourceProfiles(Element resource, NodeStack stack) { 164 this.resource = resource; 165 if (this.resource.getName().equals("contained")) 166 this.owner = stack.parent.element; 167 else 168 this.owner = resource; 169 this.stack = stack; 170 this.profiles = new HashMap<StructureDefinition, ProfileUsage>(); 171 this.processed = false; 172 } 173 174 public boolean isProcessed() { 175 return processed; 176 } 177 178 public void setProcessed() { 179 processed = true; 180 } 181 182 public NodeStack getStack() { 183 return stack; 184 } 185 186 public Element getOwner() { 187 return owner; 188 } 189 190 public boolean hasProfiles() { 191 return !profiles.isEmpty(); 192 } 193 194 public void addProfiles(List<ValidationMessage> errors, ValidationProfileSet profiles, String path, Element element) throws FHIRException { 195 for (ProfileRegistration profile : profiles.getCanonical()) 196 addProfile(errors, profile.getProfile(), profile.isError(), path, element); 197 } 198 199 public boolean addProfile(List<ValidationMessage> errors, String profile, boolean error, String path, Element element) { 200 String effectiveProfile = profile; 201 String version = null; 202 if (profile.contains("|")) { 203 effectiveProfile = profile.substring(0, profile.indexOf('|')); 204 version = profile.substring(profile.indexOf('|')+1); 205 } 206 StructureDefinition sd = context.fetchResource(StructureDefinition.class, effectiveProfile); 207 if (warningOrError(error, errors, IssueType.INVALID, element.line(), element.col(), path, sd != null, "StructureDefinition reference \"{0}\" could not be resolved", profile)) { 208 if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, version==null || (sd.getVersion()!=null && sd.getVersion().equals(version)), 209 "Referenced version " + version + " does not match found version " + sd.getVersion() + " for profile " + sd.getUrl(), profile)) { 210 if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, sd.hasSnapshot(), 211 "StructureDefinition has no snapshot - validation is against the snapshot, so it must be provided")) { 212 if (!profiles.containsKey(sd)) { 213 profiles.put(sd, new ProfileUsage(sd)); 214 addAncestorProfiles(sd); 215 return true; 216 } 217 } 218 } 219 } 220 return false; 221 } 222 223 public void addAncestorProfiles(StructureDefinition sd) { 224 if (sd.getDerivation().equals(StructureDefinition.TypeDerivationRule.CONSTRAINT)) { 225 StructureDefinition parentSd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 226 if (parentSd != null && !profiles.containsKey(parentSd)) { 227 ProfileUsage pu = new ProfileUsage(parentSd); 228 pu.setChecked(); // We're going to check the child, so no need to check the parent 229 profiles.put(parentSd, pu); 230 } 231 } 232 } 233 234 public List<ProfileUsage> uncheckedProfiles() { 235 List<ProfileUsage> uncheckedProfiles = new ArrayList<ProfileUsage>(); 236 for (ProfileUsage profileUsage : profiles.values()) { 237 if (!profileUsage.isChecked()) 238 uncheckedProfiles.add(profileUsage); 239 } 240 return uncheckedProfiles; 241 } 242 243 public boolean hasUncheckedProfiles() { 244 return !uncheckedProfiles().isEmpty(); 245 } 246 247 public void checkProfile(StructureDefinition profile) { 248 ProfileUsage profileUsage = profiles.get(profile); 249 if (profileUsage==null) 250 throw new Error("Can't check profile that hasn't been added: " + profile.getUrl()); 251 else 252 profileUsage.setChecked(); 253 } 254 } 255 256 public InstanceValidator(IWorkerContext theContext, IEvaluationContext hostServices) { 257 super(); 258 this.context = theContext; 259 fpe = new FHIRPathEngine(context); 260 fpe.setHostServices(hostServices); 261 source = Source.InstanceValidator; 262 } 263 264 public InstanceValidator(ValidationEngine engine) { 265 super(); 266 this.context = engine.getContext(); 267 fpe = engine.getFpe(); 268 source = Source.InstanceValidator; 269 } 270 271 @Override 272 public boolean isNoInvariantChecks() { 273 return noInvariantChecks; 274 } 275 276 @Override 277 public IResourceValidator setNoInvariantChecks(boolean value) { 278 this.noInvariantChecks = value; 279 return this; 280 } 281 282 public IValidatorResourceFetcher getFetcher() { 283 return this.fetcher; 284 } 285 286 public IResourceValidator setFetcher(IValidatorResourceFetcher value) { 287 this.fetcher = value; 288 return this; 289 } 290 291 private boolean allowUnknownExtension(String url) { 292 if (url.contains("example.org") || url.contains("acme.com") || url.contains("nema.org") || url.startsWith("http://hl7.org/fhir/tools/StructureDefinition/") || url.equals("http://hl7.org/fhir/StructureDefinition/structuredefinition-expression")) 293 // Added structuredefinition-expression explicitly because it wasn't defined in the version of the spec it needs to be used with 294 return true; 295 for (String s : extensionDomains) 296 if (url.startsWith(s)) 297 return true; 298 return anyExtensionsAllowed; 299 } 300 301 private boolean isKnownExtension(String url) { 302 // Added structuredefinition-expression explicitly because it wasn't defined in the version of the spec it needs to be used with 303 if (url.contains("example.org") || url.contains("acme.com") || url.contains("nema.org") || url.startsWith("http://hl7.org/fhir/tools/StructureDefinition/") || url.equals("http://hl7.org/fhir/StructureDefinition/structuredefinition-expression")) 304 return true; 305 for (String s : extensionDomains) 306 if (url.startsWith(s)) 307 return true; 308 return false; 309 } 310 311 private void bpCheck(List<ValidationMessage> errors, IssueType invalid, int line, int col, String literalPath, boolean test, String message) { 312 if (bpWarnings != null) { 313 switch (bpWarnings) { 314 case Error: 315 rule(errors, invalid, line, col, literalPath, test, message); 316 case Warning: 317 warning(errors, invalid, line, col, literalPath, test, message); 318 case Hint: 319 hint(errors, invalid, line, col, literalPath, test, message); 320 default: // do nothing 321 } 322 } 323 } 324 325 @Override 326 public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, InputStream stream, FhirFormat format) throws FHIRException, IOException { 327 return validate(appContext, errors, stream, format, new ValidationProfileSet()); 328 } 329 330 @Override 331 public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, InputStream stream, FhirFormat format, String profile) throws FHIRException, IOException { 332 return validate(appContext, errors, stream, format, new ValidationProfileSet(profile, true)); 333 } 334 335 @Override 336 public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, InputStream stream, FhirFormat format, StructureDefinition profile) throws FHIRException, IOException { 337 return validate(appContext, errors, stream, format, new ValidationProfileSet(profile)); 338 } 339 340 @Override 341 public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, InputStream stream, FhirFormat format, ValidationProfileSet profiles) throws FHIRException, IOException { 342 ParserBase parser = Manager.makeParser(context, format); 343 if (parser instanceof XmlParser) 344 ((XmlParser) parser).setAllowXsiLocation(allowXsiLocation); 345 parser.setupValidation(ValidationPolicy.EVERYTHING, errors); 346 long t = System.nanoTime(); 347 Element e = parser.parse(stream); 348 loadTime = System.nanoTime() - t; 349 if (e != null) 350 validate(appContext, errors, e, profiles); 351 return e; 352 } 353 354 @Override 355 public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Resource resource) throws FHIRException, IOException { 356 return validate(appContext, errors, resource, new ValidationProfileSet()); 357 } 358 359 @Override 360 public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Resource resource, String profile) throws FHIRException, IOException { 361 return validate(appContext, errors, resource, new ValidationProfileSet(profile, true)); 362 } 363 364 @Override 365 public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Resource resource, StructureDefinition profile) throws FHIRException, IOException { 366 return validate(appContext, errors, resource, new ValidationProfileSet(profile)); 367 } 368 369 @Override 370 public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Resource resource, ValidationProfileSet profiles) throws FHIRException, IOException { 371 long t = System.nanoTime(); 372 Element e = new ObjectConverter(context).convert(resource); 373 loadTime = System.nanoTime() - t; 374 validate(appContext, errors, e, profiles); 375 return e; 376 } 377 378 @Override 379 public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, org.w3c.dom.Element element) throws FHIRException, IOException { 380 return validate(appContext, errors, element, new ValidationProfileSet()); 381 } 382 383 @Override 384 public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, org.w3c.dom.Element element, String profile) throws FHIRException, IOException { 385 return validate(appContext, errors, element, new ValidationProfileSet(profile, true)); 386 } 387 388 @Override 389 public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, org.w3c.dom.Element element, StructureDefinition profile) throws FHIRException, IOException { 390 return validate(appContext, errors, element, new ValidationProfileSet(profile)); 391 } 392 393 @Override 394 public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, org.w3c.dom.Element element, ValidationProfileSet profiles) throws FHIRException, IOException { 395 XmlParser parser = new XmlParser(context); 396 parser.setupValidation(ValidationPolicy.EVERYTHING, errors); 397 long t = System.nanoTime(); 398 Element e = parser.parse(element); 399 loadTime = System.nanoTime() - t; 400 if (e != null) 401 validate(appContext, errors, e, profiles); 402 return e; 403 } 404 405 @Override 406 public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Document document) throws FHIRException, IOException { 407 return validate(appContext, errors, document, new ValidationProfileSet()); 408 } 409 410 @Override 411 public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Document document, String profile) throws FHIRException, IOException { 412 return validate(appContext, errors, document, new ValidationProfileSet(profile, true)); 413 } 414 415 @Override 416 public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Document document, StructureDefinition profile) throws FHIRException, IOException { 417 return validate(appContext, errors, document, new ValidationProfileSet(profile)); 418 } 419 420 @Override 421 public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Document document, ValidationProfileSet profiles) throws FHIRException, IOException { 422 XmlParser parser = new XmlParser(context); 423 parser.setupValidation(ValidationPolicy.EVERYTHING, errors); 424 long t = System.nanoTime(); 425 Element e = parser.parse(document); 426 loadTime = System.nanoTime() - t; 427 if (e != null) 428 validate(appContext, errors, e, profiles); 429 return e; 430 } 431 432 @Override 433 public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, JsonObject object) throws FHIRException, IOException { 434 return validate(appContext, errors, object, new ValidationProfileSet()); 435 } 436 437 @Override 438 public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, JsonObject object, String profile) throws FHIRException, IOException { 439 return validate(appContext, errors, object, new ValidationProfileSet(profile, true)); 440 } 441 442 @Override 443 public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, JsonObject object, StructureDefinition profile) throws FHIRException, IOException { 444 return validate(appContext, errors, object, new ValidationProfileSet(profile)); 445 } 446 447 @Override 448 public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, JsonObject object, ValidationProfileSet profiles) throws FHIRException, IOException { 449 JsonParser parser = new JsonParser(context); 450 parser.setupValidation(ValidationPolicy.EVERYTHING, errors); 451 long t = System.nanoTime(); 452 Element e = parser.parse(object); 453 loadTime = System.nanoTime() - t; 454 if (e != null) 455 validate(appContext, errors, e, profiles); 456 return e; 457 } 458 459 @Override 460 public void validate(Object appContext, List<ValidationMessage> errors, Element element) throws FHIRException, IOException { 461 ValidationProfileSet profileSet = new ValidationProfileSet(); 462 validate(appContext, errors, element, profileSet); 463 } 464 465 private void validateRemainder(Object appContext, List<ValidationMessage> errors) throws IOException, FHIRException { 466 boolean processedResource; 467 do { 468 processedResource = false; 469 Set<Element> keys = new HashSet<Element>(); 470 keys.addAll(resourceProfilesMap.keySet()); 471 for (Element resource : keys) { 472 ResourceProfiles rp = resourceProfilesMap.get(resource); 473 if (rp.hasUncheckedProfiles()) { 474 processedResource = true; 475 start(appContext, errors, rp.getOwner(), resource, null, rp.getStack()); 476 } 477 } 478 } while (processedResource); 479 } 480 481 @Override 482 public void validate(Object appContext, List<ValidationMessage> errors, Element element, String profile) throws FHIRException, IOException { 483 validate(appContext, errors, element, new ValidationProfileSet(profile, true)); 484 } 485 486 @Override 487 public void validate(Object appContext, List<ValidationMessage> errors, Element element, StructureDefinition profile) throws FHIRException, IOException { 488 validate(appContext, errors, element, new ValidationProfileSet(profile)); 489 } 490 491 @Override 492 public void validate(Object appContext, List<ValidationMessage> errors, Element element, ValidationProfileSet profiles) throws FHIRException, IOException { 493 // this is the main entry point; all the other entry points end up here coming here... 494 long t = System.nanoTime(); 495 boolean isRoot = false; 496 if (resourceProfilesMap == null) { 497 resourceProfilesMap = new HashMap<Element, ResourceProfiles>(); 498 isRoot = true; 499 } 500 validateResource(appContext, errors, element, element, null, profiles, resourceIdRule, new NodeStack(element)); 501 if (isRoot) { 502 validateRemainder(appContext, errors); 503 resourceProfilesMap = null; 504 } 505 overall = System.nanoTime() - t; 506 } 507 508 509 private boolean check(String v1, String v2) { 510 return v1 == null ? Utilities.noString(v1) : v1.equals(v2); 511 } 512 513 private void checkAddress(List<ValidationMessage> errors, String path, Element focus, Address fixed) { 514 checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), "use", focus); 515 checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), "text", focus); 516 checkFixedValue(errors, path + ".city", focus.getNamedChild("city"), fixed.getCityElement(), "city", focus); 517 checkFixedValue(errors, path + ".state", focus.getNamedChild("state"), fixed.getStateElement(), "state", focus); 518 checkFixedValue(errors, path + ".country", focus.getNamedChild("country"), fixed.getCountryElement(), "country", focus); 519 checkFixedValue(errors, path + ".zip", focus.getNamedChild("zip"), fixed.getPostalCodeElement(), "postalCode", focus); 520 521 List<Element> lines = new ArrayList<Element>(); 522 focus.getNamedChildren("line", lines); 523 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, lines.size() == fixed.getLine().size(), 524 "Expected " + Integer.toString(fixed.getLine().size()) + " but found " + Integer.toString(lines.size()) + " line elements")) { 525 for (int i = 0; i < lines.size(); i++) 526 checkFixedValue(errors, path + ".coding", lines.get(i), fixed.getLine().get(i), "coding", focus); 527 } 528 } 529 530 private void checkAttachment(List<ValidationMessage> errors, String path, Element focus, Attachment fixed) { 531 checkFixedValue(errors, path + ".contentType", focus.getNamedChild("contentType"), fixed.getContentTypeElement(), "contentType", focus); 532 checkFixedValue(errors, path + ".language", focus.getNamedChild("language"), fixed.getLanguageElement(), "language", focus); 533 checkFixedValue(errors, path + ".data", focus.getNamedChild("data"), fixed.getDataElement(), "data", focus); 534 checkFixedValue(errors, path + ".url", focus.getNamedChild("url"), fixed.getUrlElement(), "url", focus); 535 checkFixedValue(errors, path + ".size", focus.getNamedChild("size"), fixed.getSizeElement(), "size", focus); 536 checkFixedValue(errors, path + ".hash", focus.getNamedChild("hash"), fixed.getHashElement(), "hash", focus); 537 checkFixedValue(errors, path + ".title", focus.getNamedChild("title"), fixed.getTitleElement(), "title", focus); 538 } 539 540 // public API 541 542 private boolean checkCode(List<ValidationMessage> errors, Element element, String path, String code, String system, String display) throws TerminologyServiceException { 543 long t = System.nanoTime(); 544 boolean ss = context.supportsSystem(system); 545 txTime = txTime + (System.nanoTime() - t); 546 if (ss) { 547 t = System.nanoTime(); 548 ValidationResult s = context.validateCode(system, code, display); 549 txTime = txTime + (System.nanoTime() - t); 550 if (s == null || s.isOk()) 551 return true; 552 if (s.getSeverity() == IssueSeverity.INFORMATION) 553 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage()); 554 else if (s.getSeverity() == IssueSeverity.WARNING) 555 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage()); 556 else 557 return rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage()); 558 return true; 559 } else if (system.startsWith("http://hl7.org/fhir")) { 560 if (system.equals("http://hl7.org/fhir/sid/icd-10")) 561 return true; // else don't check ICD-10 (for now) 562 else { 563 CodeSystem cs = getCodeSystem(system); 564 if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, cs != null, "Unknown Code System " + system)) { 565 ConceptDefinitionComponent def = getCodeDefinition(cs, code); 566 if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, def != null, "Unknown Code (" + system + "#" + code + ")")) 567 return warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, display == null || display.equals(def.getDisplay()), "Display should be '" + def.getDisplay() + "'"); 568 } 569 return false; 570 } 571 } else if (system.startsWith("http://loinc.org")) { 572 return true; 573 } else if (system.startsWith("http://unitsofmeasure.org")) { 574 return true; 575 } else 576 return true; 577 } 578 579 private void checkCodeableConcept(List<ValidationMessage> errors, String path, Element focus, CodeableConcept fixed) { 580 checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), "text", focus); 581 List<Element> codings = new ArrayList<Element>(); 582 focus.getNamedChildren("coding", codings); 583 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, codings.size() == fixed.getCoding().size(), 584 "Expected " + Integer.toString(fixed.getCoding().size()) + " but found " + Integer.toString(codings.size()) + " coding elements")) { 585 for (int i = 0; i < codings.size(); i++) 586 checkFixedValue(errors, path + ".coding", codings.get(i), fixed.getCoding().get(i), "coding", focus); 587 } 588 } 589 590 private void checkCodeableConcept(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext) { 591 if (!noTerminologyChecks && theElementCntext != null && theElementCntext.hasBinding()) { 592 ElementDefinitionBindingComponent binding = theElementCntext.getBinding(); 593 if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, "Binding for " + path + " missing (cc)")) { 594 if (binding.hasValueSet() && binding.getValueSet() instanceof Reference) { 595 ValueSet valueset = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl()); 596 if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(binding.getValueSet()) + " not found")) { 597 try { 598 CodeableConcept cc = readAsCodeableConcept(element); 599 if (!cc.hasCoding()) { 600 if (binding.getStrength() == BindingStrength.REQUIRED) 601 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "No code provided, and a code is required from the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl()); 602 else if (binding.getStrength() == BindingStrength.EXTENSIBLE) 603 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "No code provided, and a code should be provided from the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl()); 604 } else { 605 long t = System.nanoTime(); 606 607 // Check whether the codes are appropriate for the type of binding we have 608 boolean bindingsOk = true; 609 if (binding.getStrength() != BindingStrength.EXAMPLE) { 610 boolean atLeastOneSystemIsSupported = false; 611 for (Coding nextCoding : cc.getCoding()) { 612 String nextSystem = nextCoding.getSystem(); 613 if (isNotBlank(nextSystem) && context.supportsSystem(nextSystem)) { 614 atLeastOneSystemIsSupported = true; 615 break; 616 } 617 } 618 619 if (!atLeastOneSystemIsSupported && binding.getStrength() == BindingStrength.EXAMPLE) { 620 // ignore this since we can't validate but it doesn't matter.. 621 } else { 622 ValidationResult vr = context.validateCode(cc, valueset); 623 if (!vr.isOk()) { 624 bindingsOk = false; 625 if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) { 626 if (binding.getStrength() == BindingStrength.REQUIRED) 627 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Could not confirm that the codes provided are in the value set " + describeReference(binding.getValueSet()) + " and a code from this value set is required (class = "+vr.getErrorClass().toString()+")"); 628 else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { 629 if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) 630 checkMaxValueSet(errors, path, element, profile, (Reference) binding.getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet").get(0).getValue(), cc); 631 else 632 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Could not confirm that the codes provided are in the value set " + describeReference(binding.getValueSet()) + " and a code should come from this value set unless it has no suitable code (class = "+vr.getErrorClass().toString()+")"); 633 } else if (binding.getStrength() == BindingStrength.PREFERRED) 634 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Could not confirm that the codes provided are in the value set " + describeReference(binding.getValueSet()) + " and a code is recommended to come from this value set (class = "+vr.getErrorClass().toString()+")"); 635 } else { 636 if (binding.getStrength() == BindingStrength.REQUIRED) 637 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "None of the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl()+", and a code from this value set is required) (codes = "+ccSummary(cc)+")"); 638 else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { 639 if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) 640 checkMaxValueSet(errors, path, element, profile, (Reference) binding.getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet").get(0).getValue(), cc); 641 else 642 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "None of the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code should come from this value set unless it has no suitable code) (codes = "+ccSummary(cc)+")"); 643 } else if (binding.getStrength() == BindingStrength.PREFERRED) 644 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "None of the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code is recommended to come from this value set) (codes = "+ccSummary(cc)+")"); 645 } 646 } else if (vr.getMessage()!=null) 647 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage()); 648 } 649 // Then, for any codes that are in code systems we are able 650 // to validate, we'll validate that the codes actually exist 651 if (bindingsOk) { 652 for (Coding nextCoding : cc.getCoding()) { 653 String nextCode = nextCoding.getCode(); 654 String nextSystem = nextCoding.getSystem(); 655 if (isNotBlank(nextCode) && isNotBlank(nextSystem) && context.supportsSystem(nextSystem)) { 656 ValidationResult vr = context.validateCode(nextSystem, nextCode, null); 657 if (!vr.isOk()) { 658 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Code {0} is not a valid code in code system {1}", nextCode, nextSystem); 659 } 660 } 661 } 662 } 663 txTime = txTime + (System.nanoTime() - t); 664 } 665 } 666 } catch (Exception e) { 667 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error "+e.getMessage()+" validating CodeableConcept"); 668 } 669 } 670 } else if (binding.hasValueSet()) { 671 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding by URI reference cannot be checked"); 672 } else if (!noBindingMsgSuppressed) { 673 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding for path " + path + " has no source, so can't be checked"); 674 } 675 } 676 } 677 } 678 679 private void checkMaxValueSet(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, Reference maxVSUrl, CodeableConcept cc) { 680 // TODO Auto-generated method stub 681 ValueSet valueset = resolveBindingReference(profile, maxVSUrl, profile.getUrl()); 682 if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(maxVSUrl) + " not found")) { 683 try { 684 long t = System.nanoTime(); 685 ValidationResult vr = context.validateCode(cc, valueset); 686 txTime = txTime + (System.nanoTime() - t); 687 if (!vr.isOk()) { 688 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "None of the codes provided are in the maximum value set " + describeReference(maxVSUrl) + " (" + valueset.getUrl()+", and a code from this value set is required) (codes = "+ccSummary(cc)+")"); 689 } 690 } catch (Exception e) { 691 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error "+e.getMessage()+" validating CodeableConcept using maxValueSet"); 692 } 693 } 694 } 695 696 private void checkMaxValueSet(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, Reference maxVSUrl, Coding c) { 697 // TODO Auto-generated method stub 698 ValueSet valueset = resolveBindingReference(profile, maxVSUrl, profile.getUrl()); 699 if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(maxVSUrl) + " not found")) { 700 try { 701 long t = System.nanoTime(); 702 ValidationResult vr = context.validateCode(c, valueset); 703 txTime = txTime + (System.nanoTime() - t); 704 if (!vr.isOk()) { 705 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The code provided is not in the maximum value set " + describeReference(maxVSUrl) + " (" + valueset.getUrl()+", and a code from this value set is required) (code = "+c.getSystem()+"#"+c.getCode()+")"); 706 } 707 } catch (Exception e) { 708 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error "+e.getMessage()+" validating CodeableConcept using maxValueSet"); 709 } 710 } 711 } 712 713 private void checkMaxValueSet(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, Reference maxVSUrl, String value) { 714 // TODO Auto-generated method stub 715 ValueSet valueset = resolveBindingReference(profile, maxVSUrl, profile.getUrl()); 716 if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(maxVSUrl) + " not found")) { 717 try { 718 long t = System.nanoTime(); 719 ValidationResult vr = context.validateCode(null, value, null, valueset); 720 txTime = txTime + (System.nanoTime() - t); 721 if (!vr.isOk()) { 722 if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) 723 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The code provided could not be validated against the maximum value set " + describeReference(maxVSUrl) + " (" + valueset.getUrl()+"), (error = "+vr.getMessage()+")"); 724 else 725 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The code provided is not in the maximum value set " + describeReference(maxVSUrl) + " (" + valueset.getUrl()+"), and a code from this value set is required) (code = "+value+"), (error = "+vr.getMessage()+")"); 726 } 727 } catch (Exception e) { 728 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error "+e.getMessage()+" validating CodeableConcept using maxValueSet"); 729 } 730 } 731 } 732 733 private String ccSummary(CodeableConcept cc) { 734 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 735 for (Coding c : cc.getCoding()) 736 b.append(c.getSystem()+"#"+c.getCode()); 737 return b.toString(); 738 } 739 740 private CodeableConcept readAsCodeableConcept(Element element) { 741 CodeableConcept cc = new CodeableConcept(); 742 List<Element> list = new ArrayList<Element>(); 743 element.getNamedChildren("coding", list); 744 for (Element item : list) 745 cc.addCoding(readAsCoding(item)); 746 cc.setText(element.getNamedChildValue("text")); 747 return cc; 748 } 749 750 private Coding readAsCoding(Element item) { 751 Coding c = new Coding(); 752 c.setSystem(item.getNamedChildValue("system")); 753 c.setVersion(item.getNamedChildValue("version")); 754 c.setCode(item.getNamedChildValue("code")); 755 c.setDisplay(item.getNamedChildValue("display")); 756 return c; 757 } 758 759 private Reference readAsReference(Element item) { 760 Reference r = new Reference(); 761 r.setDisplay(item.getNamedChildValue("display")); 762 r.setReference(item.getNamedChildValue("reference")); 763 List<Element> identifier = item.getChildrenByName("identifier"); 764 if (identifier.isEmpty() == false) { 765 r.setIdentifier(readAsIdentifier(identifier.get(0))); 766 } 767 return r; 768 } 769 770 private Identifier readAsIdentifier(Element item) { 771 Identifier r = new Identifier(); 772 r.setSystem(item.getNamedChildValue("system")); 773 r.setValue(item.getNamedChildValue("value")); 774 return r; 775 } 776 777 private void checkCoding(List<ValidationMessage> errors, String path, Element focus, Coding fixed) { 778 checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system", focus); 779 checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), "code", focus); 780 checkFixedValue(errors, path + ".display", focus.getNamedChild("display"), fixed.getDisplayElement(), "display", focus); 781 checkFixedValue(errors, path + ".userSelected", focus.getNamedChild("userSelected"), fixed.getUserSelectedElement(), "userSelected", focus); 782 } 783 784 private void checkCoding(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept) { 785 String code = element.getNamedChildValue("code"); 786 String system = element.getNamedChildValue("system"); 787 String display = element.getNamedChildValue("display"); 788 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isAbsolute(system), "Coding.system must be an absolute reference, not a local reference"); 789 790 if (system != null && code != null && !noTerminologyChecks) { 791 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, !isValueSet(system), "The Coding references a value set, not a code system (\""+system+"\")"); 792 try { 793 if (checkCode(errors, element, path, code, system, display)) 794 if (theElementCntext != null && theElementCntext.hasBinding()) { 795 ElementDefinitionBindingComponent binding = theElementCntext.getBinding(); 796 if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, "Binding for " + path + " missing")) { 797 if (binding.hasValueSet() && binding.getValueSet() instanceof Reference) { 798 ValueSet valueset = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl()); 799 if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(binding.getValueSet()) + " not found")) { 800 try { 801 Coding c = readAsCoding(element); 802 long t = System.nanoTime(); 803 ValidationResult vr = context.validateCode(c, valueset); 804 txTime = txTime + (System.nanoTime() - t); 805 if (!vr.isOk()) { 806 if (vr.IsNoService()) 807 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided could not be validated in the absence of a terminology server"); 808 else if (vr.getErrorClass() != null && !vr.getErrorClass().isInfrastructure()) { 809 if (binding.getStrength() == BindingStrength.REQUIRED) 810 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Could not confirm that the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl()+", and a code from this value set is required)"); 811 else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { 812 if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) 813 checkMaxValueSet(errors, path, element, profile, (Reference) binding.getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet").get(0).getValue(), c); 814 else 815 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Could not confirm that the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code should come from this value set unless it has no suitable code)"); 816 } else if (binding.getStrength() == BindingStrength.PREFERRED) 817 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Could not confirm that the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code is recommended to come from this value set)"); 818 } else if (binding.getStrength() == BindingStrength.REQUIRED) 819 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The Coding provided is not in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code is required from this value set)"+(vr.getMessage() != null ? " (error message = "+vr.getMessage()+")" : "")); 820 else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { 821 if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) 822 checkMaxValueSet(errors, path, element, profile, (Reference) binding.getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet").get(0).getValue(), c); 823 else 824 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The Coding provided is not in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code should come from this value set unless it has no suitable code)"+(vr.getMessage() != null ? " (error message = "+vr.getMessage()+")" : "")); 825 } else if (binding.getStrength() == BindingStrength.PREFERRED) 826 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The Coding provided is not in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code is recommended to come from this value set)"+(vr.getMessage() != null ? " (error message = "+vr.getMessage()+")" : "")); 827 } 828 } catch (Exception e) { 829 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error "+e.getMessage()+" validating Coding"); 830 } 831 } 832 } else if (binding.hasValueSet()) { 833 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding by URI reference cannot be checked"); 834 } else if (!inCodeableConcept && !noBindingMsgSuppressed) { 835 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding for path " + path + " has no source, so can't be checked"); 836 } 837 } 838 } 839 } catch (Exception e) { 840 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error "+e.getMessage()+" validating Coding"); 841 } 842 } 843 } 844 845 private boolean isValueSet(String url) { 846 try { 847 ValueSet vs = context.fetchResourceWithException(ValueSet.class, url); 848 return vs != null; 849 } catch (Exception e) { 850 return false; 851 } 852 } 853 854 private void checkContactPoint(List<ValidationMessage> errors, String path, Element focus, ContactPoint fixed) { 855 checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system", focus); 856 checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), "value", focus); 857 checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), "use", focus); 858 checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), "period", focus); 859 860 } 861 862 protected void checkDeclaredProfiles(ResourceProfiles resourceProfiles, List<ValidationMessage> errors, Element resource, Element element, NodeStack stack) throws FHIRException { 863 Element meta = element.getNamedChild("meta"); 864 if (meta != null) { 865 List<Element> profiles = new ArrayList<Element>(); 866 meta.getNamedChildren("profile", profiles); 867 int i = 0; 868 for (Element profile : profiles) { 869 String ref = profile.primitiveValue(); 870 String p = stack.addToLiteralPath("meta", "profile", ":" + Integer.toString(i)); 871 if (rule(errors, IssueType.INVALID, element.line(), element.col(), p, !Utilities.noString(ref), "StructureDefinition reference invalid")) { 872 long t = System.nanoTime(); 873 resourceProfiles.addProfile(errors, ref, errorForUnknownProfiles, p, element); 874 i++; 875 } 876 } 877 } 878 } 879 880 private StructureDefinition checkExtension(Object appContext, List<ValidationMessage> errors, String path, Element resource, Element element, ElementDefinition def, StructureDefinition profile, NodeStack stack) throws FHIRException, IOException { 881 String url = element.getNamedChildValue("url"); 882 boolean isModifier = element.getName().equals("modifierExtension"); 883 884 long t = System.nanoTime(); 885 StructureDefinition ex = context.fetchResource(StructureDefinition.class, url); 886 sdTime = sdTime + (System.nanoTime() - t); 887 if (ex == null) { 888 if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, allowUnknownExtension(url), "The extension " + url + " is unknown, and not allowed here")) 889 hint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, isKnownExtension(url), "Unknown extension " + url); 890 } else { 891 if (def.getIsModifier()) 892 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", ex.getSnapshot().getElement().get(0).getIsModifier(), 893 "Extension modifier mismatch: the extension element is labelled as a modifier, but the underlying extension is not"); 894 else 895 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", !ex.getSnapshot().getElement().get(0).getIsModifier(), 896 "Extension modifier mismatch: the extension element is not labelled as a modifier, but the underlying extension is"); 897 898 // two questions 899 // 1. can this extension be used here? 900 checkExtensionContext(errors, element, /* path+"[url='"+url+"']", */ ex, stack, ex.getUrl()); 901 902 if (isModifier) 903 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", ex.getSnapshot().getElement().get(0).getIsModifier(), 904 "The Extension '" + url + "' must be used as a modifierExtension"); 905 else 906 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", !ex.getSnapshot().getElement().get(0).getIsModifier(), 907 "The Extension '" + url + "' must not be used as an extension (it's a modifierExtension)"); 908 909 // check the type of the extension: 910 Set<String> allowedTypes = listExtensionTypes(ex); 911 String actualType = getExtensionType(element); 912 if (actualType == null) 913 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", allowedTypes.isEmpty(), "The Extension '" + url + "' definition is for a complex extension, so it cannot contain a value"); 914 else 915 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", allowedTypes.contains(actualType), "The Extension '" + url + "' definition allows for the types "+allowedTypes.toString()+" but found type "+actualType); 916 917 // 3. is the content of the extension valid? 918 validateElement(appContext, errors, ex, ex.getSnapshot().getElement().get(0), null, null, resource, element, "Extension", stack, false); 919 920 } 921 return ex; 922 } 923 924 private String getExtensionType(Element element) { 925 for (Element e : element.getChildren()) { 926 if (e.getName().startsWith("value")) { 927 String tn = e.getName().substring(5); 928 String ltn = Utilities.uncapitalize(tn); 929 if (isPrimitiveType(ltn)) 930 return ltn; 931 else 932 return tn; 933 } 934 } 935 return null; 936 } 937 938 private Set<String> listExtensionTypes(StructureDefinition ex) { 939 ElementDefinition vd = null; 940 for (ElementDefinition ed : ex.getSnapshot().getElement()) { 941 if (ed.getPath().startsWith("Extension.value")) { 942 vd = ed; 943 break; 944 } 945 } 946 Set<String> res = new HashSet<String>(); 947 if (vd != null && !"0".equals(vd.getMax())) { 948 for (TypeRefComponent tr : vd.getType()) { 949 res.add(tr.getCode()); 950 } 951 } 952 return res; 953 } 954 955 private boolean checkExtensionContext(List<ValidationMessage> errors, Element element, StructureDefinition definition, NodeStack stack, String extensionParent) { 956 String extUrl = definition.getUrl(); 957 CommaSeparatedStringBuilder p = new CommaSeparatedStringBuilder(); 958 for (String lp : stack.getLogicalPaths()) 959 p.append(lp); 960 if (definition.getContextType() == ExtensionContext.DATATYPE) { 961 boolean ok = false; 962 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 963 for (StringType ct : definition.getContext()) { 964 b.append(ct.getValue()); 965 if (ct.getValue().equals("*") || stack.getLogicalPaths().contains(ct.getValue() + ".extension")) 966 ok = true; 967 } 968 return rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), ok, 969 "The extension " + extUrl + " is not allowed to be used on the logical path set [" + p.toString() + "] (allowed: datatype=" + b.toString() + ")"); 970 } else if (definition.getContextType() == ExtensionContext.EXTENSION) { 971 boolean ok = false; 972 for (StringType ct : definition.getContext()) 973 if (ct.getValue().equals("*") || ct.getValue().equals(extensionParent)) 974 ok = true; 975 return rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), ok, 976 "The extension " + extUrl + " is not allowed to be used with the extension '" + extensionParent + "'"); 977 } else if (definition.getContextType() == ExtensionContext.RESOURCE) { 978 boolean ok = false; 979 // String simplePath = container.getPath(); 980 // System.out.println(simplePath); 981 // if (effetive.endsWith(".extension") || simplePath.endsWith(".modifierExtension")) 982 // simplePath = simplePath.substring(0, simplePath.lastIndexOf('.')); 983 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 984 for (StringType ct : definition.getContext()) { 985 String c = ct.getValue(); 986 b.append(c); 987 if (c.equals("*") || stack.getLogicalPaths().contains(c + ".extension") || (c.startsWith("@") && stack.getLogicalPaths().contains(c.substring(1) + ".extension"))) 988 ; 989 ok = true; 990 } 991 return rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), ok, 992 "The extension " + extUrl + " is not allowed to be used on the logical path set " + p.toString() + " (allowed: resource=" + b.toString() + ")"); 993 } else 994 throw new Error("Unknown context type"); 995 } 996 // 997 // private String simplifyPath(String path) { 998 // String s = path.replace("/f:", "."); 999 // while (s.contains("[")) 1000 // s = s.substring(0, s.indexOf("["))+s.substring(s.indexOf("]")+1); 1001 // String[] parts = s.split("\\."); 1002 // int i = 0; 1003 // while (i < parts.length && !context.getProfiles().containsKey(parts[i].toLowerCase())) 1004 // i++; 1005 // if (i >= parts.length) 1006 // throw new Error("Unable to process part "+path); 1007 // int j = parts.length - 1; 1008 // while (j > 0 && (parts[j].equals("extension") || parts[j].equals("modifierExtension"))) 1009 // j--; 1010 // StringBuilder b = new StringBuilder(); 1011 // boolean first = true; 1012 // for (int k = i; k <= j; k++) { 1013 // if (k == j || !parts[k].equals(parts[k+1])) { 1014 // if (first) 1015 // first = false; 1016 // else 1017 // b.append("."); 1018 // b.append(parts[k]); 1019 // } 1020 // } 1021 // return b.toString(); 1022 // } 1023 // 1024 1025 private void checkFixedValue(List<ValidationMessage> errors, String path, Element focus, org.hl7.fhir.dstu3.model.Element fixed, String propName, Element parent) { 1026 if ((fixed == null || fixed.isEmpty()) && focus == null) 1027 ; // this is all good 1028 else if (fixed == null && focus != null) 1029 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, false, "Unexpected element " + focus.getName()); 1030 else if (fixed != null && !fixed.isEmpty() && focus == null) 1031 rule(errors, IssueType.VALUE, parent == null ? -1 : parent.line(), parent == null ? -1 : parent.col(), path, false, "Missing element '" + propName+"'"); 1032 else { 1033 String value = focus.primitiveValue(); 1034 if (fixed instanceof org.hl7.fhir.dstu3.model.BooleanType) 1035 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.BooleanType) fixed).asStringValue(), value), 1036 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.BooleanType) fixed).asStringValue() + "'"); 1037 else if (fixed instanceof org.hl7.fhir.dstu3.model.IntegerType) 1038 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.IntegerType) fixed).asStringValue(), value), 1039 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.IntegerType) fixed).asStringValue() + "'"); 1040 else if (fixed instanceof org.hl7.fhir.dstu3.model.DecimalType) 1041 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.DecimalType) fixed).asStringValue(), value), 1042 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.DecimalType) fixed).asStringValue() + "'"); 1043 else if (fixed instanceof org.hl7.fhir.dstu3.model.Base64BinaryType) 1044 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.Base64BinaryType) fixed).asStringValue(), value), 1045 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.Base64BinaryType) fixed).asStringValue() + "'"); 1046 else if (fixed instanceof org.hl7.fhir.dstu3.model.InstantType) 1047 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.InstantType) fixed).getValue().toString(), value), 1048 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.InstantType) fixed).asStringValue() + "'"); 1049 else if (fixed instanceof org.hl7.fhir.dstu3.model.StringType) 1050 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.StringType) fixed).getValue(), value), 1051 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.StringType) fixed).getValue() + "'"); 1052 else if (fixed instanceof org.hl7.fhir.dstu3.model.UriType) 1053 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.UriType) fixed).getValue(), value), 1054 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.UriType) fixed).getValue() + "'"); 1055 else if (fixed instanceof org.hl7.fhir.dstu3.model.DateType) 1056 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.DateType) fixed).getValue().toString(), value), 1057 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.DateType) fixed).getValue() + "'"); 1058 else if (fixed instanceof org.hl7.fhir.dstu3.model.DateTimeType) 1059 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.DateTimeType) fixed).getValue().toString(), value), 1060 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.DateTimeType) fixed).getValue() + "'"); 1061 else if (fixed instanceof org.hl7.fhir.dstu3.model.OidType) 1062 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.OidType) fixed).getValue(), value), 1063 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.OidType) fixed).getValue() + "'"); 1064 else if (fixed instanceof org.hl7.fhir.dstu3.model.UuidType) 1065 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.UuidType) fixed).getValue(), value), 1066 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.UuidType) fixed).getValue() + "'"); 1067 else if (fixed instanceof org.hl7.fhir.dstu3.model.CodeType) 1068 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.CodeType) fixed).getValue(), value), 1069 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.CodeType) fixed).getValue() + "'"); 1070 else if (fixed instanceof org.hl7.fhir.dstu3.model.IdType) 1071 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.IdType) fixed).getValue(), value), 1072 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.IdType) fixed).getValue() + "'"); 1073 else if (fixed instanceof Quantity) 1074 checkQuantity(errors, path, focus, (Quantity) fixed); 1075 else if (fixed instanceof Address) 1076 checkAddress(errors, path, focus, (Address) fixed); 1077 else if (fixed instanceof ContactPoint) 1078 checkContactPoint(errors, path, focus, (ContactPoint) fixed); 1079 else if (fixed instanceof Attachment) 1080 checkAttachment(errors, path, focus, (Attachment) fixed); 1081 else if (fixed instanceof Identifier) 1082 checkIdentifier(errors, path, focus, (Identifier) fixed); 1083 else if (fixed instanceof Coding) 1084 checkCoding(errors, path, focus, (Coding) fixed); 1085 else if (fixed instanceof HumanName) 1086 checkHumanName(errors, path, focus, (HumanName) fixed); 1087 else if (fixed instanceof CodeableConcept) 1088 checkCodeableConcept(errors, path, focus, (CodeableConcept) fixed); 1089 else if (fixed instanceof Timing) 1090 checkTiming(errors, path, focus, (Timing) fixed); 1091 else if (fixed instanceof Period) 1092 checkPeriod(errors, path, focus, (Period) fixed); 1093 else if (fixed instanceof Range) 1094 checkRange(errors, path, focus, (Range) fixed); 1095 else if (fixed instanceof Ratio) 1096 checkRatio(errors, path, focus, (Ratio) fixed); 1097 else if (fixed instanceof SampledData) 1098 checkSampledData(errors, path, focus, (SampledData) fixed); 1099 1100 else 1101 rule(errors, IssueType.EXCEPTION, focus.line(), focus.col(), path, false, "Unhandled fixed value type " + fixed.getClass().getName()); 1102 List<Element> extensions = new ArrayList<Element>(); 1103 focus.getNamedChildren("extension", extensions); 1104 if (fixed.getExtension().size() == 0) { 1105 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, extensions.size() == 0, "No extensions allowed"); 1106 } else if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, extensions.size() == fixed.getExtension().size(), 1107 "Extensions count mismatch: expected " + Integer.toString(fixed.getExtension().size()) + " but found " + Integer.toString(extensions.size()))) { 1108 for (Extension e : fixed.getExtension()) { 1109 Element ex = getExtensionByUrl(extensions, e.getUrl()); 1110 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, ex != null, "Extension count mismatch: unable to find extension: " + e.getUrl())) { 1111 checkFixedValue(errors, path, ex.getNamedChild("extension").getNamedChild("value"), e.getValue(), "extension.value", ex.getNamedChild("extension")); 1112 } 1113 } 1114 } 1115 } 1116 } 1117 1118 private void checkHumanName(List<ValidationMessage> errors, String path, Element focus, HumanName fixed) { 1119 checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), "use", focus); 1120 checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), "text", focus); 1121 checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), "period", focus); 1122 1123 List<Element> parts = new ArrayList<Element>(); 1124 focus.getNamedChildren("family", parts); 1125 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() > 0 == fixed.hasFamily(), 1126 "Expected " + (fixed.hasFamily() ? "1" : "0") + " but found " + Integer.toString(parts.size()) + " family elements")) { 1127 for (int i = 0; i < parts.size(); i++) 1128 checkFixedValue(errors, path + ".family", parts.get(i), fixed.getFamilyElement(), "family", focus); 1129 } 1130 focus.getNamedChildren("given", parts); 1131 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getGiven().size(), 1132 "Expected " + Integer.toString(fixed.getGiven().size()) + " but found " + Integer.toString(parts.size()) + " given elements")) { 1133 for (int i = 0; i < parts.size(); i++) 1134 checkFixedValue(errors, path + ".given", parts.get(i), fixed.getGiven().get(i), "given", focus); 1135 } 1136 focus.getNamedChildren("prefix", parts); 1137 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getPrefix().size(), 1138 "Expected " + Integer.toString(fixed.getPrefix().size()) + " but found " + Integer.toString(parts.size()) + " prefix elements")) { 1139 for (int i = 0; i < parts.size(); i++) 1140 checkFixedValue(errors, path + ".prefix", parts.get(i), fixed.getPrefix().get(i), "prefix", focus); 1141 } 1142 focus.getNamedChildren("suffix", parts); 1143 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getSuffix().size(), 1144 "Expected " + Integer.toString(fixed.getSuffix().size()) + " but found " + Integer.toString(parts.size()) + " suffix elements")) { 1145 for (int i = 0; i < parts.size(); i++) 1146 checkFixedValue(errors, path + ".suffix", parts.get(i), fixed.getSuffix().get(i), "suffix", focus); 1147 } 1148 } 1149 1150 private void checkIdentifier(List<ValidationMessage> errors, String path, Element element, ElementDefinition context) { 1151 String system = element.getNamedChildValue("system"); 1152 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isAbsolute(system), "Identifier.system must be an absolute reference, not a local reference"); 1153 } 1154 1155 private void checkIdentifier(List<ValidationMessage> errors, String path, Element focus, Identifier fixed) { 1156 checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), "use", focus); 1157 checkFixedValue(errors, path + ".type", focus.getNamedChild("type"), fixed.getType(), "type", focus); 1158 checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system", focus); 1159 checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), "value", focus); 1160 checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), "period", focus); 1161 checkFixedValue(errors, path + ".assigner", focus.getNamedChild("assigner"), fixed.getAssigner(), "assigner", focus); 1162 } 1163 1164 private void checkPeriod(List<ValidationMessage> errors, String path, Element focus, Period fixed) { 1165 checkFixedValue(errors, path + ".start", focus.getNamedChild("start"), fixed.getStartElement(), "start", focus); 1166 checkFixedValue(errors, path + ".end", focus.getNamedChild("end"), fixed.getEndElement(), "end", focus); 1167 } 1168 1169 private void checkPrimitive(Object appContext, List<ValidationMessage> errors, String path, String type, ElementDefinition context, Element e, StructureDefinition profile) throws FHIRException, IOException { 1170 if (isBlank(e.primitiveValue())) { 1171 rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(), "primitive types must have a value or must have child extensions"); 1172 return; 1173 } 1174 String regex = context.getExtensionString(ToolingExtensions.EXT_REGEX); 1175 if (regex!=null) 1176 rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().matches(regex), "Element value '" + e.primitiveValue() + "' does not meet regex '" + regex + "'"); 1177 1178 if (type.equals("boolean")) { 1179 rule(errors, IssueType.INVALID, e.line(), e.col(), path, "true".equals(e.primitiveValue()) || "false".equals(e.primitiveValue()), "boolean values must be 'true' or 'false'"); 1180 } 1181 if (type.equals("uri")) { 1182 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !e.primitiveValue().startsWith("oid:"), "URI values cannot start with oid:"); 1183 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !e.primitiveValue().startsWith("uuid:"), "URI values cannot start with uuid:"); 1184 rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().equals(e.primitiveValue().trim()), "URI values cannot have leading or trailing whitespace"); 1185 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength()==0 || e.primitiveValue().length() <= context.getMaxLength(), "value is longer than permitted maximum length of " + context.getMaxLength()); 1186 1187 // now, do we check the URI target? 1188 if (fetcher != null) { 1189 rule(errors, IssueType.INVALID, e.line(), e.col(), path, fetcher.resolveURL(appContext, path, e.primitiveValue()), "URL value '"+e.primitiveValue()+"' does not resolve"); 1190 } 1191 } 1192 if (type.equalsIgnoreCase("string") && e.hasPrimitiveValue()) { 1193 if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || e.primitiveValue().length() > 0, "@value cannot be empty")) { 1194 warning(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || e.primitiveValue().trim().equals(e.primitiveValue()), "value should not start or finish with whitespace"); 1195 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength()==0 || e.primitiveValue().length() <= context.getMaxLength(), "value is longer than permitted maximum length of " + context.getMaxLength()); 1196 } 1197 } 1198 if (type.equals("dateTime")) { 1199 rule(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), "The value '" + e.primitiveValue() + "' does not have a valid year"); 1200 rule(errors, IssueType.INVALID, e.line(), e.col(), path, 1201 e.primitiveValue() 1202 .matches("-?[0-9]{4}(-(0[1-9]|1[0-2])(-(0[0-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?)?)?"), 1203 "Not a valid date time"); 1204 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !hasTime(e.primitiveValue()) || hasTimeZone(e.primitiveValue()), "if a date has a time, it must have a timezone"); 1205 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength()==0 || e.primitiveValue().length() <= context.getMaxLength(), "value is longer than permitted maximum length of " + context.getMaxLength()); 1206 } 1207 if (type.equals("date")) { 1208 rule(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), "The value '" + e.primitiveValue() + "' does not have a valid year"); 1209 rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().matches("-?[0-9]{4}(-(0[1-9]|1[0-2])(-(0[0-9]|[1-2][0-9]|3[0-1]))?)?"), 1210 "Not a valid date"); 1211 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength()==0 || e.primitiveValue().length() <= context.getMaxLength(), "value is longer than permitted maximum value of " + context.getMaxLength()); 1212 } 1213 if (type.equals("integer")) { 1214 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxValueIntegerType() || !context.getMaxValueIntegerType().hasValue() || (context.getMaxValueIntegerType().getValue() >= new Integer(e.getValue()).intValue()), "value is greater than permitted maximum value of " + (context.hasMaxValueIntegerType() ? context.getMaxValueIntegerType() : "")); 1215 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMinValueIntegerType() || !context.getMinValueIntegerType().hasValue() || (context.getMinValueIntegerType().getValue() <= new Integer(e.getValue()).intValue()), "value is less than permitted minimum value of " + (context.hasMinValueIntegerType() ? context.getMinValueIntegerType() : "")); 1216 } 1217 if (type.equals("instant")) { 1218 rule(errors, IssueType.INVALID, e.line(), e.col(), path, 1219 e.primitiveValue().matches("-?[0-9]{4}-(0[1-9]|1[0-2])-(0[0-9]|[1-2][0-9]|3[0-1])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))"), 1220 "The instant '" + e.primitiveValue() + "' is not valid (by regex)"); 1221 rule(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), "The value '" + e.primitiveValue() + "' does not have a valid year"); 1222 } 1223 1224 if (type.equals("code") && e.primitiveValue() != null) { 1225 // Technically, a code is restricted to string which has at least one character and no leading or trailing whitespace, and where there is no whitespace 1226 // other than single spaces in the contents 1227 rule(errors, IssueType.INVALID, e.line(), e.col(), path, passesCodeWhitespaceRules(e.primitiveValue()), "The code '" + e.primitiveValue() + "' is not valid (whitespace rules)"); 1228 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength()==0 || e.primitiveValue().length() <= context.getMaxLength(), "value is longer than permitted maximum length of " + context.getMaxLength()); 1229 } 1230 1231 if (context.hasBinding() && e.primitiveValue() != null) { 1232 checkPrimitiveBinding(errors, path, type, context, e, profile); 1233 } 1234 1235 if (type.equals("xhtml")) { 1236 XhtmlNode xhtml = e.getXhtml(); 1237 if (xhtml != null) { // if it is null, this is an error already noted in the parsers 1238 // check that the namespace is there and correct. 1239 String ns = xhtml.getNsDecl(); 1240 rule(errors, IssueType.INVALID, e.line(), e.col(), path, FormatUtilities.XHTML_NS.equals(ns), "Wrong namespace on the XHTML ('"+ns+"')"); 1241 // check that inner namespaces are all correct 1242 checkInnerNS(errors, e, path, xhtml.getChildNodes()); 1243 rule(errors, IssueType.INVALID, e.line(), e.col(), path, "div".equals(xhtml.getName()), "Wrong name on the XHTML ('"+ns+"') - must start with div"); 1244 // check that no illegal elements and attributes have been used 1245 checkInnerNames(errors, e, path, xhtml.getChildNodes()); 1246 } 1247 } 1248 1249 if (context.hasFixed()) 1250 checkFixedValue(errors,path,e, context.getFixed(), context.getSliceName(), null); 1251 1252 // for nothing to check 1253 } 1254 1255 private void checkInnerNames(List<ValidationMessage> errors, Element e, String path, List<XhtmlNode> list) { 1256 for (XhtmlNode node : list) { 1257 if (node.getNodeType() == NodeType.Element) { 1258 rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.existsInList(node.getName(), 1259 "p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", "b", "em", "i", "strong", 1260 "small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup", 1261 "ul", "ol", "li", "dl", "dt", "dd", "pre", "table", "caption", "colgroup", "col", "thead", "tr", "tfoot", "tbody", "th", "td", 1262 "code", "samp", "img", "map", "area" 1263 1264 ), "Illegal element name in the XHTML ('"+node.getName()+"')"); 1265 for (String an : node.getAttributes().keySet()) { 1266 boolean ok = an.startsWith("xmlns") || Utilities.existsInList(an, 1267 "title", "style", "class", "id", "lang", "xml:lang", "dir", "accesskey", "tabindex", 1268 // tables 1269 "span", "width", "align", "valign", "char", "charoff", "abbr", "axis", "headers", "scope", "rowspan", "colspan") || 1270 1271 Utilities.existsInList(node.getName()+"."+an, "a.href", "a.name", "img.src", "img.border", "div.xmlns", "blockquote.cite", "q.cite", 1272 "a.charset", "a.type", "a.name", "a.href", "a.hreflang", "a.rel", "a.rev", "a.shape", "a.coords", "img.src", 1273 "img.alt", "img.longdesc", "img.height", "img.width", "img.usemap", "img.ismap", "map.name", "area.shape", 1274 "area.coords", "area.href", "area.nohref", "area.alt", "table.summary", "table.width", "table.border", 1275 "table.frame", "table.rules", "table.cellspacing", "table.cellpadding", "pre.space" 1276 ); 1277 if (!ok) 1278 rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, "Illegal attribute name in the XHTML ('"+an+"' on '"+node.getName()+"')"); 1279 } 1280 checkInnerNames(errors, e, path, node.getChildNodes()); 1281 } 1282 } 1283 } 1284 1285 private void checkInnerNS(List<ValidationMessage> errors, Element e, String path, List<XhtmlNode> list) { 1286 for (XhtmlNode node : list) { 1287 if (node.getNodeType() == NodeType.Element) { 1288 String ns = node.getNsDecl(); 1289 rule(errors, IssueType.INVALID, e.line(), e.col(), path, ns == null || FormatUtilities.XHTML_NS.equals(ns), "Wrong namespace on the XHTML ('"+ns+"')"); 1290 checkInnerNS(errors, e, path, node.getChildNodes()); 1291 } 1292 } 1293 } 1294 1295 private void checkPrimitiveBinding(List<ValidationMessage> errors, String path, String type, ElementDefinition elementContext, Element element, StructureDefinition profile) { 1296 // We ignore bindings that aren't on string, uri or code 1297 if (!element.hasPrimitiveValue() || !("code".equals(type) || "string".equals(type) || "uri".equals(type))) { 1298 return; 1299 } 1300 if (noTerminologyChecks) 1301 return; 1302 1303 String value = element.primitiveValue(); 1304 // System.out.println("check "+value+" in "+path); 1305 1306 // firstly, resolve the value set 1307 ElementDefinitionBindingComponent binding = elementContext.getBinding(); 1308 if (binding.hasValueSet() && binding.getValueSet() instanceof Reference) { 1309 ValueSet vs = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl()); 1310 if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null, "ValueSet {0} not found", describeReference(binding.getValueSet()))) { 1311 long t = System.nanoTime(); 1312 ValidationResult vr = context.validateCode(null, value, null, vs); 1313 txTime = txTime + (System.nanoTime() - t); 1314 if (vr != null && !vr.isOk()) { 1315 if (vr.IsNoService()) 1316 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided ('"+value+"') could not be validated in the absence of a terminology server"); 1317 else if (binding.getStrength() == BindingStrength.REQUIRED) 1318 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided ('"+value+"') is not in the value set " + describeReference(binding.getValueSet()) + " (" + vs.getUrl() + ", and a code is required from this value set)"+(vr.getMessage() != null ? " (error message = "+vr.getMessage()+")" : "")); 1319 else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { 1320 if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) 1321 checkMaxValueSet(errors, path, element, profile, (Reference) binding.getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet").get(0).getValue(), value); 1322 else 1323 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided ('"+value+"') is not in the value set " + describeReference(binding.getValueSet()) + " (" + vs.getUrl() + ", and a code should come from this value set unless it has no suitable code)"+(vr.getMessage() != null ? " (error message = "+vr.getMessage()+")" : "")); 1324 } else if (binding.getStrength() == BindingStrength.PREFERRED) 1325 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided ('"+value+"') is not in the value set " + describeReference(binding.getValueSet()) + " (" + vs.getUrl() + ", and a code is recommended to come from this value set)"+(vr.getMessage() != null ? " (error message = "+vr.getMessage()+")" : "")); 1326 } 1327 } 1328 } else if (!noBindingMsgSuppressed) 1329 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, !type.equals("code"), "Binding has no source, so can't be checked"); 1330 } 1331 1332 private void checkQuantity(List<ValidationMessage> errors, String path, Element focus, Quantity fixed) { 1333 checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), "value", focus); 1334 checkFixedValue(errors, path + ".comparator", focus.getNamedChild("comparator"), fixed.getComparatorElement(), "comparator", focus); 1335 checkFixedValue(errors, path + ".units", focus.getNamedChild("unit"), fixed.getUnitElement(), "units", focus); 1336 checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system", focus); 1337 checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), "code", focus); 1338 } 1339 1340 // implementation 1341 1342 private void checkRange(List<ValidationMessage> errors, String path, Element focus, Range fixed) { 1343 checkFixedValue(errors, path + ".low", focus.getNamedChild("low"), fixed.getLow(), "low", focus); 1344 checkFixedValue(errors, path + ".high", focus.getNamedChild("high"), fixed.getHigh(), "high", focus); 1345 1346 } 1347 1348 private void checkRatio(List<ValidationMessage> errors, String path, Element focus, Ratio fixed) { 1349 checkFixedValue(errors, path + ".numerator", focus.getNamedChild("numerator"), fixed.getNumerator(), "numerator", focus); 1350 checkFixedValue(errors, path + ".denominator", focus.getNamedChild("denominator"), fixed.getDenominator(), "denominator", focus); 1351 } 1352 1353 private void checkReference(Object appContext, List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, ElementDefinition container, String parentType, NodeStack stack) throws FHIRException, IOException { 1354 Reference reference = readAsReference(element); 1355 1356 String ref = reference.getReference(); 1357 if (Utilities.noString(ref)) { 1358 if (Utilities.noString(reference.getIdentifier().getSystem()) && Utilities.noString(reference.getIdentifier().getValue())) { 1359 warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, !Utilities.noString(element.getNamedChildValue("display")), "A Reference without an actual reference or identifier should have a display"); 1360 } 1361 return; 1362 } 1363 1364 Element we = localResolve(ref, stack, errors, path); 1365 String refType; 1366 if (ref.startsWith("#")) { 1367 refType = "contained"; 1368 } else { 1369 if (we == null) { 1370 refType = "remote"; 1371 } else { 1372 refType = "bundle"; 1373 } 1374 } 1375 String ft; 1376 if (we != null) 1377 ft = we.getType(); 1378 else 1379 ft = tryParse(ref); 1380 ReferenceValidationPolicy pol = refType.equals("contained") ? ReferenceValidationPolicy.CHECK_VALID : fetcher == null ? ReferenceValidationPolicy.IGNORE : fetcher.validationPolicy(appContext, path, ref); 1381 1382 if (pol.checkExists()) { 1383 if (we == null) { 1384 if (fetcher == null) 1385 throw new FHIRException("Resource resolution services not provided"); 1386 we = fetcher.fetch(appContext, ref); 1387 } 1388 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, we != null, "Unable to resolve resource '"+ref+"'"); 1389 } 1390 1391 if (we != null && pol.checkType()) { 1392 if (warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ft!=null, "Unable to determine type of target resource")) { 1393 boolean ok = false; 1394 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1395 for (TypeRefComponent type : container.getType()) { 1396 if (!ok && type.getCode().equals("Reference")) { 1397 // we validate as much as we can. First, can we infer a type from the profile? 1398 if (!type.hasTargetProfile() || type.getTargetProfile().equals("http://hl7.org/fhir/StructureDefinition/Resource")) 1399 ok = true; 1400 else { 1401 String pr = type.getTargetProfile(); 1402 1403 String bt = getBaseType(profile, pr); 1404 StructureDefinition sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + bt); 1405 if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, bt != null, "Unable to resolve the profile reference '" + pr + "'")) { 1406 b.append(bt); 1407 ok = bt.equals(ft); 1408 if (ok && we!=null && pol.checkValid()) { 1409 doResourceProfile(appContext, we, pr, errors, stack.push(we, -1, null, null), path, element); 1410 } 1411 } else 1412 ok = true; // suppress following check 1413 if (ok && type.hasAggregation()) { 1414 boolean modeOk; 1415 for (Enumeration<AggregationMode> mode : type.getAggregation()) { 1416 if (mode.getValue().equals(AggregationMode.CONTAINED) && refType.equals("contained")) 1417 ok = true; 1418 else if (mode.getValue().equals(AggregationMode.BUNDLED) && refType.equals("bundled")) 1419 ok = true; 1420 else if (mode.getValue().equals(AggregationMode.REFERENCED) && (refType.equals("bundled")||refType.equals("remote"))) 1421 ok = true; 1422 } 1423 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ok, "Reference is " + refType + " which isn't supported by the specified aggregation mode(s) for the reference"); 1424 } 1425 } 1426 } 1427 if (!ok && type.getCode().equals("*")) { 1428 ok = true; // can refer to anything 1429 } 1430 } 1431 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ok, "Invalid Resource target type. Found " + ft + ", but expected one of (" + b.toString() + ")"); 1432 } 1433 } 1434 if (pol == ReferenceValidationPolicy.CHECK_VALID) { 1435 // todo.... 1436 } 1437 } 1438 1439 private void doResourceProfile(Object appContext, Element resource, String profile, List<ValidationMessage> errors, NodeStack stack, String path, Element element) throws FHIRException, IOException { 1440 ResourceProfiles resourceProfiles = addResourceProfile(errors, resource, profile, path, element, stack); 1441 if (resourceProfiles.isProcessed()) { 1442 start(appContext, errors, resource, resource, null, stack); 1443 } 1444 } 1445 1446 private ResourceProfiles getResourceProfiles(Element resource, NodeStack stack) { 1447 ResourceProfiles resourceProfiles = resourceProfilesMap.get(resource); 1448 if (resourceProfiles==null) { 1449 resourceProfiles = new ResourceProfiles(resource, stack); 1450 resourceProfilesMap.put(resource, resourceProfiles); 1451 } 1452 return resourceProfiles; 1453 } 1454 1455 private ResourceProfiles addResourceProfile(List<ValidationMessage> errors, Element resource, String profile, String path, Element element, NodeStack stack) { 1456 ResourceProfiles resourceProfiles = getResourceProfiles(resource, stack); 1457 resourceProfiles.addProfile(errors, profile, errorForUnknownProfiles, path, element); 1458 return resourceProfiles; 1459 } 1460 1461 private String checkResourceType(String type) { 1462 long t = System.nanoTime(); 1463 try { 1464 if (context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + type) != null) 1465 return type; 1466 else 1467 return null; 1468 } finally { 1469 sdTime = sdTime + (System.nanoTime() - t); 1470 } 1471 } 1472 1473 private void checkSampledData(List<ValidationMessage> errors, String path, Element focus, SampledData fixed) { 1474 checkFixedValue(errors, path + ".origin", focus.getNamedChild("origin"), fixed.getOrigin(), "origin", focus); 1475 checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriodElement(), "period", focus); 1476 checkFixedValue(errors, path + ".factor", focus.getNamedChild("factor"), fixed.getFactorElement(), "factor", focus); 1477 checkFixedValue(errors, path + ".lowerLimit", focus.getNamedChild("lowerLimit"), fixed.getLowerLimitElement(), "lowerLimit", focus); 1478 checkFixedValue(errors, path + ".upperLimit", focus.getNamedChild("upperLimit"), fixed.getUpperLimitElement(), "upperLimit", focus); 1479 checkFixedValue(errors, path + ".dimensions", focus.getNamedChild("dimensions"), fixed.getDimensionsElement(), "dimensions", focus); 1480 checkFixedValue(errors, path + ".data", focus.getNamedChild("data"), fixed.getDataElement(), "data", focus); 1481 } 1482 1483 private void checkTiming(List<ValidationMessage> errors, String path, Element focus, Timing fixed) { 1484 checkFixedValue(errors, path + ".repeat", focus.getNamedChild("repeat"), fixed.getRepeat(), "value", focus); 1485 1486 List<Element> events = new ArrayList<Element>(); 1487 focus.getNamedChildren("event", events); 1488 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, events.size() == fixed.getEvent().size(), 1489 "Expected " + Integer.toString(fixed.getEvent().size()) + " but found " + Integer.toString(events.size()) + " event elements")) { 1490 for (int i = 0; i < events.size(); i++) 1491 checkFixedValue(errors, path + ".event", events.get(i), fixed.getEvent().get(i), "event", focus); 1492 } 1493 } 1494 1495 private boolean codeinExpansion(ValueSetExpansionContainsComponent cnt, String system, String code) { 1496 for (ValueSetExpansionContainsComponent c : cnt.getContains()) { 1497 if (code.equals(c.getCode()) && system.equals(c.getSystem().toString())) 1498 return true; 1499 if (codeinExpansion(c, system, code)) 1500 return true; 1501 } 1502 return false; 1503 } 1504 1505 private boolean codeInExpansion(ValueSet vs, String system, String code) { 1506 for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) { 1507 if (code.equals(c.getCode()) && (system == null || system.equals(c.getSystem()))) 1508 return true; 1509 if (codeinExpansion(c, system, code)) 1510 return true; 1511 } 1512 return false; 1513 } 1514 1515 private String describeReference(Type reference) { 1516 if (reference == null) 1517 return "null"; 1518 if (reference instanceof UriType) 1519 return ((UriType) reference).getValue(); 1520 if (reference instanceof Reference) 1521 return ((Reference) reference).getReference(); 1522 return "??"; 1523 } 1524 1525 private String describeTypes(List<TypeRefComponent> types) { 1526 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1527 for (TypeRefComponent t : types) { 1528 b.append(t.getCode()); 1529 } 1530 return b.toString(); 1531 } 1532 1533 protected ElementDefinition findElement(StructureDefinition profile, String name) { 1534 for (ElementDefinition c : profile.getSnapshot().getElement()) { 1535 if (c.getPath().equals(name)) { 1536 return c; 1537 } 1538 } 1539 return null; 1540 } 1541 1542 public BestPracticeWarningLevel getBasePracticeWarningLevel() { 1543 return bpWarnings; 1544 } 1545 1546 private String getBaseType(StructureDefinition profile, String pr) { 1547 StructureDefinition p = resolveProfile(profile, pr); 1548 if (p == null) 1549 return null; 1550 else 1551 return p.getType(); 1552 } 1553 1554 @Override 1555 public CheckDisplayOption getCheckDisplay() { 1556 return checkDisplay; 1557 } 1558 1559 // private String findProfileTag(Element element) { 1560 // String uri = null; 1561 // List<Element> list = new ArrayList<Element>(); 1562 // element.getNamedChildren("category", list); 1563 // for (Element c : list) { 1564 // if ("http://hl7.org/fhir/tag/profile".equals(c.getAttribute("scheme"))) { 1565 // uri = c.getAttribute("term"); 1566 // } 1567 // } 1568 // return uri; 1569 // } 1570 1571 private ConceptDefinitionComponent getCodeDefinition(ConceptDefinitionComponent c, String code) { 1572 if (code.equals(c.getCode())) 1573 return c; 1574 for (ConceptDefinitionComponent g : c.getConcept()) { 1575 ConceptDefinitionComponent r = getCodeDefinition(g, code); 1576 if (r != null) 1577 return r; 1578 } 1579 return null; 1580 } 1581 1582 private ConceptDefinitionComponent getCodeDefinition(CodeSystem cs, String code) { 1583 for (ConceptDefinitionComponent c : cs.getConcept()) { 1584 ConceptDefinitionComponent r = getCodeDefinition(c, code); 1585 if (r != null) 1586 return r; 1587 } 1588 return null; 1589 } 1590 1591 private Element getContainedById(Element container, String id) { 1592 List<Element> contained = new ArrayList<Element>(); 1593 container.getNamedChildren("contained", contained); 1594 for (Element we : contained) { 1595 if (id.equals(we.getNamedChildValue("id"))) 1596 return we; 1597 } 1598 return null; 1599 } 1600 1601 public IWorkerContext getContext() { 1602 return context; 1603 } 1604 1605 private ElementDefinition getCriteriaForDiscriminator(String path, ElementDefinition element, String discriminator, StructureDefinition profile) throws DefinitionException { 1606 StructureDefinition sd = profile; 1607 ElementDefinition ed = element; 1608 1609 if ("value".equals(discriminator) && element.hasFixed()) 1610 return element; 1611 1612 List<String> nodes = new ArrayList<String>(); 1613 Matcher matcher = Pattern.compile("\\.([a-zA-Z0-0]+(\\([^\\(^\\)]*\\))?)").matcher("."+discriminator); 1614 while (matcher.find()) { 1615 if (!matcher.group(1).startsWith("@")) 1616 nodes.add(matcher.group(1)); 1617 } 1618 1619 for (String fullnode : nodes) { 1620 String node = fullnode.contains("(") ? fullnode.substring(0, fullnode.indexOf('(')) : fullnode; 1621 String qualifier = fullnode.contains("(") ? fullnode.substring(fullnode.indexOf('(') + 2, fullnode.length()-2) : null; 1622 if (qualifier!=null && !node.equals("extension")) 1623 throw new DefinitionException("Function specified other than 'extension()': " + discriminator); 1624 // get the children of element 1625 List<ElementDefinition> childDefinitions; 1626 childDefinitions = ProfileUtilities.getChildMap(sd, ed); 1627 // if that's empty, get the children of the type 1628 if (childDefinitions.isEmpty()) { 1629 if (ed.getType().size() == 0) 1630 throw new DefinitionException("Error in profile for " + path + " no children, no type"); 1631 if (ed.getType().size() > 1) 1632 throw new DefinitionException("Error in profile for " + path + " multiple types defined in slice discriminator"); 1633 if (ed.hasSlicing()) { 1634 List<ElementDefinition> slices = ProfileUtilities.getSliceList(profile, ed); 1635 boolean found = false; 1636 for (ElementDefinition sed: slices) { 1637 childDefinitions = ProfileUtilities.getChildMap(sd, sed); 1638 for (ElementDefinition t : childDefinitions) { 1639 if (tailMatches(t, node)) { 1640 found = true; 1641 if (qualifier!=null) { 1642 List<ElementDefinition> childNodes = ProfileUtilities.getChildList(profile, t); 1643 if (childNodes.isEmpty()) { 1644 if (t.getType().size()==1 && t.getType().get(0).hasProfile() && t.getType().get(0).getProfile().equals(qualifier)) { 1645 found = true; 1646 break; 1647 } 1648 1649 } else { 1650 for (ElementDefinition c : childNodes) { 1651 if (c.getPath().endsWith(".url") && c.hasFixed() && c.getFixed() instanceof UriType && ((UriType)c.getFixed()).equals(qualifier)) { 1652 found = true; 1653 break; 1654 } 1655 } 1656 } 1657 } 1658 if (found) { 1659 ed = t; 1660 break; 1661 } 1662 } 1663 } 1664 } 1665 if (found) 1666 continue; 1667 } 1668 if (ed.getType().get(0).hasProfile() && ed.getType().get(0).getCode().equals("Reference") && node.equals("reference")) { 1669 long t1 = System.nanoTime(); 1670 sd = context.fetchResource(StructureDefinition.class, ed.getType().get(0).getTargetProfile()); 1671 sdTime = sdTime + (System.nanoTime() - t1); 1672 // we've skipped the reference. 1673 ed = sd.getSnapshot().getElementFirstRep(); 1674 continue; 1675 } else if (ed.getType().get(0).hasProfile()) { 1676 long t2 = System.nanoTime(); 1677 sd = context.fetchResource(StructureDefinition.class, ed.getType().get(0).getProfile()); 1678 sdTime = sdTime + (System.nanoTime() - t2); 1679 } else { 1680 long t2 = System.nanoTime(); 1681 sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + ed.getType().get(0).getCode()); 1682 sdTime = sdTime + (System.nanoTime() - t2); 1683 } 1684 if (sd == null) 1685 throw new DefinitionException("Error in profile for " + path + ": unable to find type for "+ed.getId()); 1686 childDefinitions = ProfileUtilities.getChildMap(sd, sd.getSnapshot().getElementFirstRep()); 1687 } 1688 // find a single match. (no slicing!) 1689 boolean found = false; 1690 for (ElementDefinition t : childDefinitions) { 1691 if (tailMatches(t, node)) { 1692 found = true; 1693 if (qualifier!=null) { 1694 found = false; 1695 List<ElementDefinition> childNodes = ProfileUtilities.getChildList(profile, t); 1696 if (childNodes.isEmpty()) { 1697 if (t.getType().size()==1 && t.getType().get(0).hasProfile() && t.getType().get(0).getProfile().equals(qualifier)) { 1698 found = true; 1699 ed = t; 1700 break; 1701 } 1702 1703 } else { 1704 for (ElementDefinition c : childNodes) { 1705 if (c.getPath().endsWith(".url") && c.hasFixed() && c.getFixed() instanceof UriType && ((UriType)c.getFixed()).equals(qualifier)) { 1706 found = true; 1707 ed = t; 1708 break; 1709 } 1710 } 1711 } 1712 } 1713 if (found) { 1714 ed = t; 1715 break; 1716 } 1717 } 1718 } 1719 if (!found) 1720 throw new DefinitionException("Unable to find definition for discriminator "+discriminator+" from "+path+" in "+profile.getUrl()); 1721 } 1722 return ed; 1723 // List<ElementDefinition> snapshot = null; 1724 // int index; 1725 // if (childDefinitions.isEmpty()) { 1726 // // going to look at the type 1727 // if (ed.getType().size() == 0) 1728 // throw new DefinitionException("Error in profile for " + path + " no children, no type"); 1729 // snapshot = type.getSnapshot().getElement(); 1730 // ed = snapshot.get(0); 1731 // index = 0; 1732 // } else { 1733 // snapshot = childDefinitions; 1734 // index = -1; 1735 // } 1736 // String originalPath = ed.getPath(); 1737 // String goal = originalPath + "." + discriminator; 1738 // 1739 // index++; 1740 // while (index < snapshot.size() && !snapshot.get(index).getPath().equals(originalPath)) { 1741 // if (snapshot.get(index).getPath().equals(goal)) 1742 // return snapshot.get(index); 1743 // index++; 1744 // } 1745 // throw new Error("Unable to find discriminator definition for " + goal + " in " + discriminator + " at " + path+" in "+profile.getUrl()); 1746 } 1747 1748 private boolean tailMatches(ElementDefinition t, String d) { 1749 String tail = tail(t.getPath()); 1750 if (d.contains("[")) 1751 return tail.startsWith(d.substring(0, d.indexOf('['))); 1752 else 1753 return tail.equals(d); 1754 } 1755 1756 private Element getExtensionByUrl(List<Element> extensions, String urlSimple) { 1757 for (Element e : extensions) { 1758 if (urlSimple.equals(e.getNamedChildValue("url"))) 1759 return e; 1760 } 1761 return null; 1762 } 1763 1764 public List<String> getExtensionDomains() { 1765 return extensionDomains; 1766 } 1767 1768 private Element getFromBundle(Element bundle, String ref, String fullUrl, List<ValidationMessage> errors, String path) { 1769 String targetUrl = null; 1770 String version = ""; 1771 if (ref.startsWith("http") || ref.startsWith("urn")) { 1772 // We've got an absolute reference, no need to calculate 1773 if (ref.contains("/_history/")) { 1774 targetUrl = ref.substring(0, ref.indexOf("/_history/") - 1); 1775 version = ref.substring(ref.indexOf("/_history/") + 10); 1776 } else 1777 targetUrl = ref; 1778 1779 } else if (fullUrl == null) { 1780 //This isn't a problem for signatures - if it's a signature, we won't have a resolution for a relative reference. For anything else, this is an error 1781 rule(errors, IssueType.REQUIRED, -1, -1, path, path.startsWith("Bundle.signature"), "Relative Reference appears inside Bundle whose entry is missing a fullUrl"); 1782 return null; 1783 1784 } else if (ref.split("/").length!=2) { 1785 rule(errors, IssueType.INVALID, -1, -1, path, false, "Relative URLs must be of the format [ResourceName]/[id]. Encountered " + ref); 1786 return null; 1787 1788 } else { 1789 String base = ""; 1790 if (fullUrl.startsWith("urn")) { 1791 String[] parts = fullUrl.split("\\:"); 1792 for (int i=0; i < parts.length-1; i++) { 1793 base = base + parts[i] + ":"; 1794 } 1795 } else { 1796 String[] parts; 1797 parts = fullUrl.split("/"); 1798 for (int i=0; i < parts.length-2; i++) { 1799 base = base + parts[i] + "/"; 1800 } 1801 } 1802 1803 String id = null; 1804 if (ref.contains("/_history/")) { 1805 version = ref.substring(ref.indexOf("/_history/") + 10); 1806 id = ref.substring(0, ref.indexOf("/history/")-1); 1807 } else if (base.startsWith("urn")) 1808 id = ref.split("/")[1]; 1809 else 1810 id = ref; 1811 1812 targetUrl = base + id; 1813 } 1814 1815 List<Element> entries = new ArrayList<Element>(); 1816 bundle.getNamedChildren("entry", entries); 1817 Element match = null; 1818 for (Element we : entries) { 1819 if (we.getChildValue("fullUrl").equals(targetUrl)) { 1820 Element r = we.getNamedChild("resource"); 1821 if (version.isEmpty()) { 1822 rule(errors, IssueType.FORBIDDEN, -1, -1, path, match==null, "Multiple matches in bundle for reference " + ref); 1823 match = r; 1824 } else { 1825 try { 1826 if (r.getChildren("meta").get(0).getChildValue("versionId").equals(version)) { 1827 rule(errors, IssueType.FORBIDDEN, -1, -1, path, match==null, "Multiple matches in bundle for reference " + ref); 1828 match = r; 1829 } 1830 } catch (Exception e) { 1831 warning(errors, IssueType.REQUIRED, -1, -1, path, r.getChildren("meta").size()==1 && r.getChildren("meta").get(0).getChildValue("versionId")!=null, "Entries matching fullURL " + targetUrl + " should declare meta/versionId because there are version-specific references"); 1832 // If one of these things is null 1833 } 1834 } 1835 } 1836 } 1837 1838 warning(errors, IssueType.REQUIRED, -1, -1, path, match!=null || !targetUrl.startsWith("urn"), "URN reference is not locally contained within the bundle " + ref); 1839 return match; 1840 } 1841 1842 private StructureDefinition getProfileForType(String type) { 1843 if (logical != null) 1844 for (BundleEntryComponent be : logical.getEntry()) { 1845 if (be.hasResource() && be.getResource() instanceof StructureDefinition) { 1846 StructureDefinition sd = (StructureDefinition) be.getResource(); 1847 if (sd.getId().equals(type)) 1848 return sd; 1849 } 1850 } 1851 1852 long t = System.nanoTime(); 1853 try { 1854 return context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + type); 1855 } finally { 1856 sdTime = sdTime + (System.nanoTime() - t); 1857 } 1858 } 1859 1860 private Element getValueForDiscriminator(Object appContext, List<ValidationMessage> errors, Element element, String discriminator, ElementDefinition criteria, NodeStack stack) throws FHIRException, IOException { 1861 String p = stack.getLiteralPath()+"."+element.getName(); 1862 Element focus = element; 1863 String[] dlist = discriminator.split("\\."); 1864 for (String d : dlist) { 1865 if (focus.fhirType().equals("Reference") && d.equals("reference")) { 1866 String url = focus.getChildValue("reference"); 1867 if (Utilities.noString(url)) 1868 throw new FHIRException("No reference resolving discriminator "+discriminator+" from "+element.getProperty().getName()); 1869 // Note that we use the passed in stack here. This might be a problem if the discriminator is deep enough? 1870 Element target = resolve(appContext, url, stack, errors, p); 1871 if (target == null) 1872 throw new FHIRException("Unable to find resource "+url+" at "+d+" resolving discriminator "+discriminator+" from "+element.getProperty().getName()); 1873 focus = target; 1874 } else if (d.equals("value") && focus.isPrimitive()) { 1875 return focus; 1876 } else { 1877 List<Element> children = focus.getChildren(d); 1878 if (children.isEmpty()) 1879 throw new FHIRException("Unable to find "+d+" resolving discriminator "+discriminator+" from "+element.getProperty().getName()); 1880 if (children.size() > 1) 1881 throw new FHIRException("Found "+Integer.toString(children.size())+" items for "+d+" resolving discriminator "+discriminator+" from "+element.getProperty().getName()); 1882 focus = children.get(0); 1883 p = p + "."+d; 1884 } 1885 } 1886 return focus; 1887 } 1888 1889 private CodeSystem getCodeSystem(String system) { 1890 long t = System.nanoTime(); 1891 try { 1892 return context.fetchCodeSystem(system); 1893 } finally { 1894 txTime = txTime + (System.nanoTime() - t); 1895 } 1896 } 1897 1898 private boolean hasTime(String fmt) { 1899 return fmt.contains("T"); 1900 } 1901 1902 private boolean hasTimeZone(String fmt) { 1903 return fmt.length() > 10 && (fmt.substring(10).contains("-") || fmt.substring(10).contains("+") || fmt.substring(10).contains("Z")); 1904 } 1905 1906 private boolean isAbsolute(String uri) { 1907 return Utilities.noString(uri) || uri.startsWith("http:") || uri.startsWith("https:") || uri.startsWith("urn:uuid:") || uri.startsWith("urn:oid:") || uri.startsWith("urn:ietf:") 1908 || uri.startsWith("urn:iso:") || uri.startsWith("urn:iso-astm:") || isValidFHIRUrn(uri); 1909 } 1910 1911 private boolean isValidFHIRUrn(String uri) { 1912 return (uri.equals("urn:x-fhir:uk:id:nhs-number")) || uri.startsWith("urn:"); // Anyone can invent a URN, so why should we complain? 1913 } 1914 1915 public boolean isAnyExtensionsAllowed() { 1916 return anyExtensionsAllowed; 1917 } 1918 1919 public boolean isErrorForUnknownProfiles() { 1920 return errorForUnknownProfiles; 1921 } 1922 1923 public void setErrorForUnknownProfiles(boolean errorForUnknownProfiles) { 1924 this.errorForUnknownProfiles = errorForUnknownProfiles; 1925 } 1926 1927 private boolean isParametersEntry(String path) { 1928 String[] parts = path.split("\\."); 1929 return parts.length > 2 && parts[parts.length - 1].equals("resource") && (pathEntryHasName(parts[parts.length - 2], "parameter") || pathEntryHasName(parts[parts.length - 2], "part")); 1930 } 1931 1932 private boolean isBundleEntry(String path) { 1933 String[] parts = path.split("\\."); 1934 return parts.length > 2 && parts[parts.length - 1].equals("resource") && pathEntryHasName(parts[parts.length - 2], "entry"); 1935 } 1936 1937 private boolean isBundleOutcome(String path) { 1938 String[] parts = path.split("\\."); 1939 return parts.length > 2 && parts[parts.length - 1].equals("outcome") && pathEntryHasName(parts[parts.length - 2], "response"); 1940 } 1941 1942 1943 private static boolean pathEntryHasName(String thePathEntry, String theName) { 1944 if (thePathEntry.equals(theName)) { 1945 return true; 1946 } 1947 if (thePathEntry.length() >= theName.length() + 3) { 1948 if (thePathEntry.startsWith(theName)) { 1949 if (thePathEntry.charAt(theName.length()) == '[') { 1950 return true; 1951 } 1952 } 1953 } 1954 return false; 1955 } 1956 1957 private boolean isPrimitiveType(String code) { 1958 StructureDefinition sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+code); 1959 return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 1960 } 1961 1962 1963 1964 public boolean isSuppressLoincSnomedMessages() { 1965 return suppressLoincSnomedMessages; 1966 } 1967 1968 private boolean nameMatches(String name, String tail) { 1969 if (tail.endsWith("[x]")) 1970 return name.startsWith(tail.substring(0, tail.length() - 3)); 1971 else 1972 return (name.equals(tail)); 1973 } 1974 1975 // private String mergePath(String path1, String path2) { 1976 // // path1 is xpath path 1977 // // path2 is dotted path 1978 // String[] parts = path2.split("\\."); 1979 // StringBuilder b = new StringBuilder(path1); 1980 // for (int i = 1; i < parts.length -1; i++) 1981 // b.append("/f:"+parts[i]); 1982 // return b.toString(); 1983 // } 1984 1985 private boolean passesCodeWhitespaceRules(String v) { 1986 if (!v.trim().equals(v)) 1987 return false; 1988 boolean lastWasSpace = true; 1989 for (char c : v.toCharArray()) { 1990 if (c == ' ') { 1991 if (lastWasSpace) 1992 return false; 1993 else 1994 lastWasSpace = true; 1995 } else if (Character.isWhitespace(c)) 1996 return false; 1997 else 1998 lastWasSpace = false; 1999 } 2000 return true; 2001 } 2002 2003 private Element localResolve(String ref, NodeStack stack, List<ValidationMessage> errors, String path) { 2004 if (ref.startsWith("#")) { 2005 // work back through the contained list. 2006 // really, there should only be one level for this (contained resources cannot contain 2007 // contained resources), but we'll leave that to some other code to worry about 2008 while (stack != null && stack.getElement() != null) { 2009 if (stack.getElement().getProperty().isResource()) { 2010 // ok, we'll try to find the contained reference 2011 Element res = getContainedById(stack.getElement(), ref.substring(1)); 2012 if (res != null) 2013 return res; 2014 } 2015 if (stack.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY) { 2016 return null; // we don't try to resolve contained references across this boundary 2017 } 2018 stack = stack.parent; 2019 } 2020 return null; 2021 } else { 2022 // work back through the contained list - if any of them are bundles, try to resolve 2023 // the resource in the bundle 2024 String fullUrl = null; // we're going to try to work this out as we go up 2025 while (stack != null && stack.getElement() != null) { 2026 if (stack.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY && fullUrl==null && stack.parent.getElement().getName().equals("entry")) { 2027 fullUrl = stack.parent.getElement().getChildValue("fullUrl"); // we don't try to resolve contained references across this boundary 2028 if (fullUrl==null) 2029 rule(errors, IssueType.REQUIRED, stack.parent.getElement().line(), stack.parent.getElement().col(), stack.parent.getLiteralPath(), fullUrl!=null, "Bundle entry missing fullUrl"); 2030 } 2031 if ("Bundle".equals(stack.getElement().getType())) { 2032 Element res = getFromBundle(stack.getElement(), ref, fullUrl, errors, path); 2033 return res; 2034 } 2035 stack = stack.parent; 2036 } 2037 } 2038 return null; 2039 } 2040 2041 private Element resolve(Object appContext, String ref, NodeStack stack, List<ValidationMessage> errors, String path) throws IOException, FHIRException { 2042 Element local = localResolve(ref, stack, errors, path); 2043 if (local!=null) 2044 return local; 2045 if (fetcher == null) 2046 return null; 2047 return fetcher.fetch(appContext, ref); 2048 } 2049 2050 private ValueSet resolveBindingReference(DomainResource ctxt, Type reference, String uri) { 2051 if (reference instanceof UriType) { 2052 long t = System.nanoTime(); 2053 ValueSet fr = context.fetchResource(ValueSet.class, ((UriType) reference).getValue().toString()); 2054 txTime = txTime + (System.nanoTime() - t); 2055 return fr; 2056 } 2057 else if (reference instanceof Reference) { 2058 String s = ((Reference) reference).getReference(); 2059 if (s.startsWith("#")) { 2060 for (Resource c : ctxt.getContained()) { 2061 if (c.getId().equals(s.substring(1)) && (c instanceof ValueSet)) 2062 return (ValueSet) c; 2063 } 2064 return null; 2065 } else { 2066 long t = System.nanoTime(); 2067 String ref = ((Reference) reference).getReference(); 2068 if (!Utilities.isAbsoluteUrl(ref)) 2069 ref = resolve(uri, ref); 2070 ValueSet fr = context.fetchResource(ValueSet.class, ref); 2071 txTime = txTime + (System.nanoTime() - t); 2072 return fr; 2073 } 2074 } 2075 else 2076 return null; 2077 } 2078 2079 private String resolve(String uri, String ref) { 2080 String[] up = uri.split("\\/"); 2081 String[] rp = ref.split("\\/"); 2082 if (context.getResourceNames().contains(up[up.length-2]) && context.getResourceNames().contains(rp[0])) { 2083 StringBuilder b = new StringBuilder(); 2084 for (int i = 0; i < up.length-2; i++) { 2085 b.append(up[i]); 2086 b.append("/"); 2087 } 2088 b.append(ref); 2089 return b.toString(); 2090 } else 2091 return ref; 2092 } 2093 2094 private Element resolveInBundle(List<Element> entries, String ref, String fullUrl, String type, String id) { 2095 if (Utilities.isAbsoluteUrl(ref)) { 2096 // if the reference is absolute, then you resolve by fullUrl. No other thinking is required. 2097 for (Element entry : entries) { 2098 String fu = entry.getNamedChildValue("fullUrl"); 2099 if (ref.equals(fu)) 2100 return entry; 2101 } 2102 return null; 2103 } else { 2104 // split into base, type, and id 2105 String u = null; 2106 if (fullUrl != null && fullUrl.endsWith(type+"/"+id)) 2107 // fullUrl = complex 2108 u = fullUrl.substring((type+"/"+id).length())+ref; 2109 String[] parts = ref.split("\\/"); 2110 if (parts.length >= 2) { 2111 String t = parts[0]; 2112 String i = parts[1]; 2113 for (Element entry : entries) { 2114 String fu = entry.getNamedChildValue("fullUrl"); 2115 if (u != null && fullUrl.equals(u)) 2116 return entry; 2117 if (u == null) { 2118 Element resource = entry.getNamedChild("resource"); 2119 String et = resource.getType(); 2120 String eid = resource.getNamedChildValue("id"); 2121 if (t.equals(et) && i.equals(eid)) 2122 return entry; 2123 } 2124 } 2125 } 2126 return null; 2127 } 2128 } 2129 2130 private ElementDefinition resolveNameReference(StructureDefinitionSnapshotComponent snapshot, String contentReference) { 2131 for (ElementDefinition ed : snapshot.getElement()) 2132 if (contentReference.equals("#"+ed.getId())) 2133 return ed; 2134 return null; 2135 } 2136 2137 private StructureDefinition resolveProfile(StructureDefinition profile, String pr) { 2138 if (pr.startsWith("#")) { 2139 for (Resource r : profile.getContained()) { 2140 if (r.getId().equals(pr.substring(1)) && r instanceof StructureDefinition) 2141 return (StructureDefinition) r; 2142 } 2143 return null; 2144 } else { 2145 long t = System.nanoTime(); 2146 StructureDefinition fr = context.fetchResource(StructureDefinition.class, pr); 2147 sdTime = sdTime + (System.nanoTime() - t); 2148 return fr; 2149 } 2150 } 2151 2152 private ElementDefinition resolveType(String type) { 2153 if (logical != null) 2154 for (BundleEntryComponent be : logical.getEntry()) { 2155 if (be.hasResource() && be.getResource() instanceof StructureDefinition) { 2156 StructureDefinition sd = (StructureDefinition) be.getResource(); 2157 if (sd.getId().equals(type)) 2158 return sd.getSnapshot().getElement().get(0); 2159 } 2160 } 2161 String url = "http://hl7.org/fhir/StructureDefinition/" + type; 2162 long t = System.nanoTime(); 2163 StructureDefinition sd = context.fetchResource(StructureDefinition.class, url); 2164 sdTime = sdTime + (System.nanoTime() - t); 2165 if (sd == null || !sd.hasSnapshot()) 2166 return null; 2167 else 2168 return sd.getSnapshot().getElement().get(0); 2169 } 2170 2171 public void setAnyExtensionsAllowed(boolean anyExtensionsAllowed) { 2172 this.anyExtensionsAllowed = anyExtensionsAllowed; 2173 } 2174 2175 public IResourceValidator setBestPracticeWarningLevel(BestPracticeWarningLevel value) { 2176 bpWarnings = value; 2177 return this; 2178 } 2179 2180 @Override 2181 public void setCheckDisplay(CheckDisplayOption checkDisplay) { 2182 this.checkDisplay = checkDisplay; 2183 } 2184 2185 public void setSuppressLoincSnomedMessages(boolean suppressLoincSnomedMessages) { 2186 this.suppressLoincSnomedMessages = suppressLoincSnomedMessages; 2187 } 2188 2189 public IdStatus getResourceIdRule() { 2190 return resourceIdRule; 2191 } 2192 2193 public void setResourceIdRule(IdStatus resourceIdRule) { 2194 this.resourceIdRule = resourceIdRule; 2195 } 2196 2197 2198 public boolean isAllowXsiLocation() { 2199 return allowXsiLocation; 2200 } 2201 2202 public void setAllowXsiLocation(boolean allowXsiLocation) { 2203 this.allowXsiLocation = allowXsiLocation; 2204 } 2205 2206 /** 2207 * 2208 * @param element 2209 * - the candidate that might be in the slice 2210 * @param path 2211 * - for reporting any errors. the XPath for the element 2212 * @param slice 2213 * - the definition of how slicing is determined 2214 * @param ed 2215 * - the slice for which to test membership 2216 * @param errors 2217 * @param stack 2218 * @return 2219 * @throws DefinitionException 2220 * @throws DefinitionException 2221 * @throws IOException 2222 * @throws FHIRException 2223 */ 2224 private boolean sliceMatches(Object appContext, Element element, String path, ElementDefinition slicer, ElementDefinition ed, StructureDefinition profile, List<ValidationMessage> errors, NodeStack stack) throws DefinitionException, FHIRException, IOException { 2225 if (!slicer.getSlicing().hasDiscriminator()) 2226 return false; // cannot validate in this case 2227 2228 ExpressionNode n = (ExpressionNode) ed.getUserData("slice.expression.cache"); 2229 if (n == null) { 2230 long t = System.nanoTime(); 2231 // GG: this approach is flawed because it treats discriminators individually rather than collectively 2232 String expression = "true"; 2233 for (ElementDefinitionSlicingDiscriminatorComponent s : slicer.getSlicing().getDiscriminator()) { 2234 String discriminator = s.getPath(); 2235 if (s.getType() == DiscriminatorType.PROFILE) 2236 throw new FHIRException("Validating against slices with discriminators based on profiles is not yet supported by the FHIRPath engine: " + discriminator); 2237 // Todo: Fix this once FHIRPath (and this engine) supports a boolean function that test profile conformance 2238 2239 ElementDefinition criteriaElement = getCriteriaForDiscriminator(path, ed, discriminator, profile); 2240 if (s.getType() == DiscriminatorType.TYPE) { 2241 String type = null; 2242 if (!criteriaElement.getPath().contains("[") && discriminator.contains("[")) { 2243 discriminator = discriminator.substring(0, discriminator.indexOf('[')); 2244 String lastNode = tail(discriminator); 2245 type = tail(criteriaElement.getPath()).substring(lastNode.length()); 2246 type = type.substring(0,1).toLowerCase() + type.substring(1); 2247 } else if (!criteriaElement.hasType() || criteriaElement.getType().size()==1) { 2248 if (discriminator.contains("[")) 2249 discriminator = discriminator.substring(0, discriminator.indexOf('[')); 2250 type = criteriaElement.getType().get(0).getCode(); 2251 } 2252 if (type==null) 2253 throw new DefinitionException("Discriminator (" + discriminator + ") is based on type, but slice " + ed.getId() + " does not declare a type"); 2254 if (discriminator.isEmpty()) 2255 expression = expression + " and this is " + type; 2256 else 2257 expression = expression + " and " + discriminator + " is " + type; 2258 } else if (criteriaElement.hasFixed()) { 2259 expression = expression + " and " + discriminator + " = "; 2260 Type fixed = criteriaElement.getFixed(); 2261 if (fixed instanceof StringType) { 2262 Gson gson = new Gson(); 2263 String json = gson.toJson((StringType)fixed); 2264 String escapedString = json.substring(json.indexOf(":")+2); 2265 escapedString = escapedString.substring(0, escapedString.indexOf(",\"myStringValue")-1); 2266 expression = expression + "'" + escapedString + "'"; 2267 } else if (fixed instanceof UriType) { 2268 expression = expression + "'" + ((UriType)fixed).asStringValue() + "'"; 2269 } else if (fixed instanceof IntegerType) { 2270 expression = expression + ((IntegerType)fixed).asStringValue(); 2271 } else if (fixed instanceof DecimalType) { 2272 expression = expression + ((IntegerType)fixed).asStringValue(); 2273 } else if (fixed instanceof BooleanType) { 2274 expression = expression + ((BooleanType)fixed).asStringValue(); 2275 } else 2276 throw new DefinitionException("Unsupported fixed value type for discriminator(" + discriminator + ") for slice " + ed.getId() + ": " + fixed.getClass().getName()); 2277 } else if (criteriaElement.hasBinding() && criteriaElement.getBinding().hasStrength() && criteriaElement.getBinding().getStrength().equals(BindingStrength.REQUIRED) && criteriaElement.getBinding().getValueSetReference()!=null) { 2278 expression = expression + " and " + discriminator + " in '" + criteriaElement.getBinding().getValueSetReference().getReference() + "'"; 2279 } else if (criteriaElement.hasMin() && criteriaElement.getMin()>0) { 2280 expression = expression + " and " + discriminator + ".exists()"; 2281 } else if (criteriaElement.hasMax() && criteriaElement.getMax().equals("0")) { 2282 expression = expression + " and " + discriminator + ".exists().not()"; 2283 } else { 2284 throw new DefinitionException("Could not match discriminator (" + discriminator + ") for slice " + ed.getId() + " - does not have fixed value, binding or existence assertions"); 2285 } 2286 } 2287 2288 try { 2289 n = fpe.parse(expression); 2290 } catch (FHIRLexerException e) { 2291 throw new FHIRException("Problem processing expression "+expression +" in profile " + profile.getUrl() + " path " + path + ": " + e.getMessage()); 2292 } 2293 fpeTime = fpeTime + (System.nanoTime() - t); 2294 ed.setUserData("slice.expression.cache", n); 2295 } 2296 2297 String msg; 2298 boolean ok; 2299 try { 2300 long t = System.nanoTime(); 2301 ok = fpe.evaluateToBoolean(null, element, n); 2302 fpeTime = fpeTime + (System.nanoTime() - t); 2303 msg = fpe.forLog(); 2304 } catch (Exception ex) { 2305 throw new FHIRException("Problem evaluating slicing expression for element in profile " + profile.getUrl() + " path " + path + ": " + ex.getMessage()); 2306 } 2307 return ok; 2308 } 2309 2310 // we assume that the following things are true: 2311 // the instance at root is valid against the schema and schematron 2312 // the instance validator had no issues against the base resource profile 2313 private void start(Object appContext, List<ValidationMessage> errors, Element resource, Element element, StructureDefinition defn, NodeStack stack) throws FHIRException, FHIRException, IOException { 2314 // profile is valid, and matches the resource name 2315 ResourceProfiles resourceProfiles = getResourceProfiles(element, stack); 2316 if (!resourceProfiles.isProcessed()) 2317 checkDeclaredProfiles(resourceProfiles, errors, resource, element, stack); 2318 2319 if (!resourceProfiles.isProcessed()) { 2320 resourceProfiles.setProcessed(); 2321 if (!resourceProfiles.hasProfiles() && 2322 (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), defn.hasSnapshot(), 2323 "StructureDefinition has no snapshot - validation is against the snapshot, so it must be provided"))) { 2324 // Don't need to validate against the resource if there's a profile because the profile snapshot will include the relevant parts of the resources 2325 validateElement(appContext, errors, defn, defn.getSnapshot().getElement().get(0), null, null, resource, element, element.getName(), stack, false); 2326 } 2327 2328 // specific known special validations 2329 if (element.getType().equals("Bundle")) 2330 validateBundle(errors, element, stack); 2331 if (element.getType().equals("Observation")) 2332 validateObservation(errors, element, stack); 2333 if (element.getType().equals("QuestionnaireResponse")) 2334 validateQuestionannaireResponse(errors, element, stack); 2335 } 2336 for (ProfileUsage profileUsage : resourceProfiles.uncheckedProfiles()) { 2337 profileUsage.setChecked(); 2338 validateElement(appContext, errors, profileUsage.getProfile(), profileUsage.getProfile().getSnapshot().getElement().get(0), null, null, resource, element, element.getName(), stack, false); 2339 } 2340 } 2341 2342 private void validateQuestionannaireResponse(List<ValidationMessage> errors, Element element, NodeStack stack) { 2343 Element q = element.getNamedChild("questionnaire"); 2344 if (hint(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), q != null && isNotBlank(q.getNamedChildValue("reference")), "No questionnaire is identified, so no validation can be performed against the base questionnaire")) { 2345 long t = System.nanoTime(); 2346 Questionnaire qsrc = context.fetchResource(Questionnaire.class, q.getNamedChildValue("reference")); 2347 sdTime = sdTime + (System.nanoTime() - t); 2348 if (warning(errors, IssueType.REQUIRED, q.line(), q.col(), stack.getLiteralPath(), qsrc != null, "The questionnaire could not be resolved, so no validation can be performed against the base questionnaire")) { 2349 boolean inProgress = "in-progress".equals(element.getNamedChildValue("status")); 2350 validateQuestionannaireResponseItems(qsrc, qsrc.getItem(), errors, element, stack, inProgress); 2351 } 2352 } 2353 } 2354 2355 private void validateQuestionannaireResponseItem(Questionnaire qsrc, QuestionnaireItemComponent qItem, List<ValidationMessage> errors, Element element, NodeStack stack, boolean inProgress) { 2356 String text = element.getNamedChildValue("text"); 2357 rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), Utilities.noString(text) || text.equals(qItem.getText()), "If text exists, it must match the questionnaire definition for linkId "+qItem.getLinkId()); 2358 2359 List<Element> answers = new ArrayList<Element>(); 2360 element.getNamedChildren("answer", answers); 2361 if (inProgress) 2362 warning(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), (answers.size() > 0) || !qItem.getRequired(), "No response answer found for required item "+qItem.getLinkId()); 2363 else 2364 rule(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), (answers.size() > 0) || !qItem.getRequired(), "No response answer found for required item "+qItem.getLinkId()); 2365 if (answers.size() > 1) 2366 rule(errors, IssueType.INVALID, answers.get(1).line(), answers.get(1).col(), stack.getLiteralPath(), qItem.getRepeats(), "Only one response answer item with this linkId allowed"); 2367 2368 for (Element answer : answers) { 2369 NodeStack ns = stack.push(answer, -1, null, null); 2370 switch (qItem.getType()) { 2371 case GROUP: 2372 rule(errors, IssueType.STRUCTURE, answer.line(), answer.col(), stack.getLiteralPath(), false, "Items of type group should not have answers"); 2373 break; 2374 case DISPLAY: // nothing 2375 break; 2376 case BOOLEAN: 2377 validateQuestionnaireResponseItemType(errors, answer, ns, "boolean"); 2378 break; 2379 case DECIMAL: 2380 validateQuestionnaireResponseItemType(errors, answer, ns, "decimal"); 2381 break; 2382 case INTEGER: 2383 validateQuestionnaireResponseItemType(errors, answer, ns, "integer"); 2384 break; 2385 case DATE: 2386 validateQuestionnaireResponseItemType(errors, answer, ns, "date"); 2387 break; 2388 case DATETIME: 2389 validateQuestionnaireResponseItemType(errors, answer, ns, "dateTime"); 2390 break; 2391 case TIME: 2392 validateQuestionnaireResponseItemType(errors, answer, ns, "time"); 2393 break; 2394 case STRING: 2395 validateQuestionnaireResponseItemType(errors, answer, ns, "string"); 2396 break; 2397 case TEXT: 2398 validateQuestionnaireResponseItemType(errors, answer, ns, "text"); 2399 break; 2400 case URL: 2401 validateQuestionnaireResponseItemType(errors, answer, ns, "uri"); 2402 break; 2403 case ATTACHMENT: 2404 validateQuestionnaireResponseItemType(errors, answer, ns, "Attachment"); 2405 break; 2406 case REFERENCE: 2407 validateQuestionnaireResponseItemType(errors, answer, ns, "Reference"); 2408 break; 2409 case QUANTITY: 2410 if (validateQuestionnaireResponseItemType(errors, answer, ns, "Quantity").equals("Quantity")) 2411 if (qItem.hasExtension("???")) 2412 validateQuestionnaireResponseItemQuantity(errors, answer, ns); 2413 break; 2414 case CHOICE: 2415 String itemType=validateQuestionnaireResponseItemType(errors, answer, ns, "Coding", "date", "time", "integer", "string"); 2416 if (itemType.equals("Coding")) validateAnswerCode(errors, answer, ns, qsrc, qItem, false); 2417 else if (itemType.equals("date")) checkOption(errors, answer, ns, qsrc, qItem, "date"); 2418 else if (itemType.equals("time")) checkOption(errors, answer, ns, qsrc, qItem, "time"); 2419 else if (itemType.equals("integer")) checkOption(errors, answer, ns, qsrc, qItem, "integer"); 2420 else if (itemType.equals("string")) checkOption(errors, answer, ns, qsrc, qItem, "string"); 2421 break; 2422 case OPENCHOICE: 2423 itemType=validateQuestionnaireResponseItemType(errors, answer, ns, "Coding", "date", "time", "integer", "string"); 2424 if (itemType.equals("Coding")) validateAnswerCode(errors, answer, ns, qsrc, qItem, true); 2425 else if (itemType.equals("date")) checkOption(errors, answer, ns, qsrc, qItem, "date"); 2426 else if (itemType.equals("time")) checkOption(errors, answer, ns, qsrc, qItem, "time"); 2427 else if (itemType.equals("integer")) checkOption(errors, answer, ns, qsrc, qItem, "integer"); 2428 else if (itemType.equals("string")) checkOption(errors, answer, ns, qsrc, qItem, "string", true); 2429 break; 2430 } 2431 validateQuestionannaireResponseItems(qsrc, qItem.getItem(), errors, answer, stack, inProgress); 2432 } 2433 if (qItem.getType() == null) { 2434 fail(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), false, "Definition for item "+qItem.getLinkId() + " does not contain a type"); 2435 } else if (qItem.getType() == QuestionnaireItemType.GROUP) { 2436 validateQuestionannaireResponseItems(qsrc, qItem.getItem(), errors, element, stack, inProgress); 2437 } else { 2438 List<Element> items = new ArrayList<Element>(); 2439 element.getNamedChildren("item", items); 2440 for (Element item : items) { 2441 NodeStack ns = stack.push(item, -1, null, null); 2442 rule(errors, IssueType.STRUCTURE, answers.get(0).line(), answers.get(0).col(), stack.getLiteralPath(), false, "Items not of type group should not have items - Item with linkId {0} of type {1} has {2} item(s)", qItem.getLinkId(), qItem.getType(), items.size()); 2443 } 2444 } 2445 } 2446 2447 private void validateQuestionannaireResponseItem(Questionnaire qsrc, QuestionnaireItemComponent qItem, List<ValidationMessage> errors, List<Element> elements, NodeStack stack, boolean inProgress) { 2448 if (elements.size() > 1) 2449 rule(errors, IssueType.INVALID, elements.get(1).line(), elements.get(1).col(), stack.getLiteralPath(), qItem.getRepeats(), "Only one response item with this linkId allowed"); 2450 for (Element element : elements) { 2451 NodeStack ns = stack.push(element, -1, null, null); 2452 validateQuestionannaireResponseItem(qsrc, qItem, errors, element, ns, inProgress); 2453 } 2454 } 2455 2456 private int getLinkIdIndex(List<QuestionnaireItemComponent> qItems, String linkId) { 2457 for (int i = 0; i < qItems.size(); i++) { 2458 if (linkId.equals(qItems.get(i).getLinkId())) 2459 return i; 2460 } 2461 return -1; 2462 } 2463 2464 private void validateQuestionannaireResponseItems(Questionnaire qsrc, List<QuestionnaireItemComponent> qItems, List<ValidationMessage> errors, Element element, NodeStack stack, boolean inProgress) { 2465 List<Element> items = new ArrayList<Element>(); 2466 element.getNamedChildren("item", items); 2467 // now, sort into stacks 2468 Map<String, List<Element>> map = new HashMap<String, List<Element>>(); 2469 int lastIndex = -1; 2470 for (Element item : items) { 2471 String linkId = item.getNamedChildValue("linkId"); 2472 if (rule(errors, IssueType.REQUIRED, item.line(), item.col(), stack.getLiteralPath(), !Utilities.noString(linkId), "No LinkId, so can't be validated")) { 2473 int index = getLinkIdIndex(qItems, linkId); 2474 if (index == -1) { 2475 QuestionnaireItemComponent qItem = findQuestionnaireItem(qsrc, linkId); 2476 if (qItem != null) { 2477 rule(errors, IssueType.STRUCTURE, item.line(), item.col(), stack.getLiteralPath(), index > -1, "Structural Error: item is in the wrong place"); 2478 NodeStack ns = stack.push(item, -1, null, null); 2479 validateQuestionannaireResponseItem(qsrc, qItem, errors, element, ns, inProgress); 2480 } 2481 else 2482 rule(errors, IssueType.NOTFOUND, item.line(), item.col(), stack.getLiteralPath(), index > -1, "LinkId \""+linkId+"\" not found in questionnaire"); 2483 } 2484 else 2485 { 2486 rule(errors, IssueType.STRUCTURE, item.line(), item.col(), stack.getLiteralPath(), index >= lastIndex, "Structural Error: items are out of order"); 2487 lastIndex = index; 2488 List<Element> mapItem = map.get(linkId); 2489 if (mapItem == null) { 2490 mapItem = new ArrayList<Element>(); 2491 map.put(linkId, mapItem); 2492 } 2493 mapItem.add(item); 2494 } 2495 } 2496 } 2497 2498 // ok, now we have a list of known items, grouped by linkId. We"ve made an error for anything out of order 2499 for (QuestionnaireItemComponent qItem : qItems) { 2500 List<Element> mapItem = map.get(qItem.getLinkId()); 2501 if (mapItem != null) 2502 validateQuestionannaireResponseItem(qsrc, qItem, errors, mapItem, stack, inProgress); 2503 else 2504 rule(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), !qItem.getRequired(), "No response found for required item "+qItem.getLinkId()); 2505 } 2506 } 2507 2508 private void validateQuestionnaireResponseItemQuantity( List<ValidationMessage> errors, Element answer, NodeStack stack) { 2509 2510 } 2511 2512 private String validateQuestionnaireResponseItemType(List<ValidationMessage> errors, Element element, NodeStack stack, String... types) { 2513 List<Element> values = new ArrayList<Element>(); 2514 element.getNamedChildrenWithWildcard("value[x]", values); 2515 if (values.size() > 0) { 2516 NodeStack ns = stack.push(values.get(0), -1, null, null); 2517 CommaSeparatedStringBuilder l = new CommaSeparatedStringBuilder(); 2518 for (String s : types) { 2519 l.append(s); 2520 if (values.get(0).getName().equals("value"+Utilities.capitalize(s))) 2521 return(s); 2522 } 2523 if (types.length == 1) 2524 rule(errors, IssueType.STRUCTURE, values.get(0).line(), values.get(0).col(), ns.getLiteralPath(), false, "Answer value must be of type "+types[0]); 2525 else 2526 rule(errors, IssueType.STRUCTURE, values.get(0).line(), values.get(0).col(), ns.getLiteralPath(), false, "Answer value must be one of the types "+l.toString()); 2527 } 2528 return null; 2529 } 2530 2531 private QuestionnaireItemComponent findQuestionnaireItem(Questionnaire qSrc, String linkId) { 2532 return findItem(qSrc.getItem(), linkId); 2533 } 2534 2535 private QuestionnaireItemComponent findItem(List<QuestionnaireItemComponent> list, String linkId) { 2536 for (QuestionnaireItemComponent item : list) { 2537 if (linkId.equals(item.getLinkId())) 2538 return item; 2539 QuestionnaireItemComponent result = findItem(item.getItem(), linkId); 2540 if (result != null) 2541 return result; 2542 } 2543 return null; 2544 } 2545 2546 /* private void validateAnswerCode(List<ValidationMessage> errors, Element value, NodeStack stack, List<Coding> optionList) { 2547 String system = value.getNamedChildValue("system"); 2548 String code = value.getNamedChildValue("code"); 2549 boolean found = false; 2550 for (Coding c : optionList) { 2551 if (ObjectUtil.equals(c.getSystem(), system) && ObjectUtil.equals(c.getCode(), code)) { 2552 found = true; 2553 break; 2554 } 2555 } 2556 rule(errors, IssueType.STRUCTURE, value.line(), value.col(), stack.getLiteralPath(), found, "The code "+system+"::"+code+" is not a valid option"); 2557 }*/ 2558 2559 private void validateAnswerCode(List<ValidationMessage> errors, Element value, NodeStack stack, Questionnaire qSrc, Reference ref, boolean theOpenChoice) { 2560 ValueSet vs = resolveBindingReference(qSrc, ref, qSrc.getUrl()); 2561 if (warning(errors, IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), vs != null, "ValueSet " + describeReference(ref) + " not found")) { 2562 try { 2563 Coding c = readAsCoding(value); 2564 if (isBlank(c.getCode()) && isBlank(c.getSystem()) && isNotBlank(c.getDisplay())) { 2565 if (theOpenChoice) { 2566 return; 2567 } 2568 } 2569 2570 long t = System.nanoTime(); 2571 ValidationResult res = context.validateCode(c, vs); 2572 txTime = txTime + (System.nanoTime() - t); 2573 if (!res.isOk()) 2574 rule(errors, IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), false, "The value provided ("+c.getSystem()+"::"+c.getCode()+") is not in the options value set in the questionnaire"); 2575 } catch (Exception e) { 2576 warning(errors, IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), false, "Error " + e.getMessage() + " validating Coding against Questionnaire Options"); 2577 } 2578 } 2579 } 2580 2581 private void validateAnswerCode( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean theOpenChoice) { 2582 Element v = answer.getNamedChild("valueCoding"); 2583 NodeStack ns = stack.push(v, -1, null, null); 2584 if (qItem.getOption().size() > 0) 2585 checkCodingOption(errors, answer, stack, qSrc, qItem, theOpenChoice); 2586 // validateAnswerCode(errors, v, stack, qItem.getOption()); 2587 else if (qItem.hasOptions()) 2588 validateAnswerCode(errors, v, stack, qSrc, qItem.getOptions(), theOpenChoice); 2589 else 2590 hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Cannot validate options because no option or options are provided"); 2591 } 2592 2593 private void checkOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, String type) { 2594 checkOption(errors, answer, stack, qSrc, qItem, type, false); 2595 } 2596 2597 private void checkOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, String type, boolean openChoice) { 2598 if (type.equals("integer")) checkIntegerOption(errors, answer, stack, qSrc, qItem, openChoice); 2599 else if (type.equals("date")) checkDateOption(errors, answer, stack, qSrc, qItem, openChoice); 2600 else if (type.equals("time")) checkTimeOption(errors, answer, stack, qSrc, qItem, openChoice); 2601 else if (type.equals("string")) checkStringOption(errors, answer, stack, qSrc, qItem, openChoice); 2602 else if (type.equals("Coding")) checkCodingOption(errors, answer, stack, qSrc, qItem, openChoice); 2603 } 2604 2605 private void checkIntegerOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean openChoice) { 2606 Element v = answer.getNamedChild("valueInteger"); 2607 NodeStack ns = stack.push(v, -1, null, null); 2608 if (qItem.getOption().size() > 0) { 2609 List<IntegerType> list = new ArrayList<IntegerType>(); 2610 for (QuestionnaireItemOptionComponent components : qItem.getOption()) { 2611 try { 2612 list.add(components.getValueIntegerType()); 2613 } catch (FHIRException e) { 2614 // If it's the wrong type, just keep going 2615 } 2616 } 2617 if (list.isEmpty() && !openChoice) { 2618 rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Option list has no option values of type integer"); 2619 } else { 2620 boolean found = false; 2621 for (IntegerType item : list) { 2622 if (item.getValue() == Integer.parseInt(v.primitiveValue())) { 2623 found = true; 2624 break; 2625 } 2626 } 2627 if (!found) { 2628 rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, "The integer "+v.primitiveValue()+" is not a valid option"); 2629 } 2630 } 2631 } else 2632 hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Cannot validate integer answer option because no option list is provided"); 2633 } 2634 2635 private void checkDateOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean openChoice) { 2636 Element v = answer.getNamedChild("valueDate"); 2637 NodeStack ns = stack.push(v, -1, null, null); 2638 if (qItem.getOption().size() > 0) { 2639 List<DateType> list = new ArrayList<DateType>(); 2640 for (QuestionnaireItemOptionComponent components : qItem.getOption()) { 2641 try { 2642 list.add(components.getValueDateType()); 2643 } catch (FHIRException e) { 2644 // If it's the wrong type, just keep going 2645 } 2646 } 2647 if (list.isEmpty() && !openChoice) { 2648 rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Option list has no option values of type date"); 2649 } else { 2650 boolean found = false; 2651 for (DateType item : list) { 2652 if (item.getValue().equals(v.primitiveValue())) { 2653 found = true; 2654 break; 2655 } 2656 } 2657 if (!found) { 2658 rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, "The date "+v.primitiveValue()+" is not a valid option"); 2659 } 2660 } 2661 } else 2662 hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Cannot validate date answer option because no option list is provided"); 2663 } 2664 2665 private void checkTimeOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean openChoice) { 2666 Element v = answer.getNamedChild("valueTime"); 2667 NodeStack ns = stack.push(v, -1, null, null); 2668 if (qItem.getOption().size() > 0) { 2669 List<TimeType> list = new ArrayList<TimeType>(); 2670 for (QuestionnaireItemOptionComponent components : qItem.getOption()) { 2671 try { 2672 list.add(components.getValueTimeType()); 2673 } catch (FHIRException e) { 2674 // If it's the wrong type, just keep going 2675 } 2676 } 2677 if (list.isEmpty() && !openChoice) { 2678 rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Option list has no option values of type time"); 2679 } else { 2680 boolean found = false; 2681 for (TimeType item : list) { 2682 if (item.getValue().equals(v.primitiveValue())) { 2683 found = true; 2684 break; 2685 } 2686 } 2687 if (!found) { 2688 rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, "The time "+v.primitiveValue()+" is not a valid option"); 2689 } 2690 } 2691 } else 2692 hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Cannot validate time answer option because no option list is provided"); 2693 } 2694 2695 private void checkStringOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean openChoice) { 2696 Element v = answer.getNamedChild("valueString"); 2697 NodeStack ns = stack.push(v, -1, null, null); 2698 if (qItem.getOption().size() > 0) { 2699 List<StringType> list = new ArrayList<StringType>(); 2700 for (QuestionnaireItemOptionComponent components : qItem.getOption()) { 2701 try { 2702 if (components.getValue() != null) { 2703 list.add(components.getValueStringType()); 2704 } 2705 } catch (FHIRException e) { 2706 // If it's the wrong type, just keep going 2707 } 2708 } 2709 if (list.isEmpty() && !openChoice) { 2710 rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Option list has no option values of type string"); 2711 } else { 2712 boolean found = false; 2713 for (StringType item : list) { 2714 if (item.getValue().equals((v.primitiveValue()))) { 2715 found = true; 2716 break; 2717 } 2718 } 2719 if (!found) { 2720 rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, "The string "+v.primitiveValue()+" is not a valid option"); 2721 } 2722 } 2723 } else { 2724 hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Cannot validate string answer option because no option list is provided"); 2725 } 2726 } 2727 2728 private void checkCodingOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean openChoice) { 2729 Element v = answer.getNamedChild("valueCoding"); 2730 String system = v.getNamedChildValue("system"); 2731 String code = v.getNamedChildValue("code"); 2732 NodeStack ns = stack.push(v, -1, null, null); 2733 if (qItem.getOption().size() > 0) { 2734 List<Coding> list = new ArrayList<Coding>(); 2735 for (QuestionnaireItemOptionComponent components : qItem.getOption()) { 2736 try { 2737 if (components.getValue() != null) { 2738 list.add(components.getValueCoding()); 2739 } 2740 } catch (FHIRException e) { 2741 // If it's the wrong type, just keep going 2742 } 2743 } 2744 if (list.isEmpty() && !openChoice) { 2745 rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Option list has no option values of type coding"); 2746 } else { 2747 boolean found = false; 2748 for (Coding item : list) { 2749 if (ObjectUtil.equals(item.getSystem(), system) && ObjectUtil.equals(item.getCode(), code)) { 2750 found = true; 2751 break; 2752 } 2753 } 2754 if (!found) { 2755 rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, "The code "+system+"::"+code+" is not a valid option"); 2756 } 2757 } 2758 } else 2759 hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Cannot validate Coding option because no option list is provided"); 2760 } 2761 2762 private String tail(String path) { 2763 return path.substring(path.lastIndexOf(".") + 1); 2764 } 2765 2766 private String tryParse(String ref) { 2767 String[] parts = ref.split("\\/"); 2768 switch (parts.length) { 2769 case 1: 2770 return null; 2771 case 2: 2772 return checkResourceType(parts[0]); 2773 default: 2774 if (parts[parts.length - 2].equals("_history")) 2775 return checkResourceType(parts[parts.length - 4]); 2776 else 2777 return checkResourceType(parts[parts.length - 2]); 2778 } 2779 } 2780 2781 private boolean typesAreAllReference(List<TypeRefComponent> theType) { 2782 for (TypeRefComponent typeRefComponent : theType) { 2783 if (typeRefComponent.getCode().equals("Reference") == false) { 2784 return false; 2785 } 2786 } 2787 return true; 2788 } 2789 2790 private void validateBundle(List<ValidationMessage> errors, Element bundle, NodeStack stack) { 2791 List<Element> entries = new ArrayList<Element>(); 2792 bundle.getNamedChildren("entry", entries); 2793 String type = bundle.getNamedChildValue("type"); 2794 if (entries.size() == 0) { 2795 rule(errors, IssueType.INVALID, stack.getLiteralPath(), !(type.equals("document") || type.equals("message")), "Documents or Messages must contain at least one entry"); 2796 } else { 2797 Element firstEntry = entries.get(0); 2798 NodeStack firstStack = stack.push(firstEntry, 0, null, null); 2799 String fullUrl = firstEntry.getNamedChildValue("fullUrl"); 2800 2801 if (type.equals("document")) { 2802 Element resource = firstEntry.getNamedChild("resource"); 2803 NodeStack localStack = firstStack.push(resource, -1, null, null); 2804 String id = resource.getNamedChildValue("id"); 2805 if (rule(errors, IssueType.INVALID, firstEntry.line(), firstEntry.col(), stack.addToLiteralPath("entry", ":0"), resource != null, "No resource on first entry")) { 2806 validateDocument(errors, entries, resource, localStack.push(resource, -1, null, null), fullUrl, id); 2807 } 2808 checkAllInterlinked(errors, entries, stack, bundle); 2809 } 2810 if (type.equals("message")) { 2811 Element resource = firstEntry.getNamedChild("resource"); 2812 NodeStack localStack = firstStack.push(resource, -1, null, null); 2813 String id = resource.getNamedChildValue("id"); 2814 if (rule(errors, IssueType.INVALID, firstEntry.line(), firstEntry.col(), stack.addToLiteralPath("entry", ":0"), resource != null, "No resource on first entry")) { 2815 validateMessage(errors, entries, resource, localStack.push(resource, -1, null, null), fullUrl, id); 2816 } 2817 checkAllInterlinked(errors, entries, stack, bundle); 2818 } 2819 } 2820 } 2821 2822 private void checkAllInterlinked(List<ValidationMessage> errors, List<Element> entries, NodeStack stack, Element bundle) { 2823 List<Element> visitedResources = new ArrayList<Element>(); 2824 HashMap<Element,Element> candidateEntries = new HashMap<Element,Element>(); 2825 List<Element> candidateResources = new ArrayList<Element>(); 2826 for (Element entry: entries) { 2827 candidateEntries.put(entry.getNamedChild("resource"), entry); 2828 candidateResources.add(entry.getNamedChild("resource")); 2829 } 2830 followResourceLinks(entries.get(0), visitedResources, candidateEntries, candidateResources, true, errors, stack); 2831 List<Element> unusedResources = new ArrayList<Element>(); 2832 unusedResources.addAll(candidateResources); 2833 unusedResources.removeAll(visitedResources); 2834 int i = 0; 2835 for (Element entry : entries) { 2836 warning(errors, IssueType.INFORMATIONAL, entry.line(), entry.col(), stack.addToLiteralPath("entry", Integer.toString(i)), !unusedResources.contains(entry.getNamedChild("resource")), "Entry isn't reachable by traversing from first Bundle entry"); 2837 i++; 2838 } 2839 // Todo - check if the remaining resources point *to* the elements in the referenced set. Any that are still left over are errors 2840 } 2841 2842 private void followResourceLinks(Element entry, List<Element> visitedResources, HashMap<Element, Element> candidateEntries, List<Element> candidateResources, boolean referenced, List<ValidationMessage> errors, NodeStack stack) { 2843 Element resource = entry.getNamedChild("resource"); 2844 if (visitedResources.contains(resource)) 2845 return; 2846 2847 if (referenced) 2848 visitedResources.add(resource); 2849 2850 List<String> references = findReferences(resource); 2851 for (String reference: references) { 2852 Element r = getFromBundle(stack.getElement(), reference, entry.getChildValue("fullUrl"), errors, stack.addToLiteralPath("entry:" + candidateResources.indexOf(resource))); 2853 if (r!=null && !visitedResources.contains(r)) { 2854 followResourceLinks(candidateEntries.get(r), visitedResources, candidateEntries, candidateResources, referenced, errors, stack); 2855 } 2856 } 2857 } 2858 2859 private List<String> findReferences(Element start) { 2860 List<String> references = new ArrayList<String>(); 2861 findReferences(start, references); 2862 return references; 2863 } 2864 2865 private void findReferences(Element start, List<String> references) { 2866 for (Element child : start.getChildren()) { 2867 if (child.getType().equals("Reference")) { 2868 String ref = child.getChildValue("reference"); 2869 if (ref!=null && !ref.startsWith("#")) 2870 references.add(ref); 2871 } 2872 findReferences(child, references); 2873 } 2874 } 2875 2876 private void validateBundleReference(List<ValidationMessage> errors, List<Element> entries, Element ref, String name, NodeStack stack, String fullUrl, String type, String id) { 2877 String reference = null; 2878 try { 2879 reference = ref.getNamedChildValue("reference"); 2880 } catch (Error e) { 2881 2882 } 2883 2884 if (ref != null && !Utilities.noString(reference)) { 2885 Element target = resolveInBundle(entries, reference, fullUrl, type, id); 2886 rule(errors, IssueType.INVALID, ref.line(), ref.col(), stack.addToLiteralPath("reference"), target != null, "Unable to resolve the target of the reference in the bundle (" + name + ")"); 2887 } 2888 } 2889 2890 private void validateContains(Object appContext, List<ValidationMessage> errors, String path, ElementDefinition child, ElementDefinition context, Element resource, Element element, NodeStack stack, IdStatus idstatus) throws FHIRException, FHIRException, IOException { 2891 String resourceName = element.getType(); 2892 long t = System.nanoTime(); 2893 StructureDefinition profile = this.context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + resourceName); 2894 sdTime = sdTime + (System.nanoTime() - t); 2895 // special case: resource wrapper is reset if we're crossing a bundle boundary, but not otherwise 2896 if (element.getSpecial() == SpecialElement.BUNDLE_ENTRY || element.getSpecial() == SpecialElement.BUNDLE_OUTCOME || element.getSpecial() == SpecialElement.PARAMETER ) 2897 resource = element; 2898 if (rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), profile != null, "No profile found for contained resource of type '" + resourceName + "'")) 2899 validateResource(appContext, errors, resource, element, profile, null, idstatus, stack); 2900 } 2901 2902 private void validateDocument(List<ValidationMessage> errors, List<Element> entries, Element composition, NodeStack stack, String fullUrl, String id) { 2903 // first entry must be a composition 2904 if (rule(errors, IssueType.INVALID, composition.line(), composition.col(), stack.getLiteralPath(), composition.getType().equals("Composition"), 2905 "The first entry in a document must be a composition")) { 2906 // the composition subject and section references must resolve in the bundle 2907 Element elem = composition.getNamedChild("subject"); 2908 if (rule(errors, IssueType.INVALID, composition.line(), composition.col(), stack.getLiteralPath(), elem != null, "A document composition must have a subject")) 2909 validateBundleReference(errors, entries, elem, "Composition Subject", stack.push(elem, -1, null, null), fullUrl, "Composition", id); 2910 validateSections(errors, entries, composition, stack, fullUrl, id); 2911 } 2912 } 2913 // rule(errors, IssueType.INVALID, bundle.line(), bundle.col(), "Bundle", !"urn:guid:".equals(base), "The base 'urn:guid:' is not valid (use urn:uuid:)"); 2914 // rule(errors, IssueType.INVALID, entry.line(), entry.col(), localStack.getLiteralPath(), !"urn:guid:".equals(ebase), "The base 'urn:guid:' is not valid"); 2915 // rule(errors, IssueType.INVALID, entry.line(), entry.col(), localStack.getLiteralPath(), !Utilities.noString(base) || !Utilities.noString(ebase), "entry 2916 // does not have a base"); 2917 // String firstBase = null; 2918 // firstBase = ebase == null ? base : ebase; 2919 2920 private void validateElement(Object appContext, List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition, StructureDefinition cprofile, ElementDefinition context, 2921 Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept) throws FHIRException, FHIRException, IOException { 2922 element.markValidation(profile, definition); 2923 2924 // System.out.println(" "+stack.getLiteralPath()+" "+Long.toString((System.nanoTime() - time) / 1000000)); 2925 // time = System.nanoTime(); 2926 if (resource.getName().equals("contained")) { 2927 NodeStack ancestor = stack; 2928 while (!ancestor.element.isResource() || ancestor.element.getName().equals("contained")) 2929 ancestor = ancestor.parent; 2930 checkInvariants(errors, stack.getLiteralPath(), profile, definition, null, null, ancestor.element, element); 2931 } else 2932 checkInvariants(errors, stack.getLiteralPath(), profile, definition, null, null, resource, element); 2933 if (definition.getFixed()!=null) 2934 checkFixedValue(errors, stack.getLiteralPath(), element, definition.getFixed(), definition.getSliceName(), null); 2935 2936 2937 // get the list of direct defined children, including slices 2938 List<ElementDefinition> childDefinitions = ProfileUtilities.getChildMap(profile, definition); 2939 if (childDefinitions.isEmpty()) { 2940 if (actualType == null) 2941 return; // there'll be an error elsewhere in this case, and we're going to stop. 2942 2943 StructureDefinition dt = this.context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + actualType); 2944 if (dt == null) 2945 throw new DefinitionException("Unable to resolve actual type " + actualType); 2946 2947 childDefinitions = ProfileUtilities.getChildMap(dt, dt.getSnapshot().getElement().get(0)); 2948 } 2949 2950 // 1. List the children, and remember their exact path (convenience) 2951 List<ElementInfo> children = new ArrayList<InstanceValidator.ElementInfo>(); 2952 ChildIterator iter = new ChildIterator(stack.getLiteralPath(), element); 2953 while (iter.next()) 2954 children.add(new ElementInfo(iter.name(), iter.element(), iter.path(), iter.count())); 2955 2956 // 2. assign children to a definition 2957 // for each definition, for each child, check whether it belongs in the slice 2958 ElementDefinition slicer = null; 2959 boolean unsupportedSlicing = false; 2960 List<String> problematicPaths = new ArrayList<String>(); 2961 String slicingPath = null; 2962 int sliceOffset = 0; 2963 for (int i = 0; i < childDefinitions.size(); i++) { 2964 ElementDefinition ed = childDefinitions.get(i); 2965 boolean childUnsupportedSlicing = false; 2966 boolean process = true; 2967 if (ed.hasSlicing() && !ed.getSlicing().getOrdered()) 2968 slicingPath = ed.getPath(); 2969 else if (slicingPath!=null && ed.getPath().equals(slicingPath)) 2970 ; // nothing 2971 else if (slicingPath != null && !ed.getPath().startsWith(slicingPath)) 2972 slicingPath = null; 2973 // where are we with slicing 2974 if (ed.hasSlicing()) { 2975 if (slicer != null && slicer.getPath().equals(ed.getPath())) 2976 throw new DefinitionException("Slice encountered midway through path on " + slicer.getPath()); 2977 slicer = ed; 2978 process = false; 2979 sliceOffset = i; 2980 } else if (slicer != null && !slicer.getPath().equals(ed.getPath())) 2981 slicer = null; 2982 2983// if (process) { 2984 for (ElementInfo ei : children) { 2985 boolean match = false; 2986 if (slicer == null || slicer == ed) { 2987 match = nameMatches(ei.name, tail(ed.getPath())); 2988 } else { 2989// ei.slice = slice; 2990 if (nameMatches(ei.name, tail(ed.getPath()))) 2991 try { 2992 match = sliceMatches(appContext, ei.element, ei.path, slicer, ed, profile, errors, stack); 2993 if (match) 2994 ei.slice = slicer; 2995 } catch (FHIRException e) { 2996 warning(errors, IssueType.PROCESSING, ei.line(), ei.col(), ei.path, false, e.getMessage()); 2997 unsupportedSlicing = true; 2998 childUnsupportedSlicing = true; 2999 } 3000 } 3001 if (match) { 3002 if (rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, ei.definition == null || ei.definition == slicer, "Profile " + profile.getUrl() + ", Element matches more than one slice")) { 3003 ei.definition = ed; 3004 if (ei.slice == null) { 3005 ei.index = i; 3006 } else { 3007 ei.index = sliceOffset; 3008 ei.sliceindex = i - (sliceOffset + 1); 3009 } 3010 } 3011 } else if (childUnsupportedSlicing) { 3012 problematicPaths.add(ed.getPath()); 3013 } 3014 } 3015// } 3016 } 3017 int last = -1; 3018 int lastSlice = -1; 3019 for (ElementInfo ei : children) { 3020 String sliceInfo = ""; 3021 if (slicer != null) 3022 sliceInfo = " (slice: " + slicer.getPath()+")"; 3023 if (ei.path.endsWith(".extension")) 3024 rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, ei.definition != null, "Element is unknown or does not match any slice (url=\"" + ei.element.getNamedChildValue("url") + "\")" + (profile==null ? "" : " for profile " + profile.getUrl())); 3025 else if (!unsupportedSlicing) 3026 if (ei.slice!=null && (ei.slice.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPEN) || ei.slice.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPEN))) 3027 hint(errors, IssueType.INFORMATIONAL, ei.line(), ei.col(), ei.path, (ei.definition != null), 3028 "Element " + ei.element.getName() + " is unknown or does not match any slice " + sliceInfo + (profile==null ? "" : " for profile " + profile.getUrl())); 3029 else 3030 if (ei.slice!=null && (ei.slice.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPEN) || ei.slice.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPEN))) 3031 rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, (ei.definition != null), 3032 "Element " + ei.element.getName() + " is unknown or does not match any slice " + sliceInfo + (profile==null ? "" : " for profile " + profile.getUrl())); 3033 else 3034 hint(errors, IssueType.NOTSUPPORTED, ei.line(), ei.col(), ei.path, (ei.definition != null), 3035 "Could not verify slice for profile " + profile.getUrl()); 3036 // TODO: Should get the order of elements correct when parsing elements that are XML attributes vs. elements 3037 boolean isXmlAttr = false; 3038 if (ei.definition!=null) 3039 for (Enumeration<PropertyRepresentation> r : ei.definition.getRepresentation()) { 3040 if (r.getValue() == PropertyRepresentation.XMLATTR) { 3041 isXmlAttr = true; 3042 break; 3043 } 3044 } 3045 3046 rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, (ei.definition == null) || (ei.index >= last) || isXmlAttr, "As specified by profile " + profile.getUrl() + ", Element '"+ei.name+"' is out of order"); 3047 if (ei.slice != null && ei.index == last && ei.slice.getSlicing().getOrdered()) 3048 rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, (ei.definition == null) || (ei.sliceindex >= lastSlice) || isXmlAttr, "As specified by profile " + profile.getUrl() + ", Element '"+ei.name+"' is out of order in ordered slice"); 3049 if (ei.definition == null || !isXmlAttr) 3050 last = ei.index; 3051 if (ei.slice != null) 3052 lastSlice = ei.sliceindex; 3053 else 3054 lastSlice = -1; 3055 } 3056 3057 // 3. report any definitions that have a cardinality problem 3058 for (ElementDefinition ed : childDefinitions) { 3059 if (ed.getRepresentation().isEmpty()) { // ignore xml attributes 3060 int count = 0; 3061 List<ElementDefinition> slices = null; 3062 if (ed.hasSlicing()) 3063 slices = ProfileUtilities.getSliceList(profile, ed); 3064 for (ElementInfo ei : children) 3065 if (ei.definition == ed) 3066 count++; 3067 else if (slices!=null) { 3068 for (ElementDefinition sed : slices) { 3069 if (ei.definition == sed) { 3070 count++; 3071 break; 3072 } 3073 } 3074 } 3075 String location = "Profile " + profile.getUrl() + ", Element '" + stack.getLiteralPath() + "." + tail(ed.getPath()) + (ed.hasSliceName()? "[" + ed.getSliceName() + "]": ""); 3076 if (ed.getMin() > 0) { 3077 if (problematicPaths.contains(ed.getPath())) 3078 hint(errors, IssueType.NOTSUPPORTED, element.line(), element.col(), stack.getLiteralPath(), count >= ed.getMin(), 3079 location + "': Unable to check minimum required (" + Integer.toString(ed.getMin()) + ") due to lack of slicing validation"); 3080 else 3081 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), count >= ed.getMin(), 3082 location + "': minimum required = " + Integer.toString(ed.getMin()) + ", but only found " + Integer.toString(count)); 3083 } 3084 if (ed.hasMax() && !ed.getMax().equals("*")) { 3085 if (problematicPaths.contains(ed.getPath())) 3086 hint(errors, IssueType.NOTSUPPORTED, element.line(), element.col(), stack.getLiteralPath(), count <= Integer.parseInt(ed.getMax()), 3087 location + ": Unable to check max allowed (" + ed.getMax() + ") due to lack of slicing validation"); 3088 else 3089 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), count <= Integer.parseInt(ed.getMax()), 3090 location + ": max allowed = " + ed.getMax() + ", but found " + Integer.toString(count)); 3091 } 3092 } 3093 } 3094 // 4. check order if any slices are ordered. (todo) 3095 3096 // 5. inspect each child for validity 3097 for (ElementInfo ei : children) { 3098 List<String> profiles = new ArrayList<String>(); 3099 if (ei.definition != null) { 3100 String type = null; 3101 ElementDefinition typeDefn = null; 3102 if (ei.definition.getType().size() == 1 && !ei.definition.getType().get(0).getCode().equals("*") && !ei.definition.getType().get(0).getCode().equals("Element") 3103 && !ei.definition.getType().get(0).getCode().equals("BackboneElement")) { 3104 type = ei.definition.getType().get(0).getCode(); 3105 // Excluding reference is a kludge to get around versioning issues 3106 if (ei.definition.getType().get(0).hasProfile() && !type.equals("Reference")) 3107 profiles.add(ei.definition.getType().get(0).getProfile()); 3108 3109 } else if (ei.definition.getType().size() == 1 && ei.definition.getType().get(0).getCode().equals("*")) { 3110 String prefix = tail(ei.definition.getPath()); 3111 assert prefix.endsWith("[x]"); 3112 type = ei.name.substring(prefix.length() - 3); 3113 if (isPrimitiveType(type)) 3114 type = Utilities.uncapitalize(type); 3115 // Excluding reference is a kludge to get around versioning issues 3116 if (ei.definition.getType().get(0).hasProfile() && !type.equals("Reference")) 3117 profiles.add(ei.definition.getType().get(0).getProfile()); 3118 } else if (ei.definition.getType().size() > 1) { 3119 3120 String prefix = tail(ei.definition.getPath()); 3121 assert typesAreAllReference(ei.definition.getType()) || prefix.endsWith("[x]") : prefix; 3122 3123 prefix = prefix.substring(0, prefix.length() - 3); 3124 for (TypeRefComponent t : ei.definition.getType()) 3125 if ((prefix + Utilities.capitalize(t.getCode())).equals(ei.name)) { 3126 type = t.getCode(); 3127 // Excluding reference is a kludge to get around versioning issues 3128 if (t.hasProfile() && !type.equals("Reference")) 3129 profiles.add(t.getProfile()); 3130 } 3131 if (type == null) { 3132 TypeRefComponent trc = ei.definition.getType().get(0); 3133 if (trc.getCode().equals("Reference")) 3134 type = "Reference"; 3135 else 3136 rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), stack.getLiteralPath(), false, 3137 "The element " + ei.name + " is illegal. Valid types at this point are " + describeTypes(ei.definition.getType())); 3138 } 3139 } else if (ei.definition.getContentReference() != null) { 3140 typeDefn = resolveNameReference(profile.getSnapshot(), ei.definition.getContentReference()); 3141 } 3142 3143 if (type != null) { 3144 if (type.startsWith("@")) { 3145 ei.definition = findElement(profile, type.substring(1)); 3146 type = null; 3147 } 3148 } 3149 NodeStack localStack = stack.push(ei.element, ei.count, ei.definition, type == null ? typeDefn : resolveType(type)); 3150 String localStackLiterapPath = localStack.getLiteralPath(); 3151 String eiPath = ei.path; 3152 assert(eiPath.equals(localStackLiterapPath)) : "ei.path: " + ei.path + " - localStack.getLiteralPath: " + localStackLiterapPath; 3153 boolean thisIsCodeableConcept = false; 3154 3155 if (type != null) { 3156 if (isPrimitiveType(type)) { 3157 checkPrimitive(appContext, errors, ei.path, type, ei.definition, ei.element, profile); 3158 } else { 3159 if (type.equals("Identifier")) 3160 checkIdentifier(errors, ei.path, ei.element, ei.definition); 3161 else if (type.equals("Coding")) 3162 checkCoding(errors, ei.path, ei.element, profile, ei.definition, inCodeableConcept); 3163 else if (type.equals("CodeableConcept")) { 3164 checkCodeableConcept(errors, ei.path, ei.element, profile, ei.definition); 3165 thisIsCodeableConcept = true; 3166 } else if (type.equals("Reference")) 3167 checkReference(appContext, errors, ei.path, ei.element, profile, ei.definition, actualType, localStack); 3168 3169 // We only check extensions if we're not in a complex extension or if the element we're dealing with is not defined as part of that complex extension 3170 if (type.equals("Extension") && ei.element.getChildValue("url").contains("/")) 3171 checkExtension(appContext, errors, ei.path, resource, ei.element, ei.definition, profile, localStack); 3172 else if (type.equals("Resource")) 3173 validateContains(appContext, errors, ei.path, ei.definition, definition, resource, ei.element, localStack, idStatusForEntry(element, ei)); // if 3174 // (str.matches(".*([.,/])work\\1$")) 3175 else { 3176 StructureDefinition p = null; 3177 boolean elementValidated = false; 3178 if (profiles.isEmpty()) { 3179 p = getProfileForType(type); 3180 rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.path, p != null, "Unknown type " + type); 3181 } else if (profiles.size()==1) { 3182 p = this.context.fetchResource(StructureDefinition.class, profiles.get(0)); 3183 rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.path, p != null, "Unknown profile " + profiles.get(0)); 3184 } else { 3185 elementValidated = true; 3186 HashMap<String, List<ValidationMessage>> goodProfiles = new HashMap<String, List<ValidationMessage>>(); 3187 List<List<ValidationMessage>> badProfiles = new ArrayList<List<ValidationMessage>>(); 3188 for (String typeProfile : profiles) { 3189 p = this.context.fetchResource(StructureDefinition.class, typeProfile); 3190 if (rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.path, p != null, "Unknown profile " + typeProfile)) { 3191 List<ValidationMessage> profileErrors = new ArrayList<ValidationMessage>(); 3192 validateElement(appContext, profileErrors, p, p.getSnapshot().getElement().get(0), profile, ei.definition, resource, ei.element, type, localStack, thisIsCodeableConcept); 3193 boolean hasError = false; 3194 for (ValidationMessage msg : profileErrors) { 3195 if (msg.getLevel()==ValidationMessage.IssueSeverity.ERROR || msg.getLevel()==ValidationMessage.IssueSeverity.FATAL) { 3196 hasError = true; 3197 break; 3198 } 3199 } 3200 if (hasError) 3201 badProfiles.add(profileErrors); 3202 else 3203 goodProfiles.put(typeProfile, profileErrors); 3204 } 3205 if (goodProfiles.size()==1) { 3206 errors.addAll(goodProfiles.get(0)); 3207 } else if (goodProfiles.size()==0) { 3208 rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.path, false, "Unable to find matching profile among choices: " + StringUtils.join("; ", profiles)); 3209 for (List<ValidationMessage> messages : badProfiles) { 3210 errors.addAll(messages); 3211 } 3212 } else { 3213 warning(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.path, false, "Found multiple matching profiles among choices: " + StringUtils.join("; ", goodProfiles.keySet())); 3214 for (List<ValidationMessage> messages : goodProfiles.values()) { 3215 errors.addAll(messages); 3216 } 3217 } 3218 } 3219 } 3220 if (p!=null) { 3221 if (!elementValidated) 3222 validateElement(appContext, errors, p, p.getSnapshot().getElement().get(0), profile, ei.definition, resource, ei.element, type, localStack, thisIsCodeableConcept); 3223 int index = profile.getSnapshot().getElement().indexOf(ei.definition); 3224 if (index < profile.getSnapshot().getElement().size() - 1) { 3225 String nextPath = profile.getSnapshot().getElement().get(index+1).getPath(); 3226 if (!nextPath.equals(ei.definition.getPath()) && nextPath.startsWith(ei.definition.getPath())) 3227 validateElement(appContext, errors, profile, ei.definition, null, null, resource, ei.element, type, localStack, thisIsCodeableConcept); 3228 } 3229 } 3230 } 3231 } 3232 } else { 3233 if (rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), stack.getLiteralPath(), ei.definition != null, "Unrecognised Content " + ei.name)) 3234 validateElement(appContext, errors, profile, ei.definition, null, null, resource, ei.element, type, localStack, false); 3235 } 3236 } 3237 } 3238 } 3239 3240 private IdStatus idStatusForEntry(Element ep, ElementInfo ei) { 3241 if (isBundleEntry(ei.path)) { 3242 Element req = ep.getNamedChild("request"); 3243 Element resp = ep.getNamedChild("response"); 3244 Element fullUrl = ep.getNamedChild("fullUrl"); 3245 Element method = null; 3246 Element url = null; 3247 if (req != null) { 3248 method = req.getNamedChild("method"); 3249 url = req.getNamedChild("url"); 3250 } 3251 if (resp != null) { 3252 return IdStatus.OPTIONAL; 3253 } if (method == null) { 3254 if (fullUrl == null) 3255 return IdStatus.REQUIRED; 3256 else if (fullUrl.primitiveValue().startsWith("urn:uuid:") || fullUrl.primitiveValue().startsWith("urn:oid:")) 3257 return IdStatus.OPTIONAL; 3258 else 3259 return IdStatus.REQUIRED; 3260 } else { 3261 String s = method.primitiveValue(); 3262 if (s.equals("PUT")) { 3263 if (url == null) 3264 return IdStatus.REQUIRED; 3265 else 3266 return IdStatus.OPTIONAL; // or maybe prohibited? not clear 3267 } else if (s.equals("POST")) 3268 return IdStatus.OPTIONAL; // this should be prohibited, but see task 9102 3269 else // actually, we should never get to here; a bundle entry with method get/delete should not have a resource 3270 return IdStatus.OPTIONAL; 3271 } 3272 } else if (isParametersEntry(ei.path) || isBundleOutcome(ei.path)) 3273 return IdStatus.OPTIONAL; 3274 else 3275 return IdStatus.REQUIRED; 3276 } 3277 3278 private void checkInvariants(List<ValidationMessage> errors, String path, StructureDefinition profile, ElementDefinition ed, String typename, String typeProfile, Element resource, Element element) throws FHIRException, FHIRException { 3279 if (noInvariantChecks) 3280 return; 3281 3282 for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) { 3283 if (inv.hasExpression()) { 3284 ExpressionNode n = (ExpressionNode) inv.getUserData("validator.expression.cache"); 3285 if (n == null) { 3286 long t = System.nanoTime(); 3287 try { 3288 n = fpe.parse(inv.getExpression()); 3289 } catch (FHIRLexerException e) { 3290 throw new FHIRException("Problem processing expression "+inv.getExpression() +" in profile " + profile.getUrl() + " path " + path + ": " + e.getMessage()); 3291 } 3292 fpeTime = fpeTime + (System.nanoTime() - t); 3293 inv.setUserData("validator.expression.cache", n); 3294 } 3295 3296 String msg; 3297 boolean ok; 3298 try { 3299 long t = System.nanoTime(); 3300 ok = fpe.evaluateToBoolean(resource, element, n); 3301 fpeTime = fpeTime + (System.nanoTime() - t); 3302 msg = fpe.forLog(); 3303 } catch (Exception ex) { 3304 ok = false; 3305 msg = ex.getMessage(); 3306 } 3307 if (!ok) { 3308 try { 3309 ok = fpe.evaluateToBoolean(resource, element, n); 3310 } catch (PathEngineException e) { 3311 throw new FHIRException("Problem processing expression "+inv.getExpression() +" in profile " + profile.getUrl() + " path " + path + ": " + e.getMessage()); 3312 } 3313 if (!Utilities.noString(msg)) 3314 msg = " ("+msg+")"; 3315 if (inv.getSeverity() == ConstraintSeverity.ERROR) 3316 rule(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getHuman()+msg+" ["+inv.getExpression()+"]"); 3317 else if (inv.getSeverity() == ConstraintSeverity.WARNING) 3318 warning(errors, IssueType.INVARIANT, element.line(), element.line(), path, ok, inv.getHuman()+msg+" ["+inv.getExpression()+"]"); 3319 } 3320 } 3321 } 3322 } 3323 3324 private void validateMessage(List<ValidationMessage> errors, List<Element> entries, Element messageHeader, NodeStack stack, String fullUrl, String id) { 3325 // first entry must be a messageheader 3326 if (rule(errors, IssueType.INVALID, messageHeader.line(), messageHeader.col(), stack.getLiteralPath(), messageHeader.getType().equals("MessageHeader"), 3327 "The first entry in a message must be a MessageHeader")) { 3328 // the composition subject and section references must resolve in the bundle 3329 List<Element> elements = messageHeader.getChildren("data"); 3330 for (Element elem: elements) 3331 validateBundleReference(errors, entries, elem, "MessageHeader Data", stack.push(elem, -1, null, null), fullUrl, "MessageHeader", id); 3332 } 3333 } 3334 3335 private void validateObservation(List<ValidationMessage> errors, Element element, NodeStack stack) { 3336 // all observations should have a subject, a performer, and a time 3337 3338 bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), element.getNamedChild("subject") != null, "All observations should have a subject"); 3339 bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), element.getNamedChild("performer") != null, "All observations should have a performer"); 3340 bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), element.getNamedChild("effectiveDateTime") != null || element.getNamedChild("effectivePeriod") != null, 3341 "All observations should have an effectiveDateTime or an effectivePeriod"); 3342 } 3343 3344 /* 3345 * The actual base entry point 3346 */ 3347 /* private void validateResource(List<ValidationMessage> errors, Element resource, Element element, StructureDefinition defn, ValidationProfileSet profiles, IdStatus idstatus, NodeStack stack) throws FHIRException, FHIRException { 3348 List<StructureDefinition> declProfiles = new ArrayList<StructureDefinition>(); 3349 List<Element> meta = element.getChildrenByName("meta"); 3350 if (!meta.isEmpty()) { 3351 for (Element profileName : meta.get(0).getChildrenByName("profile")) { 3352 StructureDefinition sd = context.fetchResource(StructureDefinition.class, profileName.getValue()); 3353 if (sd != null) 3354 declProfiles.add(sd); 3355 } 3356 } 3357 3358 if (!declProfiles.isEmpty()) { 3359 // Validate against profiles rather than the resource itself as they'll be more constrained and will cover the resource elements anyhow 3360 for (StructureDefinition sd : declProfiles) 3361 validateResource2(errors, resource, element, sd, profiles, idstatus, stack); 3362 } else 3363 validateResource2(errors, resource, element, defn, profiles, idstatus, stack); 3364 }*/ 3365 3366 private void validateResource(Object appContext, List<ValidationMessage> errors, Element resource, Element element, StructureDefinition defn, ValidationProfileSet profiles, IdStatus idstatus, NodeStack stack) throws FHIRException, FHIRException, IOException { 3367 assert stack != null; 3368 assert resource != null; 3369 3370 boolean ok = true; 3371 3372 String resourceName = element.getType(); // todo: consider namespace...? 3373 if (defn == null) { 3374 long t = System.nanoTime(); 3375 defn = element.getProperty().getStructure(); 3376 if (defn == null) 3377 defn = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + resourceName); 3378 if (profiles!=null) 3379 getResourceProfiles(resource, stack).addProfiles(errors, profiles, stack.getLiteralPath(), element); 3380 sdTime = sdTime + (System.nanoTime() - t); 3381 ok = rule(errors, IssueType.INVALID, element.line(), element.col(), stack.addToLiteralPath(resourceName), defn != null, "No definition found for resource type '" + resourceName + "'"); 3382 } 3383 3384 String type = defn.getKind() == StructureDefinitionKind.LOGICAL ? defn.getId() : defn.getType(); 3385 // special case: we have a bundle, and the profile is not for a bundle. We'll try the first entry instead 3386 if (!type.equals(resourceName) && resourceName.equals("Bundle")) { 3387 Element first = getFirstEntry(element); 3388 if (first != null && first.getType().equals(type)) { 3389 element = first; 3390 resourceName = element.getType(); 3391 idstatus = IdStatus.OPTIONAL; // why? 3392 } 3393 } 3394 ok = rule(errors, IssueType.INVALID, -1, -1, stack.getLiteralPath(), type.equals(resourceName), "Specified profile type was '" + type + "', but found type '" + resourceName + "'"); 3395 3396 if (ok) { 3397 if (idstatus == IdStatus.REQUIRED && (element.getNamedChild("id") == null)) 3398 rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, "Resource requires an id, but none is present"); 3399 else if (idstatus == IdStatus.PROHIBITED && (element.getNamedChild("id") != null)) 3400 rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, "Resource has an id, but none is allowed"); 3401 start(appContext, errors, resource, element, defn, stack); // root is both definition and type 3402 } 3403 } 3404 3405 private void loadProfiles(ValidationProfileSet profiles) throws DefinitionException { 3406 if (profiles != null) { 3407 for (String profile : profiles.getCanonicalUrls()) { 3408 StructureDefinition p = context.fetchResource(StructureDefinition.class, profile); 3409 if (p == null) 3410 throw new DefinitionException("StructureDefinition '" + profile + "' not found"); 3411 profiles.getDefinitions().add(p); 3412 } 3413 } 3414 } 3415 3416 private Element getFirstEntry(Element bundle) { 3417 List<Element> list = new ArrayList<Element>(); 3418 bundle.getNamedChildren("entry", list); 3419 if (list.isEmpty()) 3420 return null; 3421 Element resource = list.get(0).getNamedChild("resource"); 3422 if (resource == null) 3423 return null; 3424 else 3425 return resource; 3426 } 3427 3428 private void validateSections(List<ValidationMessage> errors, List<Element> entries, Element focus, NodeStack stack, String fullUrl, String id) { 3429 List<Element> sections = new ArrayList<Element>(); 3430 focus.getNamedChildren("entry", sections); 3431 int i = 0; 3432 for (Element section : sections) { 3433 NodeStack localStack = stack.push(section, 1, null, null); 3434 validateBundleReference(errors, entries, section.getNamedChild("content"), "Section Content", localStack, fullUrl, "Composition", id); 3435 validateSections(errors, entries, section, localStack, fullUrl, id); 3436 i++; 3437 } 3438 } 3439 3440 private boolean valueMatchesCriteria(Element value, ElementDefinition criteria) throws FHIRException { 3441 if (criteria.hasFixed()) { 3442 List<ValidationMessage> msgs = new ArrayList<ValidationMessage>(); 3443 checkFixedValue(msgs, "{virtual}", value, criteria.getFixed(), "value", null); 3444 return msgs.size() == 0; 3445 } else if (criteria.hasBinding() && criteria.getBinding().getStrength() == BindingStrength.REQUIRED && criteria.getBinding().hasValueSet()) { 3446 throw new FHIRException("Unable to resolve slice matching - slice matching by value set not done"); 3447 } else { 3448 throw new FHIRException("Unable to resolve slice matching - no fixed value or required value set"); 3449 } 3450 } 3451 3452 private boolean yearIsValid(String v) { 3453 if (v == null) { 3454 return false; 3455 } 3456 try { 3457 int i = Integer.parseInt(v.substring(0, Math.min(4, v.length()))); 3458 return i >= 1800 && i <= 2100; 3459 } catch (NumberFormatException e) { 3460 return false; 3461 } 3462 } 3463 3464 public class ChildIterator { 3465 private String basePath; 3466 private Element parent; 3467 private int cursor; 3468 private int lastCount; 3469 3470 public ChildIterator(String path, Element element) { 3471 parent = element; 3472 basePath = path; 3473 cursor = -1; 3474 } 3475 3476 public int count() { 3477 String nb = cursor == 0 ? "--" : parent.getChildren().get(cursor-1).getName(); 3478 String na = cursor >= parent.getChildren().size() - 1 ? "--" : parent.getChildren().get(cursor+1).getName(); 3479 if (name().equals(nb) || name().equals(na) ) { 3480 return lastCount + 1; 3481 } else 3482 return -1; 3483 } 3484 3485 public Element element() { 3486 return parent.getChildren().get(cursor); 3487 } 3488 3489 public String name() { 3490 return element().getName(); 3491 } 3492 3493 public boolean next() { 3494 if (cursor == -1) { 3495 cursor++; 3496 lastCount = 0; 3497 } else { 3498 String lastName = name(); 3499 cursor++; 3500 if (cursor < parent.getChildren().size() && name().equals(lastName)) 3501 lastCount++; 3502 else 3503 lastCount = 0; 3504 } 3505 return cursor < parent.getChildren().size(); 3506 } 3507 3508 public String path() { 3509 int i = count(); 3510 String sfx = ""; 3511 if (i > -1) { 3512 sfx = "[" + Integer.toString(lastCount + 1) + "]"; 3513 } 3514 return basePath + "." + name() + sfx; 3515 } 3516 } 3517 3518 public class NodeStack { 3519 private ElementDefinition definition; 3520 private Element element; 3521 private ElementDefinition extension; 3522 private String literalPath; // xpath format 3523 private List<String> logicalPaths; // dotted format, various entry points 3524 private NodeStack parent; 3525 private ElementDefinition type; 3526 3527 public NodeStack() { 3528 } 3529 3530 public NodeStack(Element element) { 3531 this.element = element; 3532 literalPath = element.getName(); 3533 } 3534 3535 public String addToLiteralPath(String... path) { 3536 StringBuilder b = new StringBuilder(); 3537 b.append(getLiteralPath()); 3538 for (String p : path) { 3539 if (p.startsWith(":")) { 3540 b.append("["); 3541 b.append(p.substring(1)); 3542 b.append("]"); 3543 } else { 3544 b.append("."); 3545 b.append(p); 3546 } 3547 } 3548 return b.toString(); 3549 } 3550 3551 private ElementDefinition getDefinition() { 3552 return definition; 3553 } 3554 3555 private Element getElement() { 3556 return element; 3557 } 3558 3559 protected String getLiteralPath() { 3560 return literalPath == null ? "" : literalPath; 3561 } 3562 3563 private List<String> getLogicalPaths() { 3564 return logicalPaths == null ? new ArrayList<String>() : logicalPaths; 3565 } 3566 3567 private ElementDefinition getType() { 3568 return type; 3569 } 3570 3571 private NodeStack push(Element element, int count, ElementDefinition definition, ElementDefinition type) { 3572 NodeStack res = new NodeStack(); 3573 res.parent = this; 3574 res.element = element; 3575 res.definition = definition; 3576 res.literalPath = getLiteralPath() + "." + element.getName(); 3577 if (count > -1) 3578 res.literalPath = res.literalPath + "[" + Integer.toString(count) + "]"; 3579 res.logicalPaths = new ArrayList<String>(); 3580 if (type != null) { 3581 // type will be bull if we on a stitching point of a contained resource, or if.... 3582 res.type = type; 3583 String t = tail(definition.getPath()); 3584 for (String lp : getLogicalPaths()) { 3585 res.logicalPaths.add(lp + "." + t); 3586 if (t.endsWith("[x]")) 3587 res.logicalPaths.add(lp + "." + t.substring(0, t.length() - 3) + type.getPath()); 3588 } 3589 res.logicalPaths.add(type.getPath()); 3590 } else if (definition != null) { 3591 for (String lp : getLogicalPaths()) 3592 res.logicalPaths.add(lp + "." + element.getName()); 3593 } else 3594 res.logicalPaths.addAll(getLogicalPaths()); 3595 // CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 3596 // for (String lp : res.logicalPaths) 3597 // b.append(lp); 3598 // System.out.println(res.literalPath+" : "+b.toString()); 3599 return res; 3600 } 3601 3602 private void setType(ElementDefinition type) { 3603 this.type = type; 3604 } 3605 } 3606 3607 private void checkForProcessingInstruction(List<ValidationMessage> errors, Document document) { 3608 Node node = document.getFirstChild(); 3609 while (node != null) { 3610 rule(errors, IssueType.INVALID, -1, -1, "(document)", node.getNodeType() != Node.PROCESSING_INSTRUCTION_NODE, "No processing instructions allowed in resources"); 3611 node = node.getNextSibling(); 3612 } 3613 } 3614 3615 public class ElementInfo { 3616 3617 public int index; // order of definition in overall order. all slices get the index of the slicing definition 3618 public int sliceindex; // order of the definition in the slices (if slice != null) 3619 public int count; 3620 public ElementDefinition definition; 3621 public ElementDefinition slice; 3622 private Element element; 3623 private String name; 3624 private String path; 3625 3626 public ElementInfo(String name, Element element, String path, int count) { 3627 this.name = name; 3628 this.element = element; 3629 this.path = path; 3630 this.count = count; 3631 } 3632 3633 public int col() { 3634 return element.col(); 3635 } 3636 3637 public int line() { 3638 return element.line(); 3639 } 3640 3641 } 3642 3643 public String reportTimes() { 3644 String s = String.format("Times: overall = %d, tx = %d, sd = %d, load = %d, fpe = %d", overall, txTime, sdTime, loadTime, fpeTime); 3645 overall = 0; 3646 txTime = 0; 3647 sdTime = 0; 3648 loadTime = 0; 3649 fpeTime = 0; 3650 return s; 3651 } 3652 3653 public boolean isNoBindingMsgSuppressed() { 3654 return noBindingMsgSuppressed; 3655 } 3656 3657 public IResourceValidator setNoBindingMsgSuppressed(boolean noBindingMsgSuppressed) { 3658 this.noBindingMsgSuppressed = noBindingMsgSuppressed; 3659 return this; 3660 } 3661 3662 3663 public boolean isNoTerminologyChecks() { 3664 return noTerminologyChecks; 3665 } 3666 3667 public IResourceValidator setNoTerminologyChecks(boolean noTerminologyChecks) { 3668 this.noTerminologyChecks = noTerminologyChecks; 3669 return this; 3670 } 3671 3672 public void checkAllInvariants(){ 3673 for (StructureDefinition sd : context.allStructures()) { 3674 if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 3675 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 3676 for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) { 3677 if (inv.hasExpression()) { 3678 try { 3679 ExpressionNode n = (ExpressionNode) inv.getUserData("validator.expression.cache"); 3680 if (n == null) { 3681 n = fpe.parse(inv.getExpression()); 3682 inv.setUserData("validator.expression.cache", n); 3683 } 3684 fpe.check(null, sd.getKind() == StructureDefinitionKind.RESOURCE ? sd.getType() : "DomainResource", ed.getPath(), n); 3685 } catch (Exception e) { 3686 System.out.println("Error processing structure ["+sd.getId()+"] path "+ed.getPath()+":"+inv.getKey()+" (\""+inv.getExpression()+"\"): "+e.getMessage()); 3687 } 3688 } 3689 } 3690 } 3691 } 3692 } 3693 } 3694 3695}