001package org.hl7.fhir.dstu2016may.validation; 002 003import static org.apache.commons.lang3.StringUtils.isBlank; 004import static org.apache.commons.lang3.StringUtils.isNotBlank; 005 006import java.io.InputStream; 007import java.util.ArrayList; 008import java.util.HashMap; 009import java.util.List; 010import java.util.Map; 011 012import org.hl7.fhir.dstu2016may.formats.FormatUtilities; 013import org.hl7.fhir.dstu2016may.metamodel.Element; 014import org.hl7.fhir.dstu2016may.metamodel.Element.SpecialElement; 015import org.hl7.fhir.dstu2016may.metamodel.JsonParser; 016import org.hl7.fhir.dstu2016may.metamodel.Manager; 017import org.hl7.fhir.dstu2016may.metamodel.Manager.FhirFormat; 018import org.hl7.fhir.dstu2016may.metamodel.ParserBase; 019import org.hl7.fhir.dstu2016may.metamodel.ParserBase.ValidationPolicy; 020import org.hl7.fhir.dstu2016may.metamodel.XmlParser; 021import org.hl7.fhir.dstu2016may.model.Address; 022import org.hl7.fhir.dstu2016may.model.Attachment; 023import org.hl7.fhir.dstu2016may.model.Bundle; 024import org.hl7.fhir.dstu2016may.model.Bundle.BundleEntryComponent; 025import org.hl7.fhir.dstu2016may.model.CodeSystem; 026import org.hl7.fhir.dstu2016may.model.CodeSystem.ConceptDefinitionComponent; 027import org.hl7.fhir.dstu2016may.model.CodeableConcept; 028import org.hl7.fhir.dstu2016may.model.Coding; 029import org.hl7.fhir.dstu2016may.model.ContactPoint; 030import org.hl7.fhir.dstu2016may.model.DateType; 031import org.hl7.fhir.dstu2016may.model.DomainResource; 032import org.hl7.fhir.dstu2016may.model.ElementDefinition; 033import org.hl7.fhir.dstu2016may.model.ElementDefinition.ConstraintSeverity; 034import org.hl7.fhir.dstu2016may.model.ElementDefinition.ElementDefinitionBindingComponent; 035import org.hl7.fhir.dstu2016may.model.ElementDefinition.ElementDefinitionConstraintComponent; 036import org.hl7.fhir.dstu2016may.model.ElementDefinition.TypeRefComponent; 037import org.hl7.fhir.dstu2016may.model.Enumerations.BindingStrength; 038import org.hl7.fhir.dstu2016may.model.ExpressionNode; 039import org.hl7.fhir.dstu2016may.model.Extension; 040import org.hl7.fhir.dstu2016may.model.HumanName; 041import org.hl7.fhir.dstu2016may.model.Identifier; 042import org.hl7.fhir.dstu2016may.model.IntegerType; 043import org.hl7.fhir.dstu2016may.model.Period; 044import org.hl7.fhir.dstu2016may.model.Quantity; 045import org.hl7.fhir.dstu2016may.model.Questionnaire; 046import org.hl7.fhir.dstu2016may.model.Questionnaire.QuestionnaireItemComponent; 047import org.hl7.fhir.dstu2016may.model.Questionnaire.QuestionnaireItemOptionComponent; 048import org.hl7.fhir.dstu2016may.model.Questionnaire.QuestionnaireItemType; 049import org.hl7.fhir.dstu2016may.model.Range; 050import org.hl7.fhir.dstu2016may.model.Ratio; 051import org.hl7.fhir.dstu2016may.model.Reference; 052import org.hl7.fhir.dstu2016may.model.Resource; 053import org.hl7.fhir.dstu2016may.model.SampledData; 054import org.hl7.fhir.dstu2016may.model.StringType; 055import org.hl7.fhir.dstu2016may.model.StructureDefinition; 056import org.hl7.fhir.dstu2016may.model.StructureDefinition.ExtensionContext; 057import org.hl7.fhir.dstu2016may.model.StructureDefinition.StructureDefinitionKind; 058import org.hl7.fhir.dstu2016may.model.StructureDefinition.StructureDefinitionSnapshotComponent; 059import org.hl7.fhir.dstu2016may.model.StructureDefinition.TypeDerivationRule; 060import org.hl7.fhir.dstu2016may.model.TimeType; 061import org.hl7.fhir.dstu2016may.model.Timing; 062import org.hl7.fhir.dstu2016may.model.Type; 063import org.hl7.fhir.dstu2016may.model.UriType; 064import org.hl7.fhir.dstu2016may.model.ValueSet; 065import org.hl7.fhir.dstu2016may.model.ValueSet.ValueSetExpansionContainsComponent; 066import org.hl7.fhir.dstu2016may.utils.FHIRPathEngine; 067import org.hl7.fhir.dstu2016may.utils.IWorkerContext; 068import org.hl7.fhir.dstu2016may.utils.IWorkerContext.ValidationResult; 069import org.hl7.fhir.dstu2016may.utils.ProfileUtilities; 070import org.hl7.fhir.exceptions.DefinitionException; 071import org.hl7.fhir.exceptions.FHIRException; 072import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 073import org.hl7.fhir.utilities.Utilities; 074import org.hl7.fhir.utilities.xhtml.NodeType; 075import org.hl7.fhir.utilities.xhtml.XhtmlNode; 076import org.w3c.dom.Document; 077import org.w3c.dom.Node; 078 079import com.google.gson.JsonObject; 080 081import ca.uhn.fhir.util.ObjectUtil; 082import org.hl7.fhir.utilities.validation.ValidationMessage; 083import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 084import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 085import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 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 boolean anyExtensionsAllowed; 096 097 private BestPracticeWarningLevel bpWarnings; 098 // configuration items 099 private CheckDisplayOption checkDisplay; 100 private IWorkerContext context; 101 private FHIRPathEngine fpe; 102 103 private List<String> extensionDomains = new ArrayList<String>(); 104 105 private IdStatus resourceIdRule; 106 107 // used during the build process to keep the overall volume of messages down 108 private boolean suppressLoincSnomedMessages; 109 110 private Bundle logical; 111 112 // time tracking 113 private long overall = 0; 114 private long txTime = 0; 115 private long sdTime = 0; 116 private long loadTime = 0; 117 private long fpeTime = 0; 118 119 120 public InstanceValidator(IWorkerContext theContext) { 121 super(); 122 this.context = theContext; 123 fpe = new FHIRPathEngine(context); 124 source = Source.InstanceValidator; 125 } 126 127 private boolean allowUnknownExtension(String url) { 128 if (url.contains("example.org") || url.contains("acme.com") || url.contains("nema.org")) 129 return true; 130 for (String s : extensionDomains) 131 if (url.startsWith(s)) 132 return true; 133 return anyExtensionsAllowed; 134 } 135 136 private void bpCheck(List<ValidationMessage> errors, IssueType invalid, int line, int col, String literalPath, boolean test, String message) { 137 if (bpWarnings != null) { 138 switch (bpWarnings) { 139 case Error: 140 rule(errors, invalid, line, col, literalPath, test, message); 141 break; 142 case Warning: 143 warning(errors, invalid, line, col, literalPath, test, message); 144 break; 145 case Hint: 146 hint(errors, invalid, line, col, literalPath, test, message); 147 break; 148 default: // do nothing 149 } 150 } 151 } 152 153 154 @Override 155 public void validate(List<ValidationMessage> errors, InputStream stream, FhirFormat format) throws Exception { 156 validate(errors, stream, format, (StructureDefinition) null); 157 } 158 159 @Override 160 public void validate(List<ValidationMessage> errors, InputStream stream, FhirFormat format, String profile) throws Exception { 161 long t = System.nanoTime(); 162 StructureDefinition p = context.fetchResource(StructureDefinition.class, profile); 163 sdTime = sdTime + (System.nanoTime() - t); 164 if (p == null) 165 throw new DefinitionException("StructureDefinition '" + profile + "' not found"); 166 validate(errors, stream, format, p); 167 } 168 169 @Override 170 public void validate(List<ValidationMessage> errors, InputStream stream, FhirFormat format, StructureDefinition profile) throws Exception { 171 ParserBase parser = Manager.makeParser(context, format); 172 parser.setupValidation(ValidationPolicy.EVERYTHING, errors); 173 long t = System.nanoTime(); 174 Element e = parser.parse(stream); 175 loadTime = System.nanoTime() - t; 176 if (e != null) 177 validate(errors, e, profile); 178 } 179 180 @Override 181 public void validate(List<ValidationMessage> errors, Resource resource) throws Exception { 182 validate(errors, resource, (StructureDefinition) null); 183 } 184 185 @Override 186 public void validate(List<ValidationMessage> errors, Resource resource, String profile) throws Exception { 187 long t = System.nanoTime(); 188 StructureDefinition p = context.fetchResource(StructureDefinition.class, profile); 189 sdTime = sdTime + (System.nanoTime() - t); 190 if (p == null) 191 throw new DefinitionException("StructureDefinition '" + profile + "' not found"); 192 validate(errors, resource, p); 193 } 194 195 @Override 196 public void validate(List<ValidationMessage> errors, Resource resource, StructureDefinition profile) throws Exception { 197 throw new Exception("Not done yet"); 198 // ParserBase parser = new ObjectParser(context); 199 // parser.setupValidation(ValidationPolicy.EVERYTHING, errors); 200 // long t = System.nanoTime(); 201 // Element e = parser.parse(resource); 202 // loadTime = System.nanoTime() - t; 203 // validate(errors, e, profile); 204 } 205 206 @Override 207 public void validate(List<ValidationMessage> errors, Element element) throws Exception { 208 validate(errors, element, (StructureDefinition) null); 209 } 210 211 @Override 212 public void validate(List<ValidationMessage> errors, org.w3c.dom.Element element) throws Exception { 213 validate(errors, element, (StructureDefinition) null); 214 } 215 216 @Override 217 public void validate(List<ValidationMessage> errors, Element element, String profile) throws Exception { 218 long t = System.nanoTime(); 219 StructureDefinition p = context.fetchResource(StructureDefinition.class, profile); 220 sdTime = sdTime + (System.nanoTime() - t); 221 if (p == null) 222 throw new DefinitionException("StructureDefinition '" + profile + "' not found"); 223 validate(errors, element, p); 224 } 225 226 @Override 227 public void validate(List<ValidationMessage> errors, org.w3c.dom.Element element, String profile) throws Exception { 228 long t = System.nanoTime(); 229 StructureDefinition p = context.fetchResource(StructureDefinition.class, profile); 230 sdTime = sdTime + (System.nanoTime() - t); 231 if (p == null) 232 throw new DefinitionException("StructureDefinition '" + profile + "' not found"); 233 validate(errors, element, p); 234 } 235 236 @Override 237 public void validate(List<ValidationMessage> errors, org.w3c.dom.Element element, StructureDefinition profile) throws Exception { 238 XmlParser parser = new XmlParser(context); 239 parser.setupValidation(ValidationPolicy.EVERYTHING, errors); 240 long t = System.nanoTime(); 241 Element e = parser.parse(element); 242 loadTime = System.nanoTime() - t; 243 validate(errors, e, profile); 244 } 245 246 @Override 247 public void validate(List<ValidationMessage> errors, Document document) throws Exception { 248 validate(errors, document, (StructureDefinition) null); 249 } 250 251 @Override 252 public void validate(List<ValidationMessage> errors, Document document, String profile) throws Exception { 253 long t = System.nanoTime(); 254 StructureDefinition p = context.fetchResource(StructureDefinition.class, profile); 255 sdTime = sdTime + (System.nanoTime() - t); 256 if (p == null) 257 throw new DefinitionException("StructureDefinition '" + profile + "' not found"); 258 validate(errors, document, p); 259 } 260 261 @Override 262 public void validate(List<ValidationMessage> errors, Document document, StructureDefinition profile) throws Exception { 263 XmlParser parser = new XmlParser(context); 264 parser.setupValidation(ValidationPolicy.EVERYTHING, errors); 265 long t = System.nanoTime(); 266 Element e = parser.parse(document); 267 loadTime = System.nanoTime() - t; 268 validate(errors, e, profile); 269 } 270 271 @Override 272 public void validate(List<ValidationMessage> errors, JsonObject object) throws Exception { 273 // validate(errors, object, (StructureDefinition) null); 274 } 275 276 @Override 277 public void validate(List<ValidationMessage> errors, JsonObject object, String profile) throws Exception { 278 long t = System.nanoTime(); 279 StructureDefinition p = context.fetchResource(StructureDefinition.class, profile); 280 sdTime = sdTime + (System.nanoTime() - t); 281 if (p == null) 282 throw new DefinitionException("StructureDefinition '" + profile + "' not found"); 283 validate(errors, object, p); 284 } 285 286 @Override 287 public void validate(List<ValidationMessage> errors, JsonObject object, StructureDefinition profile) throws Exception { 288 JsonParser parser = new JsonParser(context); 289 parser.setupValidation(ValidationPolicy.EVERYTHING, errors); 290 long t = System.nanoTime(); 291 Element e = parser.parse(object); 292 loadTime = System.nanoTime() - t; 293 validate(errors, e, profile); 294 } 295 296 @Override 297 public void validate(List<ValidationMessage> errors, Element element, StructureDefinition profile) throws Exception { 298 // this is the main entry point; all the other entry points end up here coming here... 299 long t = System.nanoTime(); 300 validateResource(errors, element, element, profile, resourceIdRule, new NodeStack(element)); 301 overall = System.nanoTime() - t; 302 } 303 304 305 private boolean check(String v1, String v2) { 306 return v1 == null ? Utilities.noString(v1) : v1.equals(v2); 307 } 308 309 private void checkAddress(List<ValidationMessage> errors, String path, Element focus, Address fixed) { 310 checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), "use"); 311 checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), "text"); 312 checkFixedValue(errors, path + ".city", focus.getNamedChild("city"), fixed.getCityElement(), "city"); 313 checkFixedValue(errors, path + ".state", focus.getNamedChild("state"), fixed.getStateElement(), "state"); 314 checkFixedValue(errors, path + ".country", focus.getNamedChild("country"), fixed.getCountryElement(), "country"); 315 checkFixedValue(errors, path + ".zip", focus.getNamedChild("zip"), fixed.getPostalCodeElement(), "postalCode"); 316 317 List<Element> lines = new ArrayList<Element>(); 318 focus.getNamedChildren("line", lines); 319 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, lines.size() == fixed.getLine().size(), 320 "Expected " + Integer.toString(fixed.getLine().size()) + " but found " + Integer.toString(lines.size()) + " line elements")) { 321 for (int i = 0; i < lines.size(); i++) 322 checkFixedValue(errors, path + ".coding", lines.get(i), fixed.getLine().get(i), "coding"); 323 } 324 } 325 326 private void checkAttachment(List<ValidationMessage> errors, String path, Element focus, Attachment fixed) { 327 checkFixedValue(errors, path + ".contentType", focus.getNamedChild("contentType"), fixed.getContentTypeElement(), "contentType"); 328 checkFixedValue(errors, path + ".language", focus.getNamedChild("language"), fixed.getLanguageElement(), "language"); 329 checkFixedValue(errors, path + ".data", focus.getNamedChild("data"), fixed.getDataElement(), "data"); 330 checkFixedValue(errors, path + ".url", focus.getNamedChild("url"), fixed.getUrlElement(), "url"); 331 checkFixedValue(errors, path + ".size", focus.getNamedChild("size"), fixed.getSizeElement(), "size"); 332 checkFixedValue(errors, path + ".hash", focus.getNamedChild("hash"), fixed.getHashElement(), "hash"); 333 checkFixedValue(errors, path + ".title", focus.getNamedChild("title"), fixed.getTitleElement(), "title"); 334 } 335 336 // public API 337 338 private boolean checkCode(List<ValidationMessage> errors, Element element, String path, String code, String system, String display) { 339 long t = System.nanoTime(); 340 boolean ss = context.supportsSystem(system); 341 txTime = txTime + (System.nanoTime() - t); 342 if (ss) { 343 t = System.nanoTime(); 344 ValidationResult s = context.validateCode(system, code, display); 345 txTime = txTime + (System.nanoTime() - t); 346 if (s == null || s.isOk()) 347 return true; 348 if (s.getSeverity() == IssueSeverity.INFORMATION) 349 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage()); 350 else if (s.getSeverity() == IssueSeverity.WARNING) 351 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage()); 352 else 353 return rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage()); 354 return true; 355 } else if (system.startsWith("http://hl7.org/fhir")) { 356 if (system.equals("http://hl7.org/fhir/sid/icd-10")) 357 return true; // else don't check ICD-10 (for now) 358 else { 359 CodeSystem cs = getCodeSystem(system); 360 if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, cs != null, "Unknown Code System " + system)) { 361 ConceptDefinitionComponent def = getCodeDefinition(cs, code); 362 if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, def != null, "Unknown Code (" + system + "#" + code + ")")) 363 return warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, display == null || display.equals(def.getDisplay()), "Display should be '" + def.getDisplay() + "'"); 364 } 365 return false; 366 } 367 } else if (system.startsWith("http://loinc.org")) { 368 return true; 369 } else if (system.startsWith("http://unitsofmeasure.org")) { 370 return true; 371 } else 372 return true; 373 } 374 375 private void checkCodeableConcept(List<ValidationMessage> errors, String path, Element focus, CodeableConcept fixed) { 376 checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), "text"); 377 List<Element> codings = new ArrayList<Element>(); 378 focus.getNamedChildren("coding", codings); 379 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, codings.size() == fixed.getCoding().size(), 380 "Expected " + Integer.toString(fixed.getCoding().size()) + " but found " + Integer.toString(codings.size()) + " coding elements")) { 381 for (int i = 0; i < codings.size(); i++) 382 checkFixedValue(errors, path + ".coding", codings.get(i), fixed.getCoding().get(i), "coding"); 383 } 384 } 385 386 private void checkCodeableConcept(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext) { 387 if (theElementCntext != null && theElementCntext.hasBinding()) { 388 ElementDefinitionBindingComponent binding = theElementCntext.getBinding(); 389 if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, "Binding for " + path + " missing (cc)")) { 390 if (binding.hasValueSet() && binding.getValueSet() instanceof Reference) { 391 ValueSet valueset = resolveBindingReference(profile, binding.getValueSet()); 392 if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(binding.getValueSet()) + " not found")) { 393 try { 394 CodeableConcept cc = readAsCodeableConcept(element); 395 if (!cc.hasCoding()) { 396 if (binding.getStrength() == BindingStrength.REQUIRED) 397 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()); 398 else if (binding.getStrength() == BindingStrength.EXTENSIBLE) 399 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()); 400 } else { 401 long t = System.nanoTime(); 402 ValidationResult vr = context.validateCode(cc, valueset); 403 txTime = txTime + (System.nanoTime() - t); 404 if (!vr.isOk()) { 405 if (binding.getStrength() == BindingStrength.REQUIRED) 406 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"); 407 else if (binding.getStrength() == BindingStrength.EXTENSIBLE) 408 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"); 409 else if (binding.getStrength() == BindingStrength.PREFERRED) 410 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"); 411 } 412 } 413 } catch (Exception e) { 414 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error "+e.getMessage()+" validating CodeableConcept"); 415 } 416 } 417 } else if (binding.hasValueSet()) { 418 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding by URI reference cannot be checked"); 419 } else { 420 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding for path " + path + " has no source, so can't be checked"); 421 } 422 } 423 } 424 } 425 426 private CodeableConcept readAsCodeableConcept(Element element) { 427 CodeableConcept cc = new CodeableConcept(); 428 List<Element> list = new ArrayList<Element>(); 429 element.getNamedChildren("coding", list); 430 for (Element item : list) 431 cc.addCoding(readAsCoding(item)); 432 cc.setText(element.getNamedChildValue("text")); 433 return cc; 434 } 435 436 private Coding readAsCoding(Element item) { 437 Coding c = new Coding(); 438 c.setSystem(item.getNamedChildValue("system")); 439 c.setVersion(item.getNamedChildValue("version")); 440 c.setCode(item.getNamedChildValue("code")); 441 c.setDisplay(item.getNamedChildValue("display")); 442 return c; 443 } 444 445 private void checkCoding(List<ValidationMessage> errors, String path, Element focus, Coding fixed) { 446 checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system"); 447 checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), "code"); 448 checkFixedValue(errors, path + ".display", focus.getNamedChild("display"), fixed.getDisplayElement(), "display"); 449 checkFixedValue(errors, path + ".userSelected", focus.getNamedChild("userSelected"), fixed.getUserSelectedElement(), "userSelected"); 450 } 451 452 private void checkCoding(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept) { 453 String code = element.getNamedChildValue("code"); 454 String system = element.getNamedChildValue("system"); 455 String display = element.getNamedChildValue("display"); 456 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isAbsolute(system), "Coding.system must be an absolute reference, not a local reference"); 457 458 if (system != null && code != null) { 459 if (checkCode(errors, element, path, code, system, display)) 460 if (theElementCntext != null && theElementCntext.getBinding() != null) { 461 ElementDefinitionBindingComponent binding = theElementCntext.getBinding(); 462 if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, "Binding for " + path + " missing")) { 463 if (binding.hasValueSet() && binding.getValueSet() instanceof Reference) { 464 ValueSet valueset = resolveBindingReference(profile, binding.getValueSet()); 465 if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(binding.getValueSet()) + " not found")) { 466 try { 467 Coding c = readAsCoding(element); 468 long t = System.nanoTime(); 469 ValidationResult vr = context.validateCode(c, valueset); 470 txTime = txTime + (System.nanoTime() - t); 471 if (!vr.isOk()) { 472 if (binding.getStrength() == BindingStrength.REQUIRED) 473 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided is not in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code is required from this value set"); 474 else if (binding.getStrength() == BindingStrength.EXTENSIBLE) 475 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value 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"); 476 else if (binding.getStrength() == BindingStrength.PREFERRED) 477 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided is not in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code is recommended to come from this value set"); 478 } 479 } catch (Exception e) { 480 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error "+e.getMessage()+" validating CodeableConcept"); 481 } 482 } 483 } else if (binding.hasValueSet()) { 484 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding by URI reference cannot be checked"); 485 } else if (!inCodeableConcept) { 486 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding for path " + path + " has no source, so can't be checked"); 487 } 488 } 489 } 490 } 491 } 492 493 private void checkContactPoint(List<ValidationMessage> errors, String path, Element focus, ContactPoint fixed) { 494 checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system"); 495 checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), "value"); 496 checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), "use"); 497 checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), "period"); 498 499 } 500 501 private void checkDeclaredProfiles(List<ValidationMessage> errors, Element resource, Element element, NodeStack stack) throws FHIRException { 502 Element meta = element.getNamedChild("meta"); 503 if (meta != null) { 504 List<Element> profiles = new ArrayList<Element>(); 505 meta.getNamedChildren("profile", profiles); 506 int i = 0; 507 for (Element profile : profiles) { 508 String ref = profile.primitiveValue(); 509 String p = stack.addToLiteralPath("meta", "profile", ":" + Integer.toString(i)); 510 if (rule(errors, IssueType.INVALID, element.line(), element.col(), p, !Utilities.noString(ref), "StructureDefinition reference invalid")) { 511 long t = System.nanoTime(); 512 StructureDefinition pr = context.fetchResource(StructureDefinition.class, ref); 513 sdTime = sdTime + (System.nanoTime() - t); 514 if (warning(errors, IssueType.INVALID, element.line(), element.col(), p, pr != null, "StructureDefinition reference \"{0}\" could not be resolved", ref)) { 515 if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), p, pr.hasSnapshot(), 516 "StructureDefinition has no snapshot - validation is against the snapshot, so it must be provided")) { 517 validateElement(errors, pr, pr.getSnapshot().getElement().get(0), null, null, resource, element, element.getName(), stack, false); 518 } 519 } 520 i++; 521 } 522 } 523 } 524 } 525 526 private StructureDefinition checkExtension(List<ValidationMessage> errors, String path, Element element, ElementDefinition def, StructureDefinition profile, NodeStack stack) { 527 String url = element.getNamedChildValue("url"); 528 boolean isModifier = element.getName().equals("modifierExtension"); 529 530 long t = System.nanoTime(); 531 StructureDefinition ex = context.fetchResource(StructureDefinition.class, url); 532 sdTime = sdTime + (System.nanoTime() - t); 533 if (ex == null) { 534 if (!rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, allowUnknownExtension(url), "The extension " + url + " is unknown, and not allowed here")) 535 warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, allowUnknownExtension(url), "Unknown extension " + url); 536 } else { 537 if (def.getIsModifier()) 538 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", ex.getSnapshot().getElement().get(0).getIsModifier(), 539 "Extension modifier mismatch: the extension element is labelled as a modifier, but the underlying extension is not"); 540 else 541 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", !ex.getSnapshot().getElement().get(0).getIsModifier(), 542 "Extension modifier mismatch: the extension element is not labelled as a modifier, but the underlying extension is"); 543 544 // two questions 545 // 1. can this extension be used here? 546 checkExtensionContext(errors, element, /* path+"[url='"+url+"']", */ ex, stack, ex.getUrl()); 547 548 if (isModifier) 549 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", ex.getSnapshot().getElement().get(0).getIsModifier(), 550 "The Extension '" + url + "' must be used as a modifierExtension"); 551 else 552 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", !ex.getSnapshot().getElement().get(0).getIsModifier(), 553 "The Extension '" + url + "' must not be used as an extension (it's a modifierExtension)"); 554 555 // 2. is the content of the extension valid? 556 557 } 558 return ex; 559 } 560 561 private boolean checkExtensionContext(List<ValidationMessage> errors, Element element, StructureDefinition definition, NodeStack stack, String extensionParent) { 562 String extUrl = definition.getUrl(); 563 CommaSeparatedStringBuilder p = new CommaSeparatedStringBuilder(); 564 for (String lp : stack.getLogicalPaths()) 565 p.append(lp); 566 if (definition.getContextType() == ExtensionContext.DATATYPE) { 567 boolean ok = false; 568 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 569 for (StringType ct : definition.getContext()) { 570 b.append(ct.getValue()); 571 if (ct.getValue().equals("*") || stack.getLogicalPaths().contains(ct.getValue() + ".extension")) 572 ok = true; 573 } 574 return rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), ok, 575 "The extension " + extUrl + " is not allowed to be used on the logical path set [" + p.toString() + "] (allowed: datatype=" + b.toString() + ")"); 576 } else if (definition.getContextType() == ExtensionContext.EXTENSION) { 577 boolean ok = false; 578 for (StringType ct : definition.getContext()) 579 if (ct.getValue().equals("*") || ct.getValue().equals(extensionParent)) 580 ok = true; 581 return rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), ok, 582 "The extension " + extUrl + " is not allowed to be used with the extension '" + extensionParent + "'"); 583 } else if (definition.getContextType() == ExtensionContext.RESOURCE) { 584 boolean ok = false; 585 // String simplePath = container.getPath(); 586 // System.out.println(simplePath); 587 // if (effetive.endsWith(".extension") || simplePath.endsWith(".modifierExtension")) 588 // simplePath = simplePath.substring(0, simplePath.lastIndexOf('.')); 589 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 590 for (StringType ct : definition.getContext()) { 591 String c = ct.getValue(); 592 b.append(c); 593 if (c.equals("*") || stack.getLogicalPaths().contains(c + ".extension") || (c.startsWith("@") && stack.getLogicalPaths().contains(c.substring(1) + ".extension"))) 594 ; 595 ok = true; 596 } 597 return rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), ok, 598 "The extension " + extUrl + " is not allowed to be used on the logical path set " + p.toString() + " (allowed: resource=" + b.toString() + ")"); 599 } else 600 throw new Error("Unknown context type"); 601 } 602 // 603 // private String simplifyPath(String path) { 604 // String s = path.replace("/f:", "."); 605 // while (s.contains("[")) 606 // s = s.substring(0, s.indexOf("["))+s.substring(s.indexOf("]")+1); 607 // String[] parts = s.split("\\."); 608 // int i = 0; 609 // while (i < parts.length && !context.getProfiles().containsKey(parts[i].toLowerCase())) 610 // i++; 611 // if (i >= parts.length) 612 // throw new Error("Unable to process part "+path); 613 // int j = parts.length - 1; 614 // while (j > 0 && (parts[j].equals("extension") || parts[j].equals("modifierExtension"))) 615 // j--; 616 // StringBuilder b = new StringBuilder(); 617 // boolean first = true; 618 // for (int k = i; k <= j; k++) { 619 // if (k == j || !parts[k].equals(parts[k+1])) { 620 // if (first) 621 // first = false; 622 // else 623 // b.append("."); 624 // b.append(parts[k]); 625 // } 626 // } 627 // return b.toString(); 628 // } 629 // 630 631 private void checkFixedValue(List<ValidationMessage> errors, String path, Element focus, org.hl7.fhir.dstu2016may.model.Element fixed, String propName) { 632 if (fixed == null && focus == null) 633 ; // this is all good 634 else if (fixed == null && focus != null) 635 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, false, "Unexpected element " + focus.getName()); 636 else if (fixed != null && focus == null) 637 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, false, "Mising element " + propName); 638 else { 639 String value = focus.primitiveValue(); 640 if (fixed instanceof org.hl7.fhir.dstu2016may.model.BooleanType) 641 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.BooleanType) fixed).asStringValue(), value), 642 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.BooleanType) fixed).asStringValue() + "'"); 643 else if (fixed instanceof org.hl7.fhir.dstu2016may.model.IntegerType) 644 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.IntegerType) fixed).asStringValue(), value), 645 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.IntegerType) fixed).asStringValue() + "'"); 646 else if (fixed instanceof org.hl7.fhir.dstu2016may.model.DecimalType) 647 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.DecimalType) fixed).asStringValue(), value), 648 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.DecimalType) fixed).asStringValue() + "'"); 649 else if (fixed instanceof org.hl7.fhir.dstu2016may.model.Base64BinaryType) 650 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.Base64BinaryType) fixed).asStringValue(), value), 651 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.Base64BinaryType) fixed).asStringValue() + "'"); 652 else if (fixed instanceof org.hl7.fhir.dstu2016may.model.InstantType) 653 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.InstantType) fixed).getValue().toString(), value), 654 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.InstantType) fixed).asStringValue() + "'"); 655 else if (fixed instanceof org.hl7.fhir.dstu2016may.model.StringType) 656 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.StringType) fixed).getValue(), value), 657 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.StringType) fixed).getValue() + "'"); 658 else if (fixed instanceof org.hl7.fhir.dstu2016may.model.UriType) 659 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.UriType) fixed).getValue(), value), 660 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.UriType) fixed).getValue() + "'"); 661 else if (fixed instanceof org.hl7.fhir.dstu2016may.model.DateType) 662 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.DateType) fixed).getValue().toString(), value), 663 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.DateType) fixed).getValue() + "'"); 664 else if (fixed instanceof org.hl7.fhir.dstu2016may.model.DateTimeType) 665 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.DateTimeType) fixed).getValue().toString(), value), 666 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.DateTimeType) fixed).getValue() + "'"); 667 else if (fixed instanceof org.hl7.fhir.dstu2016may.model.OidType) 668 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.OidType) fixed).getValue(), value), 669 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.OidType) fixed).getValue() + "'"); 670 else if (fixed instanceof org.hl7.fhir.dstu2016may.model.UuidType) 671 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.UuidType) fixed).getValue(), value), 672 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.UuidType) fixed).getValue() + "'"); 673 else if (fixed instanceof org.hl7.fhir.dstu2016may.model.CodeType) 674 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.CodeType) fixed).getValue(), value), 675 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.CodeType) fixed).getValue() + "'"); 676 else if (fixed instanceof org.hl7.fhir.dstu2016may.model.IdType) 677 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu2016may.model.IdType) fixed).getValue(), value), 678 "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu2016may.model.IdType) fixed).getValue() + "'"); 679 else if (fixed instanceof Quantity) 680 checkQuantity(errors, path, focus, (Quantity) fixed); 681 else if (fixed instanceof Address) 682 checkAddress(errors, path, focus, (Address) fixed); 683 else if (fixed instanceof ContactPoint) 684 checkContactPoint(errors, path, focus, (ContactPoint) fixed); 685 else if (fixed instanceof Attachment) 686 checkAttachment(errors, path, focus, (Attachment) fixed); 687 else if (fixed instanceof Identifier) 688 checkIdentifier(errors, path, focus, (Identifier) fixed); 689 else if (fixed instanceof Coding) 690 checkCoding(errors, path, focus, (Coding) fixed); 691 else if (fixed instanceof HumanName) 692 checkHumanName(errors, path, focus, (HumanName) fixed); 693 else if (fixed instanceof CodeableConcept) 694 checkCodeableConcept(errors, path, focus, (CodeableConcept) fixed); 695 else if (fixed instanceof Timing) 696 checkTiming(errors, path, focus, (Timing) fixed); 697 else if (fixed instanceof Period) 698 checkPeriod(errors, path, focus, (Period) fixed); 699 else if (fixed instanceof Range) 700 checkRange(errors, path, focus, (Range) fixed); 701 else if (fixed instanceof Ratio) 702 checkRatio(errors, path, focus, (Ratio) fixed); 703 else if (fixed instanceof SampledData) 704 checkSampledData(errors, path, focus, (SampledData) fixed); 705 706 else 707 rule(errors, IssueType.EXCEPTION, focus.line(), focus.col(), path, false, "Unhandled fixed value type " + fixed.getClass().getName()); 708 List<Element> extensions = new ArrayList<Element>(); 709 focus.getNamedChildren("extension", extensions); 710 if (fixed.getExtension().size() == 0) { 711 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, extensions.size() == 0, "No extensions allowed"); 712 } else if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, extensions.size() == fixed.getExtension().size(), 713 "Extensions count mismatch: expected " + Integer.toString(fixed.getExtension().size()) + " but found " + Integer.toString(extensions.size()))) { 714 for (Extension e : fixed.getExtension()) { 715 Element ex = getExtensionByUrl(extensions, e.getUrl()); 716 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, ex != null, "Extension count mismatch: unable to find extension: " + e.getUrl())) { 717 checkFixedValue(errors, path, ex.getNamedChild("extension").getNamedChild("value"), e.getValue(), "extension.value"); 718 } 719 } 720 } 721 } 722 } 723 724 private void checkHumanName(List<ValidationMessage> errors, String path, Element focus, HumanName fixed) { 725 checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), "use"); 726 checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), "text"); 727 checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), "period"); 728 729 List<Element> parts = new ArrayList<Element>(); 730 focus.getNamedChildren("family", parts); 731 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getFamily().size(), 732 "Expected " + Integer.toString(fixed.getFamily().size()) + " but found " + Integer.toString(parts.size()) + " family elements")) { 733 for (int i = 0; i < parts.size(); i++) 734 checkFixedValue(errors, path + ".family", parts.get(i), fixed.getFamily().get(i), "family"); 735 } 736 focus.getNamedChildren("given", parts); 737 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getGiven().size(), 738 "Expected " + Integer.toString(fixed.getGiven().size()) + " but found " + Integer.toString(parts.size()) + " given elements")) { 739 for (int i = 0; i < parts.size(); i++) 740 checkFixedValue(errors, path + ".given", parts.get(i), fixed.getGiven().get(i), "given"); 741 } 742 focus.getNamedChildren("prefix", parts); 743 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getPrefix().size(), 744 "Expected " + Integer.toString(fixed.getPrefix().size()) + " but found " + Integer.toString(parts.size()) + " prefix elements")) { 745 for (int i = 0; i < parts.size(); i++) 746 checkFixedValue(errors, path + ".prefix", parts.get(i), fixed.getPrefix().get(i), "prefix"); 747 } 748 focus.getNamedChildren("suffix", parts); 749 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getSuffix().size(), 750 "Expected " + Integer.toString(fixed.getSuffix().size()) + " but found " + Integer.toString(parts.size()) + " suffix elements")) { 751 for (int i = 0; i < parts.size(); i++) 752 checkFixedValue(errors, path + ".suffix", parts.get(i), fixed.getSuffix().get(i), "suffix"); 753 } 754 } 755 756 private void checkIdentifier(List<ValidationMessage> errors, String path, Element element, ElementDefinition context) { 757 String system = element.getNamedChildValue("system"); 758 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isAbsolute(system), "Identifier.system must be an absolute reference, not a local reference"); 759 } 760 761 private void checkIdentifier(List<ValidationMessage> errors, String path, Element focus, Identifier fixed) { 762 checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), "use"); 763 checkFixedValue(errors, path + ".type", focus.getNamedChild("type"), fixed.getType(), "type"); 764 checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system"); 765 checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), "value"); 766 checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), "period"); 767 checkFixedValue(errors, path + ".assigner", focus.getNamedChild("assigner"), fixed.getAssigner(), "assigner"); 768 } 769 770 private void checkPeriod(List<ValidationMessage> errors, String path, Element focus, Period fixed) { 771 checkFixedValue(errors, path + ".start", focus.getNamedChild("start"), fixed.getStartElement(), "start"); 772 checkFixedValue(errors, path + ".end", focus.getNamedChild("end"), fixed.getEndElement(), "end"); 773 } 774 775 private void checkPrimitive(List<ValidationMessage> errors, String path, String type, ElementDefinition context, Element e, StructureDefinition profile) { 776 if (type.equals("boolean")) { 777 rule(errors, IssueType.INVALID, e.line(), e.col(), path, "true".equals(e.primitiveValue()) || "false".equals(e.primitiveValue()), "boolean values must be 'true' or 'false'"); 778 } 779 if (type.equals("uri")) { 780 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !e.primitiveValue().startsWith("oid:"), "URI values cannot start with oid:"); 781 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !e.primitiveValue().startsWith("uuid:"), "URI values cannot start with uuid:"); 782 rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().equals(e.primitiveValue().trim()), "URI values cannot have leading or trailing whitespace"); 783 } 784 if (!type.equalsIgnoreCase("string") && e.hasPrimitiveValue()) { 785 if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || e.primitiveValue().length() > 0, "@value cannot be empty")) { 786 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"); 787 } 788 } 789 if (type.equals("dateTime")) { 790 rule(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), "The value '" + e.primitiveValue() + "' does not have a valid year"); 791 rule(errors, IssueType.INVALID, e.line(), e.col(), path, 792 e.primitiveValue() 793 .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))?)?)?)?"), 794 "Not a valid date time"); 795 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"); 796 797 } 798 if (type.equals("instant")) { 799 rule(errors, IssueType.INVALID, e.line(), e.col(), path, 800 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))"), 801 "The instant '" + e.primitiveValue() + "' is not valid (by regex)"); 802 rule(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), "The value '" + e.primitiveValue() + "' does not have a valid year"); 803 } 804 805 if (type.equals("code") && e.primitiveValue() != null) { 806 // 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 807 // other than single spaces in the contents 808 rule(errors, IssueType.INVALID, e.line(), e.col(), path, passesCodeWhitespaceRules(e.primitiveValue()), "The code '" + e.primitiveValue() + "' is not valid (whitespace rules)"); 809 } 810 811 if (context.hasBinding() && e.primitiveValue() != null) { 812 checkPrimitiveBinding(errors, path, type, context, e, profile); 813 } 814 815 if (type.equals("xhtml")) { 816 XhtmlNode xhtml = e.getXhtml(); 817 if (xhtml != null) { // if it is null, this is an error already noted in the parsers 818 // check that the namespace is there and correct. 819 String ns = xhtml.getNsDecl(); 820 rule(errors, IssueType.INVALID, e.line(), e.col(), path, FormatUtilities.XHTML_NS.equals(ns), "Wrong namespace on the XHTML ('"+ns+"')"); 821 // check that inner namespaces are all correct 822 checkInnerNS(errors, e, path, xhtml.getChildNodes()); 823 rule(errors, IssueType.INVALID, e.line(), e.col(), path, "div".equals(xhtml.getName()), "Wrong name on the XHTML ('"+ns+"') - must start with div"); 824 // check that no illegal elements and attributes have been used 825 checkInnerNames(errors, e, path, xhtml.getChildNodes()); 826 } 827 } 828 // for nothing to check 829 } 830 831 private void checkInnerNames(List<ValidationMessage> errors, Element e, String path, List<XhtmlNode> list) { 832 for (XhtmlNode node : list) { 833 if (node.getNodeType() == NodeType.Element) { 834 rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.existsInList(node.getName(), 835 "p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", "b", "em", "i", "strong", 836 "small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup", 837 "ul", "ol", "li", "dl", "dt", "dd", "pre", "table", "caption", "colgroup", "col", "thead", "tr", "tfoot", "tbody", "th", "td", 838 "code", "samp", "img", "map", "area" 839 840 ), "Illegal element name in the XHTML ('"+node.getName()+"')"); 841 for (String an : node.getAttributes().keySet()) { 842 boolean ok = an.startsWith("xmlns") || Utilities.existsInList(an, 843 "title", "style", "class", "id", "lang", "xml:lang", "dir", "accesskey", "tabindex", 844 // tables 845 "span", "width", "align", "valign", "char", "charoff", "abbr", "axis", "headers", "scope", "rowspan", "colspan") || 846 847 Utilities.existsInList(node.getName()+"."+an, "a.href", "a.name", "img.src", "img.border", "div.xmlns", "blockquote.cite", "q.cite", 848 "a.charset", "a.type", "a.name", "a.href", "a.hreflang", "a.rel", "a.rev", "a.shape", "a.coords", "img.src", 849 "img.alt", "img.longdesc", "img.height", "img.width", "img.usemap", "img.ismap", "map.name", "area.shape", 850 "area.coords", "area.href", "area.nohref", "area.alt", "table.summary", "table.width", "table.border", 851 "table.frame", "table.rules", "table.cellspacing", "table.cellpadding", "pre.space" 852 ); 853 if (!ok) 854 rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, "Illegal attribute name in the XHTML ('"+an+"' on '"+node.getName()+"')"); 855 } 856 checkInnerNames(errors, e, path, node.getChildNodes()); 857 } 858 } 859 } 860 861 private void checkInnerNS(List<ValidationMessage> errors, Element e, String path, List<XhtmlNode> list) { 862 for (XhtmlNode node : list) { 863 if (node.getNodeType() == NodeType.Element) { 864 String ns = node.getNsDecl(); 865 rule(errors, IssueType.INVALID, e.line(), e.col(), path, ns == null || FormatUtilities.XHTML_NS.equals(ns), "Wrong namespace on the XHTML ('"+ns+"')"); 866 checkInnerNS(errors, e, path, node.getChildNodes()); 867 } 868 } 869 } 870 871 // note that we don't check the type here; it could be string, uri or code. 872 private void checkPrimitiveBinding(List<ValidationMessage> errors, String path, String type, ElementDefinition elementContext, Element element, StructureDefinition profile) { 873 if (!element.hasPrimitiveValue()) 874 return; 875 876 String value = element.primitiveValue(); 877 // System.out.println("check "+value+" in "+path); 878 879 // firstly, resolve the value set 880 ElementDefinitionBindingComponent binding = elementContext.getBinding(); 881 if (binding.hasValueSet() && binding.getValueSet() instanceof Reference) { 882 ValueSet vs = resolveBindingReference(profile, binding.getValueSet()); 883 if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null, "ValueSet {0} not found", describeReference(binding.getValueSet()))) { 884 long t = System.nanoTime(); 885 ValidationResult vr = context.validateCode(null, value, null, vs); 886 txTime = txTime + (System.nanoTime() - t); 887 if (vr != null && !vr.isOk()) { 888 if (binding.getStrength() == BindingStrength.REQUIRED) 889 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"); 890 else if (binding.getStrength() == BindingStrength.EXTENSIBLE) 891 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"); 892 else if (binding.getStrength() == BindingStrength.PREFERRED) 893 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"); 894 } 895 } 896 } else 897 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, !type.equals("code"), "Binding has no source, so can't be checked"); 898 } 899 900 private void checkQuantity(List<ValidationMessage> errors, String path, Element focus, Quantity fixed) { 901 checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), "value"); 902 checkFixedValue(errors, path + ".comparator", focus.getNamedChild("comparator"), fixed.getComparatorElement(), "comparator"); 903 checkFixedValue(errors, path + ".units", focus.getNamedChild("unit"), fixed.getUnitElement(), "units"); 904 checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system"); 905 checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), "code"); 906 } 907 908 // implementation 909 910 private void checkRange(List<ValidationMessage> errors, String path, Element focus, Range fixed) { 911 checkFixedValue(errors, path + ".low", focus.getNamedChild("low"), fixed.getLow(), "low"); 912 checkFixedValue(errors, path + ".high", focus.getNamedChild("high"), fixed.getHigh(), "high"); 913 914 } 915 916 private void checkRatio(List<ValidationMessage> errors, String path, Element focus, Ratio fixed) { 917 checkFixedValue(errors, path + ".numerator", focus.getNamedChild("numerator"), fixed.getNumerator(), "numerator"); 918 checkFixedValue(errors, path + ".denominator", focus.getNamedChild("denominator"), fixed.getDenominator(), "denominator"); 919 } 920 921 private void checkReference(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, ElementDefinition container, String parentType, NodeStack stack) { 922 String ref = element.getNamedChildValue("reference"); 923 if (Utilities.noString(ref)) { 924 // todo - what should we do in this case? 925 warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, !Utilities.noString(element.getNamedChildValue("display")), "A Reference without an actual reference should have a display"); 926 return; 927 } 928 929 Element we = resolve(ref, stack); 930 String ft; 931 if (we != null) 932 ft = we.getType(); 933 else 934 ft = tryParse(ref); 935 if (hint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ft != null, "Unable to determine type of target resource")) { 936 boolean ok = false; 937 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 938 for (TypeRefComponent type : container.getType()) { 939 if (!ok && type.getCode().equals("Reference")) { 940 // we validate as much as we can. First, can we infer a type from the profile? 941 if (!type.hasProfile() || type.getProfile().get(0).getValue().equals("http://hl7.org/fhir/StructureDefinition/Resource")) 942 ok = true; 943 else { 944 String pr = type.getProfile().get(0).getValue(); 945 946 String bt = getBaseType(profile, pr); 947 if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, bt != null, "Unable to resolve the profile reference '" + pr + "'")) { 948 b.append(bt); 949 ok = bt.equals(ft); 950 } else 951 ok = true; // suppress following check 952 } 953 } 954 if (!ok && type.getCode().equals("*")) { 955 ok = true; // can refer to anything 956 } 957 } 958 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ok, "Invalid Resource target type. Found " + ft + ", but expected one of (" + b.toString() + ")"); 959 } 960 } 961 962 private String checkResourceType(String type) { 963 long t = System.nanoTime(); 964 try { 965 if (context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + type) != null) 966 return type; 967 else 968 return null; 969 } finally { 970 sdTime = sdTime + (System.nanoTime() - t); 971 } 972 } 973 974 private void checkSampledData(List<ValidationMessage> errors, String path, Element focus, SampledData fixed) { 975 checkFixedValue(errors, path + ".origin", focus.getNamedChild("origin"), fixed.getOrigin(), "origin"); 976 checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriodElement(), "period"); 977 checkFixedValue(errors, path + ".factor", focus.getNamedChild("factor"), fixed.getFactorElement(), "factor"); 978 checkFixedValue(errors, path + ".lowerLimit", focus.getNamedChild("lowerLimit"), fixed.getLowerLimitElement(), "lowerLimit"); 979 checkFixedValue(errors, path + ".upperLimit", focus.getNamedChild("upperLimit"), fixed.getUpperLimitElement(), "upperLimit"); 980 checkFixedValue(errors, path + ".dimensions", focus.getNamedChild("dimensions"), fixed.getDimensionsElement(), "dimensions"); 981 checkFixedValue(errors, path + ".data", focus.getNamedChild("data"), fixed.getDataElement(), "data"); 982 } 983 984 private void checkTiming(List<ValidationMessage> errors, String path, Element focus, Timing fixed) { 985 checkFixedValue(errors, path + ".repeat", focus.getNamedChild("repeat"), fixed.getRepeat(), "value"); 986 987 List<Element> events = new ArrayList<Element>(); 988 focus.getNamedChildren("event", events); 989 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, events.size() == fixed.getEvent().size(), 990 "Expected " + Integer.toString(fixed.getEvent().size()) + " but found " + Integer.toString(events.size()) + " event elements")) { 991 for (int i = 0; i < events.size(); i++) 992 checkFixedValue(errors, path + ".event", events.get(i), fixed.getEvent().get(i), "event"); 993 } 994 } 995 996 private boolean codeinExpansion(ValueSetExpansionContainsComponent cnt, String system, String code) { 997 for (ValueSetExpansionContainsComponent c : cnt.getContains()) { 998 if (code.equals(c.getCode()) && system.equals(c.getSystem().toString())) 999 return true; 1000 if (codeinExpansion(c, system, code)) 1001 return true; 1002 } 1003 return false; 1004 } 1005 1006 private boolean codeInExpansion(ValueSet vs, String system, String code) { 1007 for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) { 1008 if (code.equals(c.getCode()) && (system == null || system.equals(c.getSystem()))) 1009 return true; 1010 if (codeinExpansion(c, system, code)) 1011 return true; 1012 } 1013 return false; 1014 } 1015 1016 private String describeReference(Type reference) { 1017 if (reference == null) 1018 return "null"; 1019 if (reference instanceof UriType) 1020 return ((UriType) reference).getValue(); 1021 if (reference instanceof Reference) 1022 return ((Reference) reference).getReference(); 1023 return "??"; 1024 } 1025 1026 private String describeTypes(List<TypeRefComponent> types) { 1027 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1028 for (TypeRefComponent t : types) { 1029 b.append(t.getCode()); 1030 } 1031 return b.toString(); 1032 } 1033 1034 private ElementDefinition findElement(StructureDefinition profile, String name) { 1035 for (ElementDefinition c : profile.getSnapshot().getElement()) { 1036 if (c.getPath().equals(name)) { 1037 return c; 1038 } 1039 } 1040 return null; 1041 } 1042 1043 private String genFullUrl(String bundleBase, String entryBase, String type, String id) { 1044 String base = Utilities.noString(entryBase) ? bundleBase : entryBase; 1045 if (Utilities.noString(base)) { 1046 return type + "/" + id; 1047 } else if ("urn:uuid".equals(base) || "urn:oid".equals(base)) 1048 return base + id; 1049 else 1050 return Utilities.appendSlash(base) + type + "/" + id; 1051 } 1052 1053 public BestPracticeWarningLevel getBasePracticeWarningLevel() { 1054 return bpWarnings; 1055 } 1056 1057 private String getBaseType(StructureDefinition profile, String pr) { 1058 // if (pr.startsWith("http://hl7.org/fhir/StructureDefinition/")) { 1059 // // this just has to be a base type 1060 // return pr.substring(40); 1061 // } else { 1062 StructureDefinition p = resolveProfile(profile, pr); 1063 if (p == null) 1064 return null; 1065 else if (p.getKind() == StructureDefinitionKind.RESOURCE) 1066 return p.getSnapshot().getElement().get(0).getPath(); 1067 else 1068 return p.getSnapshot().getElement().get(0).getType().get(0).getCode(); 1069 // } 1070 } 1071 1072 @Override 1073 public CheckDisplayOption getCheckDisplay() { 1074 return checkDisplay; 1075 } 1076 1077 // private String findProfileTag(Element element) { 1078 // String uri = null; 1079 // List<Element> list = new ArrayList<Element>(); 1080 // element.getNamedChildren("category", list); 1081 // for (Element c : list) { 1082 // if ("http://hl7.org/fhir/tag/profile".equals(c.getAttribute("scheme"))) { 1083 // uri = c.getAttribute("term"); 1084 // } 1085 // } 1086 // return uri; 1087 // } 1088 1089 private ConceptDefinitionComponent getCodeDefinition(ConceptDefinitionComponent c, String code) { 1090 if (code.equals(c.getCode())) 1091 return c; 1092 for (ConceptDefinitionComponent g : c.getConcept()) { 1093 ConceptDefinitionComponent r = getCodeDefinition(g, code); 1094 if (r != null) 1095 return r; 1096 } 1097 return null; 1098 } 1099 1100 private ConceptDefinitionComponent getCodeDefinition(CodeSystem cs, String code) { 1101 for (ConceptDefinitionComponent c : cs.getConcept()) { 1102 ConceptDefinitionComponent r = getCodeDefinition(c, code); 1103 if (r != null) 1104 return r; 1105 } 1106 return null; 1107 } 1108 1109 private Element getContainedById(Element container, String id) { 1110 List<Element> contained = new ArrayList<Element>(); 1111 container.getNamedChildren("contained", contained); 1112 for (Element we : contained) { 1113 if (id.equals(we.getNamedChildValue("id"))) 1114 return we; 1115 } 1116 return null; 1117 } 1118 1119 public IWorkerContext getContext() { 1120 return context; 1121 } 1122 1123 private ElementDefinition getCriteriaForDiscriminator(String path, ElementDefinition ed, String discriminator, StructureDefinition profile) throws DefinitionException, org.hl7.fhir.exceptions.DefinitionException { 1124 List<ElementDefinition> childDefinitions = ProfileUtilities.getChildMap(profile, ed); 1125 List<ElementDefinition> snapshot = null; 1126 int index; 1127 if (childDefinitions.isEmpty()) { 1128 // going to look at the type 1129 if (ed.getType().size() == 0) 1130 throw new DefinitionException("Error in profile for " + path + " no children, no type"); 1131 if (ed.getType().size() > 1) 1132 throw new DefinitionException("Error in profile for " + path + " multiple types defined in slice discriminator"); 1133 StructureDefinition type; 1134 if (ed.getType().get(0).hasProfile()) { 1135 // need to do some special processing for reference here... 1136 if (ed.getType().get(0).getCode().equals("Reference")) 1137 discriminator = discriminator.substring(discriminator.indexOf(".")+1); 1138 long t = System.nanoTime(); 1139 type = context.fetchResource(StructureDefinition.class, ed.getType().get(0).getProfile().get(0).getValue()); 1140 sdTime = sdTime + (System.nanoTime() - t); 1141 } else { 1142 long t = System.nanoTime(); 1143 type = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + ed.getType().get(0).getCode()); 1144 sdTime = sdTime + (System.nanoTime() - t); 1145 } 1146 snapshot = type.getSnapshot().getElement(); 1147 ed = snapshot.get(0); 1148 index = 0; 1149 } else { 1150 snapshot = childDefinitions; 1151 index = -1; 1152 } 1153 String originalPath = ed.getPath(); 1154 String goal = originalPath + "." + discriminator; 1155 1156 index++; 1157 while (index < snapshot.size() && !snapshot.get(index).getPath().equals(originalPath)) { 1158 if (snapshot.get(index).getPath().equals(goal)) 1159 return snapshot.get(index); 1160 index++; 1161 } 1162 throw new Error("Unable to find discriminator definition for " + goal + " in " + discriminator + " at " + path); 1163 } 1164 1165 private Element getExtensionByUrl(List<Element> extensions, String urlSimple) { 1166 for (Element e : extensions) { 1167 if (urlSimple.equals(e.getNamedChildValue("url"))) 1168 return e; 1169 } 1170 return null; 1171 } 1172 1173 public List<String> getExtensionDomains() { 1174 return extensionDomains; 1175 } 1176 1177 private Element getFromBundle(Element bundle, String ref, String fullUrl) { 1178 List<Element> entries = new ArrayList<Element>(); 1179 bundle.getNamedChildren("entry", entries); 1180 for (Element we : entries) { 1181 Element res = we.getNamedChild("resource"); 1182 if (res != null) { 1183 String url = genFullUrl(bundle.getNamedChildValue("base"), we.getNamedChildValue("base"), res.getName(), res.getNamedChildValue("id")); 1184 if (url.endsWith(ref)) 1185 return res; 1186 } 1187 } 1188 return null; 1189 } 1190 1191 private StructureDefinition getProfileForType(String type) { 1192 if (logical != null) 1193 for (BundleEntryComponent be : logical.getEntry()) { 1194 if (be.hasResource() && be.getResource() instanceof StructureDefinition) { 1195 StructureDefinition sd = (StructureDefinition) be.getResource(); 1196 if (sd.getId().equals(type)) 1197 return sd; 1198 } 1199 } 1200 1201 long t = System.nanoTime(); 1202 try { 1203 return context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + type); 1204 } finally { 1205 sdTime = sdTime + (System.nanoTime() - t); 1206 } 1207 } 1208 1209 private Element getValueForDiscriminator(Element element, String discriminator, ElementDefinition criteria) { 1210 // throw new Error("validation of slices not done yet"); 1211 return null; 1212 } 1213 1214 private CodeSystem getCodeSystem(String system) { 1215 long t = System.nanoTime(); 1216 try { 1217 return context.fetchCodeSystem(system); 1218 } finally { 1219 txTime = txTime + (System.nanoTime() - t); 1220 } 1221 } 1222 1223 private boolean hasTime(String fmt) { 1224 return fmt.contains("T"); 1225 } 1226 1227 private boolean hasTimeZone(String fmt) { 1228 return fmt.length() > 10 && (fmt.substring(10).contains("-") || fmt.substring(10).contains("+") || fmt.substring(10).contains("Z")); 1229 } 1230 1231 private boolean isAbsolute(String uri) { 1232 return Utilities.noString(uri) || uri.startsWith("http:") || uri.startsWith("https:") || uri.startsWith("urn:uuid:") || uri.startsWith("urn:oid:") || uri.startsWith("urn:ietf:") 1233 || uri.startsWith("urn:iso:") || isValidFHIRUrn(uri); 1234 } 1235 1236 private boolean isValidFHIRUrn(String uri) { 1237 return (uri.equals("urn:x-fhir:uk:id:nhs-number")); 1238 } 1239 1240 public boolean isAnyExtensionsAllowed() { 1241 return anyExtensionsAllowed; 1242 } 1243 1244 private boolean isParametersEntry(String path) { 1245 String[] parts = path.split("\\."); 1246 return parts.length > 2 && parts[parts.length - 1].equals("resource") && (parts[parts.length - 2].startsWith("parameter[") || parts[parts.length - 2].startsWith("part[")); 1247 } 1248 1249 private boolean isBundleEntry(String path) { 1250 String[] parts = path.split("\\."); 1251 return parts.length > 2 && parts[parts.length - 1].equals("resource") && parts[parts.length - 2].startsWith("entry["); 1252 } 1253 1254 private boolean isPrimitiveType(String type) { 1255 return type.equalsIgnoreCase("boolean") || type.equalsIgnoreCase("integer") || type.equalsIgnoreCase("string") || type.equalsIgnoreCase("decimal") || type.equalsIgnoreCase("uri") 1256 || type.equalsIgnoreCase("base64Binary") || type.equalsIgnoreCase("instant") || type.equalsIgnoreCase("date") || type.equalsIgnoreCase("uuid") || type.equalsIgnoreCase("id") 1257 || type.equalsIgnoreCase("xhtml") || type.equalsIgnoreCase("markdown") || type.equalsIgnoreCase("dateTime") || type.equalsIgnoreCase("time") || type.equalsIgnoreCase("code") 1258 || type.equalsIgnoreCase("oid") || type.equalsIgnoreCase("id"); 1259 } 1260 1261 1262 1263 public boolean isSuppressLoincSnomedMessages() { 1264 return suppressLoincSnomedMessages; 1265 } 1266 1267 private boolean nameMatches(String name, String tail) { 1268 if (tail.endsWith("[x]")) 1269 return name.startsWith(tail.substring(0, tail.length() - 3)); 1270 else 1271 return (name.equals(tail)); 1272 } 1273 1274 // private String mergePath(String path1, String path2) { 1275 // // path1 is xpath path 1276 // // path2 is dotted path 1277 // String[] parts = path2.split("\\."); 1278 // StringBuilder b = new StringBuilder(path1); 1279 // for (int i = 1; i < parts.length -1; i++) 1280 // b.append("/f:"+parts[i]); 1281 // return b.toString(); 1282 // } 1283 1284 private boolean passesCodeWhitespaceRules(String v) { 1285 if (!v.trim().equals(v)) 1286 return false; 1287 boolean lastWasSpace = true; 1288 for (char c : v.toCharArray()) { 1289 if (c == ' ') { 1290 if (lastWasSpace) 1291 return false; 1292 else 1293 lastWasSpace = true; 1294 } else if (Character.isWhitespace(c)) 1295 return false; 1296 else 1297 lastWasSpace = false; 1298 } 1299 return true; 1300 } 1301 1302 private Element resolve(String ref, NodeStack stack) { 1303 if (ref.startsWith("#")) { 1304 // work back through the contained list. 1305 // really, there should only be one level for this (contained resources cannot contain 1306 // contained resources), but we'll leave that to some other code to worry about 1307 while (stack != null && stack.getElement() != null) { 1308 if (stack.getElement().getProperty().isResource()) { 1309 // ok, we'll try to find the contained reference 1310 Element res = getContainedById(stack.getElement(), ref.substring(1)); 1311 if (res != null) 1312 return res; 1313 } 1314 if (stack.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY) { 1315 return null; // we don't try to resolve contained references across this boundary 1316 } 1317 stack = stack.parent; 1318 } 1319 return null; 1320 } else { 1321 // work back through the contained list - if any of them are bundles, try to resolve 1322 // the resource in the bundle 1323 String fullUrl = null; // we're going to try to work this out as we go up 1324 while (stack != null && stack.getElement() != null) { 1325 if (stack.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY) { 1326 fullUrl = "test"; // we don't try to resolve contained references across this boundary 1327 } 1328 if ("Bundle".equals(stack.getElement().getType())) { 1329 Element res = getFromBundle(stack.getElement(), ref, fullUrl); 1330 return res; 1331 } 1332 stack = stack.parent; 1333 } 1334 1335 // todo: consult the external host for resolution 1336 return null; 1337 1338 } 1339 } 1340 1341 private ValueSet resolveBindingReference(DomainResource ctxt, Type reference) { 1342 if (reference instanceof UriType) { 1343 long t = System.nanoTime(); 1344 ValueSet fr = context.fetchResource(ValueSet.class, ((UriType) reference).getValue().toString()); 1345 txTime = txTime + (System.nanoTime() - t); 1346 return fr; 1347 } 1348 else if (reference instanceof Reference) { 1349 String s = ((Reference) reference).getReference(); 1350 if (s.startsWith("#")) { 1351 for (Resource c : ctxt.getContained()) { 1352 if (c.getId().equals(s.substring(1)) && (c instanceof ValueSet)) 1353 return (ValueSet) c; 1354 } 1355 return null; 1356 } else { 1357 long t = System.nanoTime(); 1358 ValueSet fr = context.fetchResource(ValueSet.class, ((Reference) reference).getReference()); 1359 txTime = txTime + (System.nanoTime() - t); 1360 return fr; 1361 } 1362 } 1363 else 1364 return null; 1365 } 1366 1367 private Element resolveInBundle(List<Element> entries, String ref, String fullUrl, String type, String id) { 1368 if (Utilities.isAbsoluteUrl(ref)) { 1369 // if the reference is absolute, then you resolve by fullUrl. No other thinking is required. 1370 for (Element entry : entries) { 1371 String fu = entry.getNamedChildValue("fullUrl"); 1372 if (ref.equals(fu)) 1373 return entry; 1374 } 1375 return null; 1376 } else { 1377 // split into base, type, and id 1378 String u = null; 1379 if (fullUrl != null && fullUrl.endsWith(type+"/"+id)) 1380 // fullUrl = complex 1381 u = fullUrl.substring((type+"/"+id).length())+ref; 1382 String[] parts = ref.split("\\/"); 1383 if (parts.length >= 2) { 1384 String t = parts[0]; 1385 String i = parts[1]; 1386 for (Element entry : entries) { 1387 String fu = entry.getNamedChildValue("fullUrl"); 1388 if (u != null && fullUrl.equals(u)) 1389 return entry; 1390 if (u == null) { 1391 Element resource = entry.getNamedChild("resource"); 1392 String et = resource.getType(); 1393 String eid = resource.getNamedChildValue("id"); 1394 if (t.equals(et) && i.equals(eid)) 1395 return entry; 1396 } 1397 } 1398 } 1399 return null; 1400 } 1401 } 1402 1403 private ElementDefinition resolveNameReference(StructureDefinitionSnapshotComponent snapshot, String contentReference) { 1404 for (ElementDefinition ed : snapshot.getElement()) 1405 if (contentReference.equals("#"+ed.getId())) 1406 return ed; 1407 return null; 1408 } 1409 1410 private StructureDefinition resolveProfile(StructureDefinition profile, String pr) { 1411 if (pr.startsWith("#")) { 1412 for (Resource r : profile.getContained()) { 1413 if (r.getId().equals(pr.substring(1)) && r instanceof StructureDefinition) 1414 return (StructureDefinition) r; 1415 } 1416 return null; 1417 } else { 1418 long t = System.nanoTime(); 1419 StructureDefinition fr = context.fetchResource(StructureDefinition.class, pr); 1420 sdTime = sdTime + (System.nanoTime() - t); 1421 return fr; 1422 } 1423 } 1424 1425 private ElementDefinition resolveType(String type) { 1426 if (logical != null) 1427 for (BundleEntryComponent be : logical.getEntry()) { 1428 if (be.hasResource() && be.getResource() instanceof StructureDefinition) { 1429 StructureDefinition sd = (StructureDefinition) be.getResource(); 1430 if (sd.getId().equals(type)) 1431 return sd.getSnapshot().getElement().get(0); 1432 } 1433 } 1434 String url = "http://hl7.org/fhir/StructureDefinition/" + type; 1435 long t = System.nanoTime(); 1436 StructureDefinition sd = context.fetchResource(StructureDefinition.class, url); 1437 sdTime = sdTime + (System.nanoTime() - t); 1438 if (sd == null || !sd.hasSnapshot()) 1439 return null; 1440 else 1441 return sd.getSnapshot().getElement().get(0); 1442 } 1443 1444 public void setAnyExtensionsAllowed(boolean anyExtensionsAllowed) { 1445 this.anyExtensionsAllowed = anyExtensionsAllowed; 1446 } 1447 1448 public void setBestPracticeWarningLevel(BestPracticeWarningLevel value) { 1449 bpWarnings = value; 1450 } 1451 1452 @Override 1453 public void setCheckDisplay(CheckDisplayOption checkDisplay) { 1454 this.checkDisplay = checkDisplay; 1455 } 1456 1457 public void setSuppressLoincSnomedMessages(boolean suppressLoincSnomedMessages) { 1458 this.suppressLoincSnomedMessages = suppressLoincSnomedMessages; 1459 } 1460 1461 public IdStatus getResourceIdRule() { 1462 return resourceIdRule; 1463 } 1464 1465 public void setResourceIdRule(IdStatus resourceIdRule) { 1466 this.resourceIdRule = resourceIdRule; 1467 } 1468 1469 /** 1470 * 1471 * @param element 1472 * - the candidate that might be in the slice 1473 * @param path 1474 * - for reporting any errors. the XPath for the element 1475 * @param slice 1476 * - the definition of how slicing is determined 1477 * @param ed 1478 * - the slice for which to test membership 1479 * @return 1480 * @throws DefinitionException 1481 * @throws DefinitionException 1482 * @throws Exception 1483 */ 1484 private boolean sliceMatches(Element element, String path, ElementDefinition slice, ElementDefinition ed, StructureDefinition profile) throws DefinitionException, DefinitionException { 1485 if (!slice.getSlicing().hasDiscriminator()) 1486 return false; // cannot validate in this case 1487 for (StringType s : slice.getSlicing().getDiscriminator()) { 1488 String discriminator = s.getValue(); 1489 ElementDefinition criteria = getCriteriaForDiscriminator(path, ed, discriminator, profile); 1490 if (discriminator.equals("url") && criteria.getPath().equals("Extension.url")) { 1491 if (!element.getNamedChildValue("url").equals(((UriType) criteria.getFixed()).asStringValue())) 1492 return false; 1493 } else { 1494 Element value = getValueForDiscriminator(element, discriminator, criteria); 1495 if (!valueMatchesCriteria(value, criteria)) 1496 return false; 1497 } 1498 } 1499 return true; 1500 } 1501 1502 // we assume that the following things are true: 1503 // the instance at root is valid against the schema and schematron 1504 // the instance validator had no issues against the base resource profile 1505 private void start(List<ValidationMessage> errors, Element resource, Element element, StructureDefinition profile, NodeStack stack) throws FHIRException, FHIRException { 1506 // profile is valid, and matches the resource name 1507 if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), profile.hasSnapshot(), 1508 "StructureDefinition has no snapshot - validation is against the snapshot, so it must be provided")) { 1509 validateElement(errors, profile, profile.getSnapshot().getElement().get(0), null, null, resource, element, element.getName(), stack, false); 1510 1511 checkDeclaredProfiles(errors, resource, element, stack); 1512 1513 // specific known special validations 1514 if (element.getType().equals("Bundle")) 1515 validateBundle(errors, element, stack); 1516 if (element.getType().equals("Observation")) 1517 validateObservation(errors, element, stack); 1518 if (element.getType().equals("QuestionnaireResponse")) 1519 validateQuestionannaireResponse(errors, element, stack); 1520 } 1521 } 1522 1523 private void validateQuestionannaireResponse(List<ValidationMessage> errors, Element element, NodeStack stack) { 1524 Element q = element.getNamedChild("questionnaire"); 1525 if (hint(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), q != null, "No questionnaire is identified, so no validation can be performed against the base questionnaire")) { 1526 long t = System.nanoTime(); 1527 Questionnaire qsrc = context.fetchResource(Questionnaire.class, q.getNamedChildValue("reference")); 1528 sdTime = sdTime + (System.nanoTime() - t); 1529 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")) { 1530 boolean inProgress = "in-progress".equals(element.getNamedChildValue("status")); 1531 validateQuestionannaireResponseItems(qsrc, qsrc.getItem(), errors, element, stack, inProgress); 1532 } 1533 } 1534 } 1535 1536 private void validateQuestionannaireResponseItem(Questionnaire qsrc, QuestionnaireItemComponent qItem, List<ValidationMessage> errors, Element element, NodeStack stack, boolean inProgress) { 1537 String text = element.getNamedChildValue("text"); 1538 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()); 1539 1540 List<Element> answers = new ArrayList<Element>(); 1541 element.getNamedChildren("answer", answers); 1542 if (inProgress) 1543 warning(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), (answers.size() > 0) || !qItem.getRequired(), "No response answer found for required item "+qItem.getLinkId()); 1544 else 1545 rule(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), (answers.size() > 0) || !qItem.getRequired(), "No response answer found for required item "+qItem.getLinkId()); 1546 if (answers.size() > 1) 1547 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"); 1548 1549 for (Element answer : answers) { 1550 NodeStack ns = stack.push(answer, -1, null, null); 1551 switch (qItem.getType()) { 1552 case GROUP: 1553 rule(errors, IssueType.STRUCTURE, answer.line(), answer.col(), stack.getLiteralPath(), false, "Items of type group should not have answers"); 1554 break; 1555 case DISPLAY: // nothing 1556 break; 1557 case BOOLEAN: 1558 validateQuestionnaireResponseItemType(errors, answer, ns, "boolean"); 1559 break; 1560 case DECIMAL: 1561 validateQuestionnaireResponseItemType(errors, answer, ns, "decimal"); 1562 break; 1563 case INTEGER: 1564 validateQuestionnaireResponseItemType(errors, answer, ns, "integer"); 1565 break; 1566 case DATE: 1567 validateQuestionnaireResponseItemType(errors, answer, ns, "date"); 1568 break; 1569 case DATETIME: 1570 validateQuestionnaireResponseItemType(errors, answer, ns, "dateTime"); 1571 break; 1572 case INSTANT: 1573 validateQuestionnaireResponseItemType(errors, answer, ns, "instant"); 1574 break; 1575 case TIME: 1576 validateQuestionnaireResponseItemType(errors, answer, ns, "time"); 1577 break; 1578 case STRING: 1579 validateQuestionnaireResponseItemType(errors, answer, ns, "string"); 1580 break; 1581 case TEXT: 1582 validateQuestionnaireResponseItemType(errors, answer, ns, "text"); 1583 break; 1584 case URL: 1585 validateQuestionnaireResponseItemType(errors, answer, ns, "uri"); 1586 break; 1587 case ATTACHMENT: 1588 validateQuestionnaireResponseItemType(errors, answer, ns, "Attachment"); 1589 break; 1590 case REFERENCE: 1591 validateQuestionnaireResponseItemType(errors, answer, ns, "Reference"); 1592 break; 1593 case QUANTITY: 1594 if (validateQuestionnaireResponseItemType(errors, answer, ns, "Quantity").equals("Quantity")) 1595 if (qItem.hasExtension("???")) 1596 validateQuestionnaireResponseItemQuantity(errors, answer, ns); 1597 break; 1598 case CHOICE: 1599 String itemType=validateQuestionnaireResponseItemType(errors, answer, ns, "Coding", "date", "time", "integer", "string"); 1600 if (itemType.equals("Coding")) validateAnswerCode(errors, answer, ns, qsrc, qItem, false); 1601 else if (itemType.equals("date")) checkOption(errors, answer, ns, qsrc, qItem, "date"); 1602 else if (itemType.equals("time")) checkOption(errors, answer, ns, qsrc, qItem, "time"); 1603 else if (itemType.equals("integer")) checkOption(errors, answer, ns, qsrc, qItem, "integer"); 1604 else if (itemType.equals("string")) checkOption(errors, answer, ns, qsrc, qItem, "string"); 1605 break; 1606 case OPENCHOICE: 1607 itemType=validateQuestionnaireResponseItemType(errors, answer, ns, "Coding", "date", "time", "integer", "string"); 1608 if (itemType.equals("Coding")) validateAnswerCode(errors, answer, ns, qsrc, qItem, true); 1609 else if (itemType.equals("date")) checkOption(errors, answer, ns, qsrc, qItem, "date"); 1610 else if (itemType.equals("time")) checkOption(errors, answer, ns, qsrc, qItem, "time"); 1611 else if (itemType.equals("integer")) checkOption(errors, answer, ns, qsrc, qItem, "integer"); 1612 else if (itemType.equals("string")) checkOption(errors, answer, ns, qsrc, qItem, "string", true); 1613 break; 1614 default: 1615 break; 1616 } 1617 validateQuestionannaireResponseItems(qsrc, qItem.getItem(), errors, answer, stack, inProgress); 1618 } 1619 if (qItem.getType() == null) { 1620 fail(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), false, "Definition for item "+qItem.getLinkId() + " does not contain a type"); 1621 } else if (qItem.getType() == QuestionnaireItemType.GROUP) { 1622 validateQuestionannaireResponseItems(qsrc, qItem.getItem(), errors, element, stack, inProgress); 1623 } else { 1624 List<Element> items = new ArrayList<Element>(); 1625 element.getNamedChildren("item", items); 1626 for (Element item : items) { 1627 NodeStack ns = stack.push(item, -1, null, null); 1628 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()); 1629 } 1630 } 1631 } 1632 1633 private void validateQuestionannaireResponseItem(Questionnaire qsrc, QuestionnaireItemComponent qItem, List<ValidationMessage> errors, List<Element> elements, NodeStack stack, boolean inProgress) { 1634 if (elements.size() > 1) 1635 rule(errors, IssueType.INVALID, elements.get(1).line(), elements.get(1).col(), stack.getLiteralPath(), qItem.getRepeats(), "Only one response item with this linkId allowed"); 1636 for (Element element : elements) { 1637 NodeStack ns = stack.push(element, -1, null, null); 1638 validateQuestionannaireResponseItem(qsrc, qItem, errors, element, ns, inProgress); 1639 } 1640 } 1641 1642 private int getLinkIdIndex(List<QuestionnaireItemComponent> qItems, String linkId) { 1643 for (int i = 0; i < qItems.size(); i++) { 1644 if (linkId.equals(qItems.get(i).getLinkId())) 1645 return i; 1646 } 1647 return -1; 1648 } 1649 1650 private void validateQuestionannaireResponseItems(Questionnaire qsrc, List<QuestionnaireItemComponent> qItems, List<ValidationMessage> errors, Element element, NodeStack stack, boolean inProgress) { 1651 List<Element> items = new ArrayList<Element>(); 1652 element.getNamedChildren("item", items); 1653 // now, sort into stacks 1654 Map<String, List<Element>> map = new HashMap<String, List<Element>>(); 1655 int lastIndex = -1; 1656 for (Element item : items) { 1657 String linkId = item.getNamedChildValue("linkId"); 1658 if (rule(errors, IssueType.REQUIRED, item.line(), item.col(), stack.getLiteralPath(), !Utilities.noString(linkId), "No LinkId, so can't be validated")) { 1659 int index = getLinkIdIndex(qItems, linkId); 1660 if (index == -1) { 1661 QuestionnaireItemComponent qItem = findQuestionnaireItem(qsrc, linkId); 1662 if (qItem != null) { 1663 rule(errors, IssueType.STRUCTURE, item.line(), item.col(), stack.getLiteralPath(), index > -1, "Structural Error: item is in the wrong place"); 1664 NodeStack ns = stack.push(item, -1, null, null); 1665 validateQuestionannaireResponseItem(qsrc, qItem, errors, element, ns, inProgress); 1666 } 1667 else 1668 rule(errors, IssueType.NOTFOUND, item.line(), item.col(), stack.getLiteralPath(), index > -1, "LinkId \""+linkId+"\" not found in questionnaire"); 1669 } 1670 else 1671 { 1672 rule(errors, IssueType.STRUCTURE, item.line(), item.col(), stack.getLiteralPath(), index >= lastIndex, "Structural Error: items are out of order"); 1673 lastIndex = index; 1674 List<Element> mapItem = map.get(linkId); 1675 if (mapItem == null) { 1676 mapItem = new ArrayList<Element>(); 1677 map.put(linkId, mapItem); 1678 } 1679 mapItem.add(item); 1680 } 1681 } 1682 } 1683 1684 // ok, now we have a list of known items, grouped by linkId. We"ve made an error for anything out of order 1685 for (QuestionnaireItemComponent qItem : qItems) { 1686 List<Element> mapItem = map.get(qItem.getLinkId()); 1687 if (mapItem != null) 1688 validateQuestionannaireResponseItem(qsrc, qItem, errors, mapItem, stack, inProgress); 1689 else 1690 rule(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), !qItem.getRequired(), "No response found for required item "+qItem.getLinkId()); 1691 } 1692 } 1693 1694 private void validateQuestionnaireResponseItemQuantity( List<ValidationMessage> errors, Element answer, NodeStack stack) { 1695 1696 } 1697 1698 private String validateQuestionnaireResponseItemType(List<ValidationMessage> errors, Element element, NodeStack stack, String... types) { 1699 List<Element> values = new ArrayList<Element>(); 1700 element.getNamedChildrenWithWildcard("value[x]", values); 1701 if (values.size() > 0) { 1702 NodeStack ns = stack.push(values.get(0), -1, null, null); 1703 CommaSeparatedStringBuilder l = new CommaSeparatedStringBuilder(); 1704 for (String s : types) { 1705 l.append(s); 1706 if (values.get(0).getName().equals("value"+Utilities.capitalize(s))) 1707 return(s); 1708 } 1709 if (types.length == 1) 1710 rule(errors, IssueType.STRUCTURE, values.get(0).line(), values.get(0).col(), ns.getLiteralPath(), false, "Answer value must be of type "+types[0]); 1711 else 1712 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()); 1713 } 1714 return null; 1715 } 1716 1717 private QuestionnaireItemComponent findQuestionnaireItem(Questionnaire qSrc, String linkId) { 1718 return findItem(qSrc.getItem(), linkId); 1719 } 1720 1721 private QuestionnaireItemComponent findItem(List<QuestionnaireItemComponent> list, String linkId) { 1722 for (QuestionnaireItemComponent item : list) { 1723 if (linkId.equals(item.getLinkId())) 1724 return item; 1725 QuestionnaireItemComponent result = findItem(item.getItem(), linkId); 1726 if (result != null) 1727 return result; 1728 } 1729 return null; 1730 } 1731 1732 /* private void validateAnswerCode(List<ValidationMessage> errors, Element value, NodeStack stack, List<Coding> optionList) { 1733 String system = value.getNamedChildValue("system"); 1734 String code = value.getNamedChildValue("code"); 1735 boolean found = false; 1736 for (Coding c : optionList) { 1737 if (ObjectUtil.equals(c.getSystem(), system) && ObjectUtil.equals(c.getCode(), code)) { 1738 found = true; 1739 break; 1740 } 1741 } 1742 rule(errors, IssueType.STRUCTURE, value.line(), value.col(), stack.getLiteralPath(), found, "The code "+system+"::"+code+" is not a valid option"); 1743 }*/ 1744 1745 private void validateAnswerCode(List<ValidationMessage> errors, Element value, NodeStack stack, Questionnaire qSrc, Reference ref, boolean theOpenChoice) { 1746 ValueSet vs = resolveBindingReference(qSrc, ref); 1747 if (warning(errors, IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), vs != null, "ValueSet " + describeReference(ref) + " not found")) { 1748 try { 1749 Coding c = readAsCoding(value); 1750 if (isBlank(c.getCode()) && isBlank(c.getSystem()) && isNotBlank(c.getDisplay())) { 1751 if (theOpenChoice) { 1752 return; 1753 } 1754 } 1755 1756 long t = System.nanoTime(); 1757 ValidationResult res = context.validateCode(c, vs); 1758 txTime = txTime + (System.nanoTime() - t); 1759 if (!res.isOk()) 1760 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"); 1761 } catch (Exception e) { 1762 warning(errors, IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), false, "Error " + e.getMessage() + " validating Coding against Questionnaire Options"); 1763 } 1764 } 1765 } 1766 1767 private void validateAnswerCode( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean theOpenChoice) { 1768 Element v = answer.getNamedChild("valueCoding"); 1769 NodeStack ns = stack.push(v, -1, null, null); 1770 if (qItem.getOption().size() > 0) 1771 checkCodingOption(errors, answer, stack, qSrc, qItem, theOpenChoice); 1772 // validateAnswerCode(errors, v, stack, qItem.getOption()); 1773 else if (qItem.hasOptions()) 1774 validateAnswerCode(errors, v, stack, qSrc, qItem.getOptions(), theOpenChoice); 1775 else 1776 hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Cannot validate options because no option or options are provided"); 1777 } 1778 1779 private void checkOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, String type) { 1780 checkOption(errors, answer, stack, qSrc, qItem, type, false); 1781 } 1782 1783 private void checkOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, String type, boolean openChoice) { 1784 if (type.equals("integer")) checkIntegerOption(errors, answer, stack, qSrc, qItem, openChoice); 1785 else if (type.equals("date")) checkDateOption(errors, answer, stack, qSrc, qItem, openChoice); 1786 else if (type.equals("time")) checkTimeOption(errors, answer, stack, qSrc, qItem, openChoice); 1787 else if (type.equals("string")) checkStringOption(errors, answer, stack, qSrc, qItem, openChoice); 1788 else if (type.equals("Coding")) checkCodingOption(errors, answer, stack, qSrc, qItem, openChoice); 1789 } 1790 1791 private void checkIntegerOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean openChoice) { 1792 Element v = answer.getNamedChild("valueInteger"); 1793 NodeStack ns = stack.push(v, -1, null, null); 1794 if (qItem.getOption().size() > 0) { 1795 List<IntegerType> list = new ArrayList<IntegerType>(); 1796 for (QuestionnaireItemOptionComponent components : qItem.getOption()) { 1797 try { 1798 list.add(components.getValueIntegerType()); 1799 } catch (FHIRException e) { 1800 // If it's the wrong type, just keep going 1801 } 1802 } 1803 if (list.isEmpty() && !openChoice) { 1804 rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Option list has no option values of type integer"); 1805 } else { 1806 boolean found = false; 1807 for (IntegerType item : list) { 1808 if (item.getValue() == Integer.parseInt(v.primitiveValue())) { 1809 found = true; 1810 break; 1811 } 1812 } 1813 if (!found) { 1814 rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, "The integer "+v.primitiveValue()+" is not a valid option"); 1815 } 1816 } 1817 } else 1818 hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Cannot validate integer answer option because no option list is provided"); 1819 } 1820 1821 private void checkDateOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean openChoice) { 1822 Element v = answer.getNamedChild("valueDate"); 1823 NodeStack ns = stack.push(v, -1, null, null); 1824 if (qItem.getOption().size() > 0) { 1825 List<DateType> list = new ArrayList<DateType>(); 1826 for (QuestionnaireItemOptionComponent components : qItem.getOption()) { 1827 try { 1828 list.add(components.getValueDateType()); 1829 } catch (FHIRException e) { 1830 // If it's the wrong type, just keep going 1831 } 1832 } 1833 if (list.isEmpty() && !openChoice) { 1834 rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Option list has no option values of type date"); 1835 } else { 1836 boolean found = false; 1837 for (DateType item : list) { 1838 if (item.getValue().equals(v.primitiveValue())) { 1839 found = true; 1840 break; 1841 } 1842 } 1843 if (!found) { 1844 rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, "The date "+v.primitiveValue()+" is not a valid option"); 1845 } 1846 } 1847 } else 1848 hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Cannot validate date answer option because no option list is provided"); 1849 } 1850 1851 private void checkTimeOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean openChoice) { 1852 Element v = answer.getNamedChild("valueTime"); 1853 NodeStack ns = stack.push(v, -1, null, null); 1854 if (qItem.getOption().size() > 0) { 1855 List<TimeType> list = new ArrayList<TimeType>(); 1856 for (QuestionnaireItemOptionComponent components : qItem.getOption()) { 1857 try { 1858 list.add(components.getValueTimeType()); 1859 } catch (FHIRException e) { 1860 // If it's the wrong type, just keep going 1861 } 1862 } 1863 if (list.isEmpty() && !openChoice) { 1864 rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Option list has no option values of type time"); 1865 } else { 1866 boolean found = false; 1867 for (TimeType item : list) { 1868 if (item.getValue().equals(v.primitiveValue())) { 1869 found = true; 1870 break; 1871 } 1872 } 1873 if (!found) { 1874 rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, "The time "+v.primitiveValue()+" is not a valid option"); 1875 } 1876 } 1877 } else 1878 hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Cannot validate time answer option because no option list is provided"); 1879 } 1880 1881 private void checkStringOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean openChoice) { 1882 Element v = answer.getNamedChild("valueString"); 1883 NodeStack ns = stack.push(v, -1, null, null); 1884 if (qItem.getOption().size() > 0) { 1885 List<StringType> list = new ArrayList<StringType>(); 1886 for (QuestionnaireItemOptionComponent components : qItem.getOption()) { 1887 try { 1888 if (components.getValue() != null) { 1889 list.add(components.getValueStringType()); 1890 } 1891 } catch (FHIRException e) { 1892 // If it's the wrong type, just keep going 1893 } 1894 } 1895 if (list.isEmpty() && !openChoice) { 1896 rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Option list has no option values of type string"); 1897 } else { 1898 boolean found = false; 1899 for (StringType item : list) { 1900 if (item.getValue().equals((v.primitiveValue()))) { 1901 found = true; 1902 break; 1903 } 1904 } 1905 if (!found) { 1906 rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, "The string "+v.primitiveValue()+" is not a valid option"); 1907 } 1908 } 1909 } else { 1910 hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Cannot validate string answer option because no option list is provided"); 1911 } 1912 } 1913 1914 private void checkCodingOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean openChoice) { 1915 Element v = answer.getNamedChild("valueCoding"); 1916 String system = v.getNamedChildValue("system"); 1917 String code = v.getNamedChildValue("code"); 1918 NodeStack ns = stack.push(v, -1, null, null); 1919 if (qItem.getOption().size() > 0) { 1920 List<Coding> list = new ArrayList<Coding>(); 1921 for (QuestionnaireItemOptionComponent components : qItem.getOption()) { 1922 try { 1923 if (components.getValue() != null) { 1924 list.add(components.getValueCoding()); 1925 } 1926 } catch (FHIRException e) { 1927 // If it's the wrong type, just keep going 1928 } 1929 } 1930 if (list.isEmpty() && !openChoice) { 1931 rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Option list has no option values of type coding"); 1932 } else { 1933 boolean found = false; 1934 for (Coding item : list) { 1935 if (ObjectUtil.equals(item.getSystem(), system) && ObjectUtil.equals(item.getCode(), code)) { 1936 found = true; 1937 break; 1938 } 1939 } 1940 if (!found) { 1941 rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, "The code "+system+"::"+code+" is not a valid option"); 1942 } 1943 } 1944 } else 1945 hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Cannot validate Coding option because no option list is provided"); 1946 } 1947 1948 private String tail(String path) { 1949 return path.substring(path.lastIndexOf(".") + 1); 1950 } 1951 1952 private String tryParse(String ref) { 1953 String[] parts = ref.split("\\/"); 1954 switch (parts.length) { 1955 case 1: 1956 return null; 1957 case 2: 1958 return checkResourceType(parts[0]); 1959 default: 1960 if (parts[parts.length - 2].equals("_history")) 1961 return checkResourceType(parts[parts.length - 4]); 1962 else 1963 return checkResourceType(parts[parts.length - 2]); 1964 } 1965 } 1966 1967 private boolean typesAreAllReference(List<TypeRefComponent> theType) { 1968 for (TypeRefComponent typeRefComponent : theType) { 1969 if (typeRefComponent.getCode().equals("Reference") == false) { 1970 return false; 1971 } 1972 } 1973 return true; 1974 } 1975 1976 private void validateBundle(List<ValidationMessage> errors, Element bundle, NodeStack stack) { 1977 List<Element> entries = new ArrayList<Element>(); 1978 bundle.getNamedChildren("entry", entries); 1979 String type = bundle.getNamedChildValue("type"); 1980 if (entries.size() == 0) { 1981 rule(errors, IssueType.INVALID, stack.getLiteralPath(), !(type.equals("document") || type.equals("message")), "Documents or Messages must contain at least one entry"); 1982 } else { 1983 Element firstEntry = entries.get(0); 1984 NodeStack firstStack = stack.push(firstEntry, 0, null, null); 1985 String fullUrl = firstEntry.getNamedChildValue("fullUrl"); 1986 1987 if (type.equals("document")) { 1988 Element resource = firstEntry.getNamedChild("resource"); 1989 NodeStack localStack = firstStack.push(resource, -1, null, null); 1990 String id = resource.getNamedChildValue("id"); 1991 if (rule(errors, IssueType.INVALID, firstEntry.line(), firstEntry.col(), stack.addToLiteralPath("entry", ":0"), resource != null, "No resource on first entry")) { 1992 validateDocument(errors, entries, resource, localStack.push(resource, -1, null, null), fullUrl, id); 1993 } 1994 } 1995 if (type.equals("message")) 1996 validateMessage(errors, bundle); 1997 } 1998 } 1999 2000 private void validateBundleReference(List<ValidationMessage> errors, List<Element> entries, Element ref, String name, NodeStack stack, String fullUrl, String type, String id) { 2001 if (ref != null && !Utilities.noString(ref.getNamedChildValue("reference"))) { 2002 Element target = resolveInBundle(entries, ref.getNamedChildValue("reference"), fullUrl, type, id); 2003 rule(errors, IssueType.INVALID, target.line(), target.col(), stack.addToLiteralPath("reference"), target != null, "Unable to resolve the target of the reference in the bundle (" + name + ")"); 2004 } 2005 } 2006 2007 private void validateContains(List<ValidationMessage> errors, String path, ElementDefinition child, ElementDefinition context, Element resource, Element element, NodeStack stack, IdStatus idstatus) throws FHIRException, FHIRException { 2008 String resourceName = element.getType(); 2009 long t = System.nanoTime(); 2010 StructureDefinition profile = this.context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + resourceName); 2011 sdTime = sdTime + (System.nanoTime() - t); 2012 // special case: resource wrapper is reset if we're crossing a bundle boundary, but not otherwise 2013 if (element.getSpecial() == SpecialElement.BUNDLE_ENTRY) 2014 resource = element; 2015 if (rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), profile != null, "No profile found for contained resource of type '" + resourceName + "'")) 2016 validateResource(errors, resource, element, profile, idstatus, stack); 2017 } 2018 2019 private void validateDocument(List<ValidationMessage> errors, List<Element> entries, Element composition, NodeStack stack, String fullUrl, String id) { 2020 // first entry must be a composition 2021 if (rule(errors, IssueType.INVALID, composition.line(), composition.col(), stack.getLiteralPath(), composition.getType().equals("Composition"), 2022 "The first entry in a document must be a composition")) { 2023 // the composition subject and section references must resolve in the bundle 2024 Element elem = composition.getNamedChild("subject"); 2025 if (rule(errors, IssueType.INVALID, composition.line(), composition.col(), stack.getLiteralPath(), elem != null, "A document composition must have a subject")) 2026 validateBundleReference(errors, entries, elem, "Composition Subject", stack.push(elem, -1, null, null), fullUrl, "Composition", id); 2027 validateSections(errors, entries, composition, stack, fullUrl, id); 2028 } 2029 } 2030 // rule(errors, IssueType.INVALID, bundle.line(), bundle.col(), "Bundle", !"urn:guid:".equals(base), "The base 'urn:guid:' is not valid (use urn:uuid:)"); 2031 // rule(errors, IssueType.INVALID, entry.line(), entry.col(), localStack.getLiteralPath(), !"urn:guid:".equals(ebase), "The base 'urn:guid:' is not valid"); 2032 // rule(errors, IssueType.INVALID, entry.line(), entry.col(), localStack.getLiteralPath(), !Utilities.noString(base) || !Utilities.noString(ebase), "entry 2033 // does not have a base"); 2034 // String firstBase = null; 2035 // firstBase = ebase == null ? base : ebase; 2036 2037 long time = 0; 2038 private void validateElement(List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition, StructureDefinition cprofile, ElementDefinition context, 2039 Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept) throws FHIRException, FHIRException { 2040// element.markValidation(profile, definition); 2041 2042 // System.out.println(" "+stack.getLiteralPath()+" "+Long.toString((System.nanoTime() - time) / 1000000)); 2043 // time = System.nanoTime(); 2044 checkInvariants(errors, stack.getLiteralPath(), profile, definition, null, null, resource, element); 2045 2046 // get the list of direct defined children, including slices 2047 List<ElementDefinition> childDefinitions = ProfileUtilities.getChildMap(profile, definition.getName(), definition.getPath(), definition.getContentReference()); 2048 2049 // 1. List the children, and remember their exact path (convenience) 2050 List<ElementInfo> children = new ArrayList<InstanceValidator.ElementInfo>(); 2051 ChildIterator iter = new ChildIterator(stack.getLiteralPath(), element); 2052 while (iter.next()) 2053 children.add(new ElementInfo(iter.name(), iter.element(), iter.path(), iter.count())); 2054 2055 // 2. assign children to a definition 2056 // for each definition, for each child, check whether it belongs in the slice 2057 ElementDefinition slice = null; 2058 for (int i = 0; i < childDefinitions.size(); i++) { 2059 ElementDefinition ed = childDefinitions.get(i); 2060 boolean process = true; 2061 // where are we with slicing 2062 if (ed.hasSlicing()) { 2063 if (slice != null && slice.getPath().equals(ed.getPath())) 2064 throw new DefinitionException("Slice encountered midway through path on " + slice.getPath()); 2065 slice = ed; 2066 process = false; 2067 } else if (slice != null && !slice.getPath().equals(ed.getPath())) 2068 slice = null; 2069 2070 if (process) { 2071 for (ElementInfo ei : children) { 2072 boolean match = false; 2073 if (slice == null) { 2074 match = nameMatches(ei.name, tail(ed.getPath())); 2075 } else { 2076 if (nameMatches(ei.name, tail(ed.getPath()))) 2077 match = sliceMatches(ei.element, ei.path, slice, ed, profile); 2078 } 2079 if (match) { 2080 if (rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, ei.definition == null, "Element matches more than one slice")) { 2081 ei.definition = ed; 2082 ei.index = i; 2083 } 2084 } 2085 } 2086 } 2087 } 2088 int last = -1; 2089 for (ElementInfo ei : children) { 2090 if (ei.path.endsWith(".extension")) 2091 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") + "\")"); 2092 else 2093 rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, (ei.definition != null), "Element is unknown or does not match any slice"); 2094 rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, (ei.definition == null) || (ei.index >= last), "Element is out of order"); 2095 last = ei.index; 2096 } 2097 2098 // 3. report any definitions that have a cardinality problem 2099 for (ElementDefinition ed : childDefinitions) { 2100 if (ed.getRepresentation().isEmpty()) { // ignore xml attributes 2101 int count = 0; 2102 for (ElementInfo ei : children) 2103 if (ei.definition == ed) 2104 count++; 2105 if (ed.getMin() > 0) { 2106 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), count >= ed.getMin(), 2107 "Element '" + stack.getLiteralPath() + "." + tail(ed.getPath()) + "': minimum required = " + Integer.toString(ed.getMin()) + ", but only found " + Integer.toString(count)); 2108 } 2109 if (ed.hasMax() && !ed.getMax().equals("*")) { 2110 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), count <= Integer.parseInt(ed.getMax()), 2111 "Element " + tail(ed.getPath()) + " @ " + stack.getLiteralPath() + ": max allowed = " + ed.getMax() + ", but found " + Integer.toString(count)); 2112 } 2113 2114 } 2115 } 2116 // 4. check order if any slices are orderd. (todo) 2117 2118 // 5. inspect each child for validity 2119 for (ElementInfo ei : children) { 2120 if (ei.definition != null) { 2121 String type = null; 2122 ElementDefinition typeDefn = null; 2123 if (ei.definition.getType().size() == 1 && !ei.definition.getType().get(0).getCode().equals("*") && !ei.definition.getType().get(0).getCode().equals("Element") 2124 && !ei.definition.getType().get(0).getCode().equals("BackboneElement")) 2125 type = ei.definition.getType().get(0).getCode(); 2126 else if (ei.definition.getType().size() == 1 && ei.definition.getType().get(0).getCode().equals("*")) { 2127 String prefix = tail(ei.definition.getPath()); 2128 assert prefix.endsWith("[x]"); 2129 type = ei.name.substring(prefix.length() - 3); 2130 if (isPrimitiveType(type)) 2131 type = Utilities.uncapitalize(type); 2132 } else if (ei.definition.getType().size() > 1) { 2133 2134 String prefix = tail(ei.definition.getPath()); 2135 assert typesAreAllReference(ei.definition.getType()) || prefix.endsWith("[x]") : prefix; 2136 2137 prefix = prefix.substring(0, prefix.length() - 3); 2138 for (TypeRefComponent t : ei.definition.getType()) 2139 if ((prefix + Utilities.capitalize(t.getCode())).equals(ei.name)) 2140 type = t.getCode(); 2141 if (type == null) { 2142 TypeRefComponent trc = ei.definition.getType().get(0); 2143 if (trc.getCode().equals("Reference")) 2144 type = "Reference"; 2145 else 2146 rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), stack.getLiteralPath(), false, 2147 "The element " + ei.name + " is illegal. Valid types at this point are " + describeTypes(ei.definition.getType())); 2148 } 2149 } else if (ei.definition.getContentReference() != null) { 2150 typeDefn = resolveNameReference(profile.getSnapshot(), ei.definition.getContentReference()); 2151 } 2152 2153 if (type != null) { 2154 if (type.startsWith("@")) { 2155 ei.definition = findElement(profile, type.substring(1)); 2156 type = null; 2157 } 2158 } 2159 NodeStack localStack = stack.push(ei.element, ei.count, ei.definition, type == null ? typeDefn : resolveType(type)); 2160 String localStackLiterapPath = localStack.getLiteralPath(); 2161 String eiPath = ei.path; 2162 assert(eiPath.equals(localStackLiterapPath)) : "ei.path: " + ei.path + " - localStack.getLiteralPath: " + localStackLiterapPath; 2163 boolean thisIsCodeableConcept = false; 2164 2165 if (type != null) { 2166 if (isPrimitiveType(type)) 2167 checkPrimitive(errors, ei.path, type, ei.definition, ei.element, profile); 2168 else { 2169 if (type.equals("Identifier")) 2170 checkIdentifier(errors, ei.path, ei.element, ei.definition); 2171 else if (type.equals("Coding")) 2172 checkCoding(errors, ei.path, ei.element, profile, ei.definition, inCodeableConcept); 2173 else if (type.equals("CodeableConcept")) { 2174 checkCodeableConcept(errors, ei.path, ei.element, profile, ei.definition); 2175 thisIsCodeableConcept = true; 2176 } else if (type.equals("Reference")) 2177 checkReference(errors, ei.path, ei.element, profile, ei.definition, actualType, localStack); 2178 2179 if (type.equals("Extension")) 2180 checkExtension(errors, ei.path, ei.element, ei.definition, profile, localStack); 2181 else if (type.equals("Resource")) 2182 validateContains(errors, ei.path, ei.definition, definition, resource, ei.element, localStack, idStatusForEntry(element, ei)); // if 2183 // (str.matches(".*([.,/])work\\1$")) 2184 else { 2185 StructureDefinition p = getProfileForType(type); 2186 if (rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.path, p != null, "Unknown type " + type)) { 2187 validateElement(errors, p, p.getSnapshot().getElement().get(0), profile, ei.definition, resource, ei.element, type, localStack, thisIsCodeableConcept); 2188 } 2189 } 2190 } 2191 } else { 2192 if (rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), stack.getLiteralPath(), ei.definition != null, "Unrecognised Content " + ei.name)) 2193 validateElement(errors, profile, ei.definition, null, null, resource, ei.element, type, localStack, false); 2194 } 2195 } 2196 } 2197 } 2198 2199 private IdStatus idStatusForEntry(Element ep, ElementInfo ei) { 2200 if (isBundleEntry(ei.path)) { 2201 Element req = ep.getNamedChild("request"); 2202 Element resp = ep.getNamedChild("response"); 2203 Element fullUrl = ep.getNamedChild("fullUrl"); 2204 Element method = null; 2205 Element url = null; 2206 if (req != null) { 2207 method = req.getNamedChild("method"); 2208 url = req.getNamedChild("url"); 2209 } 2210 if (resp != null) { 2211 return IdStatus.OPTIONAL; 2212 } if (method == null) { 2213 if (fullUrl == null) 2214 return IdStatus.REQUIRED; 2215 else if (fullUrl.primitiveValue().startsWith("urn:uuid:")) 2216 return IdStatus.OPTIONAL; 2217 else 2218 return IdStatus.REQUIRED; 2219 } else { 2220 String s = method.primitiveValue(); 2221 if (s.equals("PUT")) { 2222 if (url == null) 2223 return IdStatus.REQUIRED; 2224 else 2225 return IdStatus.OPTIONAL; // or maybe prohibited? not clear 2226 } else if (s.equals("POST")) 2227 return IdStatus.OPTIONAL; // this should be prohibited, but see task 9102 2228 else // actually, we should never get to here; a bundle entry with method get/delete should not have a resource 2229 return IdStatus.OPTIONAL; 2230 } 2231 } else if (isParametersEntry(ei.path)) 2232 return IdStatus.OPTIONAL; 2233 else 2234 return IdStatus.REQUIRED; 2235 } 2236 2237 private void checkInvariants(List<ValidationMessage> errors, String path, StructureDefinition profile, ElementDefinition ed, String typename, String typeProfile, Element resource, Element element) throws FHIRException, FHIRException { 2238 for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) { 2239 if (inv.hasExpression()) { 2240 ExpressionNode n = (ExpressionNode) inv.getUserData("validator.expression.cache"); 2241 if (n == null) { 2242 long t = System.nanoTime(); 2243 n = fpe.parse(inv.getExpression()); 2244 fpeTime = fpeTime + (System.nanoTime() - t); 2245 inv.setUserData("validator.expression.cache", n); 2246 } 2247 2248 String msg; 2249 boolean ok; 2250 try { 2251 long t = System.nanoTime(); 2252 ok = fpe.evaluateToBoolean(resource, element, n); 2253 fpeTime = fpeTime + (System.nanoTime() - t); 2254 msg = fpe.forLog(); 2255 } catch (Exception ex) { 2256 ok = false; 2257 msg = ex.getMessage(); 2258 } 2259 if (!ok) { 2260 if (inv.getSeverity() == ConstraintSeverity.ERROR) 2261 rule(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getHuman()+" ("+msg+") ["+inv.getExpression()+"]"); 2262 else if (inv.getSeverity() == ConstraintSeverity.WARNING) 2263 warning(errors, IssueType.INVARIANT, element.line(), element.line(), path, ok, inv.getHuman()+" ("+msg+") ["+inv.getExpression()+"]"); 2264 } 2265 } 2266 } 2267 } 2268 2269 private void validateMessage(List<ValidationMessage> errors, Element bundle) { 2270 // TODO Auto-generated method stub 2271 2272 } 2273 2274 private void validateObservation(List<ValidationMessage> errors, Element element, NodeStack stack) { 2275 // all observations should have a subject, a performer, and a time 2276 2277 bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), element.getNamedChild("subject") != null, "All observations should have a subject"); 2278 bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), element.getNamedChild("performer") != null, "All observations should have a performer"); 2279 bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), element.getNamedChild("effectiveDateTime") != null || element.getNamedChild("effectivePeriod") != null, 2280 "All observations should have an effectiveDateTime or an effectivePeriod"); 2281 } 2282 2283 /* 2284 * The actual base entry point 2285 */ 2286 private void validateResource(List<ValidationMessage> errors, Element resource, Element element, StructureDefinition profile, IdStatus idstatus, NodeStack stack) throws FHIRException, FHIRException { 2287 assert stack != null; 2288 assert resource != null; 2289 2290 // getting going - either we got a profile, or not. 2291 boolean ok = true; 2292 if (ok) { 2293 String resourceName = element.getType(); 2294 if (profile == null) { 2295 long t = System.nanoTime(); 2296 profile = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + resourceName); 2297 sdTime = sdTime + (System.nanoTime() - t); 2298 ok = rule(errors, IssueType.INVALID, element.line(), element.col(), stack.addToLiteralPath(resourceName), profile != null, "No profile found for resource type '" + resourceName + "'"); 2299 } else { 2300 String type = profile.getKind() == StructureDefinitionKind.LOGICAL ? profile.getId() : profile.hasBaseType() && profile.getDerivation() == TypeDerivationRule.CONSTRAINT ? profile.getBaseType() : profile.getName(); 2301 // special case: we have a bundle, and the profile is not for a bundle. We'll try the first entry instead 2302 if (!type.equals(resourceName) && resourceName.equals("Bundle")) { 2303 Element first = getFirstEntry(element); 2304 if (first != null && first.getType().equals(type)) { 2305 element = first; 2306 resourceName = element.getType(); 2307 idstatus = IdStatus.OPTIONAL; // why? 2308 } 2309 } 2310 ok = rule(errors, IssueType.INVALID, -1, -1, stack.getLiteralPath(), type.equals(resourceName), 2311 "Specified profile type was '" + type + "', but found type '" + resourceName + "'"); 2312 } 2313 } 2314 2315 if (ok) { 2316 if (idstatus == IdStatus.REQUIRED && (element.getNamedChild("id") == null)) 2317 rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, "Resource requires an id, but none is present"); 2318 else if (idstatus == IdStatus.PROHIBITED && (element.getNamedChild("id") != null)) 2319 rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, "Resource has an id, but none is allowed"); 2320 start(errors, resource, element, profile, stack); // root is both definition and type 2321 } 2322 } 2323 2324 private Element getFirstEntry(Element bundle) { 2325 List<Element> list = new ArrayList<Element>(); 2326 bundle.getNamedChildren("entry", list); 2327 if (list.isEmpty()) 2328 return null; 2329 Element resource = list.get(0).getNamedChild("resource"); 2330 if (resource == null) 2331 return null; 2332 else 2333 return resource; 2334 } 2335 2336 private void validateSections(List<ValidationMessage> errors, List<Element> entries, Element focus, NodeStack stack, String fullUrl, String id) { 2337 List<Element> sections = new ArrayList<Element>(); 2338 focus.getNamedChildren("entry", sections); 2339 int i = 0; 2340 for (Element section : sections) { 2341 NodeStack localStack = stack.push(section, 1, null, null); 2342 validateBundleReference(errors, entries, section.getNamedChild("content"), "Section Content", localStack, fullUrl, "Composition", id); 2343 validateSections(errors, entries, section, localStack, fullUrl, id); 2344 i++; 2345 } 2346 } 2347 2348 private boolean valueMatchesCriteria(Element value, ElementDefinition criteria) { 2349 // throw new Error("validation of slices not done yet"); 2350 return false; 2351 } 2352 2353 private boolean yearIsValid(String v) { 2354 if (v == null) { 2355 return false; 2356 } 2357 try { 2358 int i = Integer.parseInt(v.substring(0, Math.min(4, v.length()))); 2359 return i >= 1800 && i <= 2100; 2360 } catch (NumberFormatException e) { 2361 return false; 2362 } 2363 } 2364 2365 public class ChildIterator { 2366 private String basePath; 2367 private Element parent; 2368 private int cursor; 2369 private int lastCount; 2370 2371 public ChildIterator(String path, Element element) { 2372 parent = element; 2373 basePath = path; 2374 cursor = -1; 2375 } 2376 2377 public int count() { 2378 String nb = cursor == 0 ? "--" : parent.getChildren().get(cursor-1).getName(); 2379 String na = cursor >= parent.getChildren().size() - 1 ? "--" : parent.getChildren().get(cursor+1).getName(); 2380 if (name().equals(nb) || name().equals(na) ) { 2381 return lastCount + 1; 2382 } else 2383 return -1; 2384 } 2385 2386 public Element element() { 2387 return parent.getChildren().get(cursor); 2388 } 2389 2390 public String name() { 2391 return element().getName(); 2392 } 2393 2394 public boolean next() { 2395 if (cursor == -1) { 2396 cursor++; 2397 lastCount = 0; 2398 } else { 2399 String lastName = name(); 2400 cursor++; 2401 if (cursor < parent.getChildren().size() && name().equals(lastName)) 2402 lastCount++; 2403 else 2404 lastCount = 0; 2405 } 2406 return cursor < parent.getChildren().size(); 2407 } 2408 2409 public String path() { 2410 int i = count(); 2411 String sfx = ""; 2412 if (i > -1) { 2413 sfx = "[" + Integer.toString(lastCount + 1) + "]"; 2414 } 2415 return basePath + "." + name() + sfx; 2416 } 2417 } 2418 2419 private class NodeStack { 2420 private ElementDefinition definition; 2421 private Element element; 2422 private ElementDefinition extension; 2423 private String literalPath; // xpath format 2424 private List<String> logicalPaths; // dotted format, various entry points 2425 private NodeStack parent; 2426 private ElementDefinition type; 2427 2428 public NodeStack() { 2429 } 2430 2431 public NodeStack(Element element) { 2432 this.element = element; 2433 literalPath = element.getName(); 2434 } 2435 2436 public String addToLiteralPath(String... path) { 2437 StringBuilder b = new StringBuilder(); 2438 b.append(getLiteralPath()); 2439 for (String p : path) { 2440 if (p.startsWith(":")) { 2441 b.append("["); 2442 b.append(p.substring(1)); 2443 b.append("]"); 2444 } else { 2445 b.append("."); 2446 b.append(p); 2447 } 2448 } 2449 return b.toString(); 2450 } 2451 2452 private ElementDefinition getDefinition() { 2453 return definition; 2454 } 2455 2456 private Element getElement() { 2457 return element; 2458 } 2459 2460 private String getLiteralPath() { 2461 return literalPath == null ? "" : literalPath; 2462 } 2463 2464 private List<String> getLogicalPaths() { 2465 return logicalPaths == null ? new ArrayList<String>() : logicalPaths; 2466 } 2467 2468 private ElementDefinition getType() { 2469 return type; 2470 } 2471 2472 private NodeStack push(Element element, int count, ElementDefinition definition, ElementDefinition type) { 2473 NodeStack res = new NodeStack(); 2474 res.parent = this; 2475 res.element = element; 2476 res.definition = definition; 2477 res.literalPath = getLiteralPath() + "." + element.getName(); 2478 if (count > -1) 2479 res.literalPath = res.literalPath + "[" + Integer.toString(count) + "]"; 2480 res.logicalPaths = new ArrayList<String>(); 2481 if (type != null) { 2482 // type will be bull if we on a stitching point of a contained resource, or if.... 2483 res.type = type; 2484 String t = tail(definition.getPath()); 2485 for (String lp : getLogicalPaths()) { 2486 res.logicalPaths.add(lp + "." + t); 2487 if (t.endsWith("[x]")) 2488 res.logicalPaths.add(lp + "." + t.substring(0, t.length() - 3) + type.getPath()); 2489 } 2490 res.logicalPaths.add(type.getPath()); 2491 } else if (definition != null) { 2492 for (String lp : getLogicalPaths()) 2493 res.logicalPaths.add(lp + "." + element.getName()); 2494 } else 2495 res.logicalPaths.addAll(getLogicalPaths()); 2496 // CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 2497 // for (String lp : res.logicalPaths) 2498 // b.append(lp); 2499 // System.out.println(res.literalPath+" : "+b.toString()); 2500 return res; 2501 } 2502 2503 private void setType(ElementDefinition type) { 2504 this.type = type; 2505 } 2506 } 2507 2508 private void checkForProcessingInstruction(List<ValidationMessage> errors, Document document) { 2509 Node node = document.getFirstChild(); 2510 while (node != null) { 2511 rule(errors, IssueType.INVALID, -1, -1, "(document)", node.getNodeType() != Node.PROCESSING_INSTRUCTION_NODE, "No processing instructions allowed in resources"); 2512 node = node.getNextSibling(); 2513 } 2514 } 2515 2516 public class ElementInfo { 2517 2518 public int index; 2519 public int count; 2520 public ElementDefinition definition; 2521 private Element element; 2522 private String name; 2523 private String path; 2524 2525 public ElementInfo(String name, Element element, String path, int count) { 2526 this.name = name; 2527 this.element = element; 2528 this.path = path; 2529 this.count = count; 2530 } 2531 2532 public int col() { 2533 return element.col(); 2534 } 2535 2536 public int line() { 2537 return element.line(); 2538 } 2539 2540 } 2541 2542 public String reportTimes() { 2543 String s = String.format("Times: overall = %d, tx = %d, sd = %d, load = %d, fpe = %d", overall, txTime, sdTime, loadTime, fpeTime); 2544 overall = 0; 2545 txTime = 0; 2546 sdTime = 0; 2547 loadTime = 0; 2548 fpeTime = 0; 2549 return s; 2550 } 2551}