001package org.hl7.fhir.validation; 002 003 004import static org.apache.commons.lang3.StringUtils.isBlank; 005 006import java.io.ByteArrayOutputStream; 007import java.io.IOException; 008import java.text.ParseException; 009import java.text.SimpleDateFormat; 010import java.util.ArrayList; 011import java.util.Date; 012import java.util.HashMap; 013import java.util.List; 014import java.util.Map; 015 016/* 017 Copyright (c) 2011+, HL7, Inc. 018 All rights reserved. 019 020 Redistribution and use in source and binary forms, with or without modification, 021 are permitted provided that the following conditions are met: 022 023 * Redistributions of source code must retain the above copyright notice, this 024 list of conditions and the following disclaimer. 025 * Redistributions in binary form must reproduce the above copyright notice, 026 this list of conditions and the following disclaimer in the documentation 027 and/or other materials provided with the distribution. 028 * Neither the name of HL7 nor the names of its contributors may be used to 029 endorse or promote products derived from this software without specific 030 prior written permission. 031 032 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 033 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 034 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 035 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 036 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 037 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 038 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 039 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 040 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 041 POSSIBILITY OF SUCH DAMAGE. 042 043 */ 044 045import org.apache.commons.lang3.StringUtils; 046import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50; 047import org.hl7.fhir.convertors.factory.VersionConvertorFactory_14_50; 048import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_50; 049import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50; 050import org.hl7.fhir.exceptions.FHIRException; 051import org.hl7.fhir.r5.context.ContextUtilities; 052import org.hl7.fhir.r5.context.IWorkerContext; 053import org.hl7.fhir.r5.elementmodel.Element; 054import org.hl7.fhir.r5.elementmodel.JsonParser; 055import org.hl7.fhir.r5.formats.IParser.OutputStyle; 056import org.hl7.fhir.r5.model.Base; 057import org.hl7.fhir.r5.model.Coding; 058import org.hl7.fhir.r5.model.DomainResource; 059import org.hl7.fhir.r5.model.Resource; 060import org.hl7.fhir.r5.model.StructureDefinition; 061import org.hl7.fhir.r5.model.ValueSet; 062import org.hl7.fhir.r5.terminologies.ValueSetUtilities; 063import org.hl7.fhir.r5.utils.XVerExtensionManager; 064import org.hl7.fhir.r5.utils.XVerExtensionManager.XVerExtensionStatus; 065import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier.IValidationContextResourceLoader; 066import org.hl7.fhir.utilities.FhirPublication; 067import org.hl7.fhir.utilities.Utilities; 068import org.hl7.fhir.utilities.i18n.I18nConstants; 069import org.hl7.fhir.utilities.validation.ValidationMessage; 070import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 071import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 072import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 073import org.hl7.fhir.validation.cli.utils.ValidationLevel; 074import org.hl7.fhir.validation.instance.utils.IndexedElement; 075 076public class BaseValidator implements IValidationContextResourceLoader { 077 078 public class BooleanValue { 079 080 private boolean value; 081 082 public BooleanValue(boolean value) { 083 super(); 084 this.value = value; 085 } 086 087 public boolean isValue() { 088 return value; 089 } 090 091 public void setValue(boolean value) { 092 this.value = value; 093 } 094 095 public void see(boolean ok) { 096 value = value && ok; 097 } 098 } 099 100 public class TrackedLocationRelatedMessage { 101 private Object location; 102 private ValidationMessage vmsg; 103 public TrackedLocationRelatedMessage(Object location, ValidationMessage vmsg) { 104 super(); 105 this.location = location; 106 this.vmsg = vmsg; 107 } 108 public Object getLocation() { 109 return location; 110 } 111 public ValidationMessage getVmsg() { 112 return vmsg; 113 } 114 } 115 116 public class ValidationControl { 117 private boolean allowed; 118 private IssueSeverity level; 119 120 public ValidationControl(boolean allowed, IssueSeverity level) { 121 super(); 122 this.allowed = allowed; 123 this.level = level; 124 } 125 public boolean isAllowed() { 126 return allowed; 127 } 128 public IssueSeverity getLevel() { 129 return level; 130 } 131 } 132 133 public static final String NO_RULE_DATE = ValidationMessage.NO_RULE_DATE; 134 135 protected final String META = "meta"; 136 protected final String ENTRY = "entry"; 137 protected final String LINK = "link"; 138 protected final String DOCUMENT = "document"; 139 protected final String RESOURCE = "resource"; 140 protected final String MESSAGE = "message"; 141 protected final String SEARCHSET = "searchset"; 142 protected final String ID = "id"; 143 protected final String FULL_URL = "fullUrl"; 144 protected final String PATH_ARG = ":0"; 145 protected final String TYPE = "type"; 146 protected final String BUNDLE = "Bundle"; 147 protected final String LAST_UPDATED = "lastUpdated"; 148 149 150 protected Source source; 151 protected IWorkerContext context; 152 protected TimeTracker timeTracker = new TimeTracker(); 153 protected XVerExtensionManager xverManager; 154 protected List<TrackedLocationRelatedMessage> trackedMessages = new ArrayList<>(); 155 protected List<ValidationMessage> messagesToRemove = new ArrayList<>(); 156 private ValidationLevel level = ValidationLevel.HINTS; 157 protected Coding jurisdiction; 158 159 public BaseValidator(IWorkerContext context, XVerExtensionManager xverManager) { 160 super(); 161 this.context = context; 162 this.xverManager = xverManager; 163 if (this.xverManager == null) { 164 this.xverManager = new XVerExtensionManager(context); 165 } 166 167 } 168 169 private boolean doingLevel(IssueSeverity error) { 170 switch (error) { 171 case ERROR: 172 return level == null || level == ValidationLevel.ERRORS || level == ValidationLevel.WARNINGS || level == ValidationLevel.HINTS; 173 case FATAL: 174 return level == null || level == ValidationLevel.ERRORS || level == ValidationLevel.WARNINGS || level == ValidationLevel.HINTS; 175 case WARNING: 176 return level == null || level == ValidationLevel.WARNINGS || level == ValidationLevel.HINTS; 177 case INFORMATION: 178 return level == null || level == ValidationLevel.HINTS; 179 case NULL: 180 return true; 181 default: 182 return true; 183 } 184 } 185 186 private boolean doingErrors() { 187 return doingLevel(IssueSeverity.ERROR); 188 } 189 190 private boolean doingWarnings() { 191 return doingLevel(IssueSeverity.WARNING); 192 } 193 194 private boolean doingHints() { 195 return doingLevel(IssueSeverity.INFORMATION); 196 } 197 198 199 /** 200 * Use to control what validation the validator performs. 201 * Using this, you can turn particular kinds of validation on and off 202 * In addition, you can override the error | warning | hint level and make it a different level 203 * 204 * There is no way to do this using the command line validator; it's a service that is only 205 * offered when the validator is hosted in some other process 206 */ 207 private Map<String, ValidationControl> validationControl = new HashMap<>(); 208 209 /** 210 * Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails 211 * 212 * @param thePass 213 * Set this parameter to <code>false</code> if the validation does not pass 214 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 215 */ 216 @Deprecated 217 protected boolean fail(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg) { 218 if (!thePass && doingErrors()) { 219 addValidationMessage(errors, ruleDate, type, line, col, path, msg, IssueSeverity.FATAL, null); 220 } 221 return thePass; 222 } 223 224 protected boolean fail(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) { 225 if (!thePass && doingErrors()) { 226 String msg = context.formatMessage(theMessage, theMessageArguments); 227 addValidationMessage(errors, ruleDate, type, line, col, path, msg, IssueSeverity.FATAL, theMessage); 228 } 229 return thePass; 230 } 231 232 /** 233 * Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails 234 * 235 * @param thePass 236 * Set this parameter to <code>false</code> if the validation does not pass 237 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 238 */ 239 @Deprecated 240 protected boolean fail(List<ValidationMessage> errors, String ruleDate, IssueType type, List<String> pathParts, boolean thePass, String msg) { 241 if (!thePass && doingErrors()) { 242 String path = toPath(pathParts); 243 addValidationMessage(errors, ruleDate, type, -1, -1, path, msg, IssueSeverity.FATAL, null); 244 } 245 return thePass; 246 } 247 248 /** 249 * Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails 250 * 251 * @param thePass 252 * Set this parameter to <code>false</code> if the validation does not pass 253 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 254 */ 255 @Deprecated 256 protected boolean fail(List<ValidationMessage> errors, String ruleDate, IssueType type, List<String> pathParts, boolean thePass, String theMessage, Object... theMessageArguments) { 257 if (!thePass && doingErrors()) { 258 String path = toPath(pathParts); 259 addValidationMessage(errors, ruleDate, type, -1, -1, path, context.formatMessage(theMessage, theMessageArguments), IssueSeverity.FATAL, theMessage); 260 } 261 return thePass; 262 } 263 264 /** 265 * Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails 266 * 267 * @param thePass 268 * Set this parameter to <code>false</code> if the validation does not pass 269 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 270 */ 271 @Deprecated 272 protected boolean fail(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, boolean thePass, String msg) { 273 if (!thePass && doingErrors()) { 274 addValidationMessage(errors, ruleDate, type, -1, -1, path, msg, IssueSeverity.FATAL, null); 275 } 276 return thePass; 277 } 278 //TODO: i18n 279 protected boolean grammarWord(String w) { 280 return w.equals("and") || w.equals("or") || w.equals("a") || w.equals("the") || w.equals("for") || w.equals("this") || w.equals("that") || w.equals("of"); 281 } 282 283 /** 284 * Test a rule and add a {@link IssueSeverity#INFORMATION} validation message if the validation fails 285 * 286 * @param thePass 287 * Set this parameter to <code>false</code> if the validation does not pass 288 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 289 */ 290 protected boolean hint(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg) { 291 if (!thePass && doingHints()) { 292 String message = context.formatMessage(msg); 293 addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.INFORMATION, msg); 294 } 295 return thePass; 296 } 297 298 /** 299 * Test a rule and add a {@link IssueSeverity#INFORMATION} validation message if the validation fails. And mark it as a slicing hint for later recovery if appropriate 300 * 301 * @param thePass 302 * Set this parameter to <code>false</code> if the validation does not pass 303 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 304 */ 305 //FIXME: formatMessage should be done here 306 protected boolean slicingHint(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, boolean isCritical, String msg, String html, String[] text) { 307 if (!thePass && doingHints()) { 308 addValidationMessage(errors, ruleDate, type, line, col, path, msg, IssueSeverity.INFORMATION, null).setSlicingHint(true).setSliceHtml(html, text).setCriticalSignpost(isCritical); 309 } 310 return thePass; 311 } 312 313 /** 314 * Test a rule and add a {@link IssueSeverity#INFORMATION} validation message if the validation fails 315 * 316 * @param thePass 317 * Set this parameter to <code>false</code> if the validation does not pass 318 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 319 */ 320 protected boolean hint(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) { 321 if (!thePass && doingHints()) { 322 String message = context.formatMessage(theMessage, theMessageArguments); 323 addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.INFORMATION, theMessage); 324 } 325 return thePass; 326 } 327 328 protected boolean hintPlural(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, int num, String theMessage, Object... theMessageArguments) { 329 if (!thePass && doingHints()) { 330 String message = context.formatMessagePlural(num, theMessage, theMessageArguments); 331 addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.INFORMATION, theMessage); 332 } 333 return thePass; 334 } 335 336 protected ValidationMessage signpost(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, String theMessage, Object... theMessageArguments) { 337 String message = context.formatMessage(theMessage, theMessageArguments); 338 return addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.INFORMATION, theMessage).setSignpost(true); 339 } 340 341 protected boolean txHint(List<ValidationMessage> errors, String ruleDate, String txLink, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) { 342 if (!thePass && doingHints()) { 343 String message = context.formatMessage(theMessage, theMessageArguments); 344 addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.INFORMATION, Source.TerminologyEngine, theMessage).setTxLink(txLink); 345 } 346 return thePass; 347 } 348 349 /** 350 * Test a rule and add a {@link IssueSeverity#INFORMATION} validation message if the validation fails 351 * 352 * @param thePass 353 * Set this parameter to <code>false</code> if the validation does not pass 354 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 355 */ 356 protected boolean hint(List<ValidationMessage> errors, String ruleDate, IssueType type, List<String> pathParts, boolean thePass, String theMessage, Object... theMessageArguments) { 357 if (!thePass && doingHints()) { 358 String path = toPath(pathParts); 359 String message = context.formatMessage(theMessage, theMessageArguments); 360 addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.INFORMATION, theMessage); 361 } 362 return thePass; 363 } 364 365 /** 366 * Test a rule and add a {@link IssueSeverity#INFORMATION} validation message if the validation fails 367 * 368 * @param thePass 369 * Set this parameter to <code>false</code> if the validation does not pass 370 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 371 */ 372 protected boolean hint(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, boolean thePass, String theMessage, Object... theMessageArguments) { 373 if (!thePass && doingHints()) { 374 String message = context.formatMessage(theMessage, theMessageArguments); 375 addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.INFORMATION, null); 376 } 377 return thePass; 378 } 379 380 /** 381 * Test a rule and add a {@link IssueSeverity#ERROR} validation message if the validation fails 382 * 383 * @param thePass 384 * Set this parameter to <code>false</code> if the validation does not pass 385 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 386 */ 387 protected boolean rule(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) { 388 if (!thePass && doingErrors()) { 389 String message = context.formatMessage(theMessage, theMessageArguments); 390 addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.ERROR, theMessage); 391 } 392 return thePass; 393 } 394 395 protected boolean rulePlural(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, int num, String theMessage, Object... theMessageArguments) { 396 if (!thePass && doingErrors()) { 397 String message = context.formatMessagePlural(num, theMessage, theMessageArguments); 398 addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.ERROR, theMessage); 399 } 400 return thePass; 401 } 402 403 protected boolean txRule(List<ValidationMessage> errors, String ruleDate, String txLink, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) { 404 if (!thePass && doingErrors()) { 405 String message = context.formatMessage(theMessage, theMessageArguments); 406 ValidationMessage vm = new ValidationMessage(Source.TerminologyEngine, type, line, col, path, message, IssueSeverity.ERROR).setMessageId(theMessage); 407 vm.setRuleDate(ruleDate); 408 if (checkMsgId(theMessage, vm)) { 409 errors.add(vm.setTxLink(txLink)); 410 } 411 } 412 return thePass; 413 } 414 415 /** 416 * Test a rule and add a {@link IssueSeverity#ERROR} validation message if the validation fails 417 * 418 * @param thePass 419 * Set this parameter to <code>false</code> if the validation does not pass 420 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 421 */ 422 protected boolean rule(List<ValidationMessage> errors, String ruleDate, IssueType type, List<String> pathParts, boolean thePass, String msg) { 423 if (!thePass && doingErrors()) { 424 String path = toPath(pathParts); 425 addValidationMessage(errors, ruleDate, type, -1, -1, path, msg, IssueSeverity.ERROR, null); 426 } 427 return thePass; 428 } 429 430 /** 431 * Test a rule and add a {@link IssueSeverity#ERROR} validation message if the validation fails 432 * 433 * @param thePass 434 * Set this parameter to <code>false</code> if the validation does not pass 435 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 436 */ 437 protected boolean rule(List<ValidationMessage> errors, String ruleDate, IssueType type, List<String> pathParts, boolean thePass, String theMessage, Object... theMessageArguments) { 438 if (!thePass && doingErrors()) { 439 String path = toPath(pathParts); 440 String message = context.formatMessage(theMessage, theMessageArguments); 441 addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.ERROR, theMessage); 442 } 443 return thePass; 444 } 445 446 /** 447 * Test a rule and add a {@link IssueSeverity#ERROR} validation message if the validation fails 448 * 449 * @param thePass 450 * Set this parameter to <code>false</code> if the validation does not pass 451 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 452 */ 453 454 455 protected boolean rule(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, boolean thePass, String theMessage, Object... theMessageArguments) { 456 if (!thePass && doingErrors()) { 457 String message = context.formatMessage(theMessage, theMessageArguments); 458 addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.ERROR, theMessage); 459 } 460 return thePass; 461 } 462 463 protected boolean rulePlural(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, boolean thePass, int num, String theMessage, Object... theMessageArguments) { 464 if (!thePass && doingErrors()) { 465 String message = context.formatMessagePlural(num, theMessage, theMessageArguments); 466 addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.ERROR, theMessage); 467 } 468 return thePass; 469 } 470 471 public boolean rule(List<ValidationMessage> errors, String ruleDate, Source source, IssueType type, String path, boolean thePass, String msg) { 472 if (!thePass && doingErrors()) { 473 addValidationMessage(errors, ruleDate, type, -1, -1, path, msg, IssueSeverity.ERROR, source, null); 474 } 475 return thePass; 476 } 477 478 /** 479 * Test a rule and add a {@link IssueSeverity#ERROR} validation message if the validation fails 480 * 481 * @param thePass 482 * Set this parameter to <code>false</code> if the validation does not pass 483 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 484 */ 485 protected boolean ruleHtml(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, boolean thePass, String msg, String html) { 486 if (!thePass && doingErrors()) { 487 msg = context.formatMessage(msg, null); 488 html = context.formatMessage(html, null); 489 addValidationMessage(errors, ruleDate, type, path, msg, html, IssueSeverity.ERROR, null); 490 } 491 return thePass; 492 } 493 494 protected String splitByCamelCase(String s) { 495 StringBuilder b = new StringBuilder(); 496 for (int i = 0; i < s.length(); i++) { 497 char c = s.charAt(i); 498 if (Character.isUpperCase(c) && !(i == 0 || Character.isUpperCase(s.charAt(i-1)))) 499 b.append(' '); 500 b.append(c); 501 } 502 return b.toString(); 503 } 504 505 protected String stripPunctuation(String s, boolean numbers) { 506 StringBuilder b = new StringBuilder(); 507 for (char c : s.toCharArray()) { 508 int t = Character.getType(c); 509 if (t == Character.UPPERCASE_LETTER || t == Character.LOWERCASE_LETTER || t == Character.TITLECASE_LETTER || t == Character.MODIFIER_LETTER || t == Character.OTHER_LETTER || (t == Character.DECIMAL_DIGIT_NUMBER && numbers) || (t == Character.LETTER_NUMBER && numbers) || c == ' ') 510 b.append(c); 511 } 512 return b.toString(); 513 } 514 515 private String toPath(List<String> pathParts) { 516 if (pathParts == null || pathParts.isEmpty()) { 517 return ""; 518 } 519 return "//" + StringUtils.join(pathParts, '/'); 520 } 521 522 /** 523 * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails 524 * 525 * @param thePass 526 * Set this parameter to <code>false</code> if the validation does not pass 527 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 528 */ 529 protected boolean warning(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) { 530 if (!thePass && doingWarnings()) { 531 String nmsg = context.formatMessage(msg, theMessageArguments); 532 IssueSeverity severity = IssueSeverity.WARNING; 533 addValidationMessage(errors, ruleDate, type, line, col, path, nmsg, severity, msg); 534 } 535 return thePass; 536 537 } 538 539 protected boolean warningPlural(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, int num, String msg, Object... theMessageArguments) { 540 if (!thePass && doingWarnings()) { 541 String nmsg = context.formatMessagePlural(num, msg, theMessageArguments); 542 IssueSeverity severity = IssueSeverity.WARNING; 543 addValidationMessage(errors, ruleDate, type, line, col, path, nmsg, severity, msg); 544 } 545 return thePass; 546 547 } 548 549 protected ValidationMessage addValidationMessage(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, String msg, IssueSeverity theSeverity, String id) { 550 Source source = this.source; 551 return addValidationMessage(errors, ruleDate, type, line, col, path, msg, theSeverity, source, id); 552 } 553 554 protected ValidationMessage addValidationMessage(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, String msg, IssueSeverity theSeverity, Source theSource, String id) { 555 ValidationMessage validationMessage = new ValidationMessage(theSource, type, line, col, path, msg, theSeverity).setMessageId(id); 556 validationMessage.setRuleDate(ruleDate); 557 if (doingLevel(theSeverity) && checkMsgId(id, validationMessage)) { 558 errors.add(validationMessage); 559 } 560 return validationMessage; 561 } 562 563 public boolean checkMsgId(String id, ValidationMessage vm) { 564 if (id != null && validationControl.containsKey(id)) { 565 ValidationControl control = validationControl.get(id); 566 if (control.level != null) { 567 vm.setLevel(control.level); 568 } 569 return control.isAllowed(); 570 } 571 return true; 572 } 573 574 /** 575 * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails 576 * 577 * @param thePass 578 * Set this parameter to <code>false</code> if the validation does not pass 579 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 580 */ 581 protected boolean txWarning(List<ValidationMessage> errors, String ruleDate, String txLink, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) { 582 if (!thePass && doingWarnings()) { 583 String nmsg = context.formatMessage(msg, theMessageArguments); 584 ValidationMessage vmsg = new ValidationMessage(Source.TerminologyEngine, type, line, col, path, nmsg, IssueSeverity.WARNING).setTxLink(txLink).setMessageId(msg); 585 vmsg.setRuleDate(ruleDate); 586 if (checkMsgId(msg, vmsg)) { 587 errors.add(vmsg); 588 } 589 } 590 return thePass; 591 592 } 593 594 /** 595 * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails. Also, keep track of it later in case we want to remove it if we find a required binding for this element later 596 * 597 * @param thePass 598 * Set this parameter to <code>false</code> if the validation does not pass 599 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 600 */ 601 protected boolean txWarningForLaterRemoval(Object location, List<ValidationMessage> errors, String ruleDate, String txLink, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) { 602 if (!thePass && doingWarnings()) { 603 String nmsg = context.formatMessage(msg, theMessageArguments); 604 ValidationMessage vmsg = new ValidationMessage(Source.TerminologyEngine, type, line, col, path, nmsg, IssueSeverity.WARNING).setTxLink(txLink).setMessageId(msg); 605 vmsg.setRuleDate(ruleDate); 606 if (checkMsgId(msg, vmsg)) { 607 errors.add(vmsg); 608 } 609 trackedMessages.add(new TrackedLocationRelatedMessage(location, vmsg)); 610 } 611 return thePass; 612 613 } 614 615 protected void removeTrackedMessagesForLocation(List<ValidationMessage> errors, Object location, String path) { 616 List<TrackedLocationRelatedMessage> messages = new ArrayList<>(); 617 for (TrackedLocationRelatedMessage m : trackedMessages) { 618 if (m.getLocation() == location) { 619 messages.add(m); 620 messagesToRemove.add(m.getVmsg()); 621 } 622 } 623 trackedMessages.removeAll(messages); 624 } 625 626 protected boolean warningOrError(boolean isError, List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) { 627 if (!thePass) { 628 String nmsg = context.formatMessage(msg, theMessageArguments); 629 IssueSeverity lvl = isError ? IssueSeverity.ERROR : IssueSeverity.WARNING; 630 if (doingLevel(lvl)) { 631 addValidationMessage(errors, ruleDate, type, line, col, path, nmsg, lvl, msg); 632 } 633 } 634 return thePass; 635 636 } 637 638 /** 639 * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails 640 * 641 * @param thePass 642 * Set this parameter to <code>false</code> if the validation does not pass 643 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 644 */ 645 protected boolean warning(List<ValidationMessage> errors, String ruleDate, IssueType type, List<String> pathParts, boolean thePass, String theMessage, Object... theMessageArguments) { 646 if (!thePass && doingWarnings()) { 647 String path = toPath(pathParts); 648 String message = context.formatMessage(theMessage, theMessageArguments); 649 addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.WARNING, theMessage); 650 } 651 return thePass; 652 } 653 654 /** 655 * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails 656 * 657 * @param thePass 658 * Set this parameter to <code>false</code> if the validation does not pass 659 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 660 */ 661 protected boolean warning(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, boolean thePass, String msg, Object... theMessageArguments) { 662 if (!thePass && doingWarnings()) { 663 String message = context.formatMessage(msg, theMessageArguments); 664 addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.WARNING, null); 665 } 666 return thePass; 667 } 668 669 /** 670 * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails 671 * 672 * @param thePass 673 * Set this parameter to <code>false</code> if the validation does not pass 674 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 675 */ 676 protected boolean warningOrHint(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, boolean thePass, boolean warning, String msg, Object... theMessageArguments) { 677 if (!thePass) { 678 String message = context.formatMessage(msg, theMessageArguments); 679 IssueSeverity lvl = warning ? IssueSeverity.WARNING : IssueSeverity.INFORMATION; 680 if (doingLevel(lvl)) { 681 addValidationMessage(errors, ruleDate, type, -1, -1, path, message, lvl, null); 682 } 683 } 684 return thePass; 685 } 686 687 /** 688 * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails 689 * 690 * @param thePass 691 * Set this parameter to <code>false</code> if the validation does not pass 692 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 693 */ 694 protected boolean warningHtml(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, boolean thePass, String msg, String html) { 695 if (!thePass && doingWarnings()) { 696 addValidationMessage(errors, ruleDate, type, path, msg, html, IssueSeverity.WARNING, null); 697 } 698 return thePass; 699 } 700 701 /** 702 * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails 703 * 704 * @param thePass 705 * Set this parameter to <code>false</code> if the validation does not pass 706 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 707 */ 708 protected boolean warningHtml(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, boolean thePass, String msg, String html, Object... theMessageArguments) { 709 if (!thePass && doingWarnings()) { 710 String nmsg = context.formatMessage(msg, theMessageArguments); 711 addValidationMessage(errors, ruleDate, type, path, nmsg, html, IssueSeverity.WARNING, msg); 712 } 713 return thePass; 714 } 715 716 //--------- 717 /** 718 * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails 719 * 720 * @param thePass 721 * Set this parameter to <code>false</code> if the validation does not pass 722 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 723 */ 724 protected boolean suppressedwarning(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) { 725 if (!thePass && doingWarnings()) { 726 String nmsg = context.formatMessage(msg, theMessageArguments); 727 addValidationMessage(errors, ruleDate, type, line, col, path, nmsg, IssueSeverity.INFORMATION, msg); 728 } 729 return thePass; 730 731 } 732 733 /** 734 * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails 735 * 736 * @param thePass 737 * Set this parameter to <code>false</code> if the validation does not pass 738 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 739 */ 740 protected boolean suppressedwarning(List<ValidationMessage> errors, String ruleDate, IssueType type, List<String> pathParts, boolean thePass, String theMessage, Object... theMessageArguments) { 741 if (!thePass && doingWarnings()) { 742 String path = toPath(pathParts); 743 String message = context.formatMessage(theMessage, theMessageArguments); 744 addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.INFORMATION, theMessage); 745 } 746 return thePass; 747 } 748 749 /** 750 * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails 751 * 752 * @param thePass 753 * Set this parameter to <code>false</code> if the validation does not pass 754 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 755 */ 756 protected boolean suppressedwarning(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, boolean thePass, String msg) { 757 if (!thePass && doingWarnings()) { 758 addValidationMessage(errors, ruleDate, type, -1, -1, path, msg, IssueSeverity.INFORMATION, null); 759 } 760 return thePass; 761 } 762 763 /** 764 * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails 765 * 766 * @param thePass 767 * Set this parameter to <code>false</code> if the validation does not pass 768 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 769 */ 770 protected boolean suppressedwarning(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, boolean thePass, String msg, String html) { 771 if (!thePass && doingWarnings()) { 772 IssueSeverity severity = IssueSeverity.INFORMATION; 773 addValidationMessage(errors, ruleDate, type, path, msg, html, severity, null); 774 } 775 return thePass; 776 } 777 778 protected void addValidationMessage(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, String msg, String html, IssueSeverity theSeverity, String id) { 779 ValidationMessage vm = new ValidationMessage(source, type, -1, -1, path, msg, html, theSeverity); 780 vm.setRuleDate(ruleDate); 781 if (checkMsgId(id, vm)) { 782 if (doingLevel(theSeverity)) { 783 errors.add(vm.setMessageId(id)); 784 } 785 } 786 } 787 788 /** 789 * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails 790 * 791 * @param thePass 792 * Set this parameter to <code>false</code> if the validation does not pass 793 * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) 794 */ 795 protected boolean suppressedwarning(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, boolean thePass, String msg, String html, Object... theMessageArguments) { 796 if (!thePass && doingWarnings()) { 797 String nmsg = context.formatMessage(msg, theMessageArguments); 798 addValidationMessage(errors, ruleDate, type, path, nmsg, html, IssueSeverity.INFORMATION, msg); 799 } 800 return thePass; 801 } 802 803 804 protected ValueSet resolveBindingReference(DomainResource ctxt, String reference, String uri, Resource src) { 805 if (reference != null) { 806 if (reference.equals("http://www.rfc-editor.org/bcp/bcp13.txt")) { 807 reference = "http://hl7.org/fhir/ValueSet/mimetypes"; 808 } 809 if (reference.startsWith("#")) { 810 for (Resource c : ctxt.getContained()) { 811 if (c.getId().equals(reference.substring(1)) && (c instanceof ValueSet)) 812 return (ValueSet) c; 813 } 814 return null; 815 } else { 816 long t = System.nanoTime(); 817 ValueSet fr = context.fetchResource(ValueSet.class, reference, src); 818 if (fr == null) { 819 if (!Utilities.isAbsoluteUrl(reference)) { 820 reference = resolve(uri, reference); 821 fr = context.fetchResource(ValueSet.class, reference, src); 822 } 823 } 824 if (fr == null) { 825 fr = ValueSetUtilities.generateImplicitValueSet(reference); 826 } 827 828 timeTracker.tx(t, "vs "+uri); 829 return fr; 830 } 831 } else 832 return null; 833 } 834 835 836 private String resolve(String uri, String ref) { 837 if (isBlank(uri)) { 838 return ref; 839 } 840 String[] up = uri.split("\\/"); 841 String[] rp = ref.split("\\/"); 842 if (context.getResourceNames().contains(up[up.length - 2]) && context.getResourceNames().contains(rp[0])) { 843 StringBuilder b = new StringBuilder(); 844 for (int i = 0; i < up.length - 2; i++) { 845 b.append(up[i]); 846 b.append("/"); 847 } 848 b.append(ref); 849 return b.toString(); 850 } else 851 return ref; 852 } 853 854 protected String describeReference(String reference) { 855 if (reference == null) 856 return "null"; 857 return reference; 858 } 859 860 protected Base resolveInBundle(String url, Element bnd) { 861 if (bnd == null) 862 return null; 863 if (bnd.fhirType().equals(BUNDLE)) { 864 for (Element be : bnd.getChildrenByName(ENTRY)) { 865 Element res = be.getNamedChild(RESOURCE); 866 if (res != null) { 867 String fullUrl = be.getChildValue(FULL_URL); 868 String rt = res.fhirType(); 869 String id = res.getChildValue(ID); 870 if (url.equals(fullUrl)) 871 return res; 872 if (url.equals(rt + "/" + id)) 873 return res; 874 } 875 } 876 } 877 return null; 878 } 879 880 protected Element resolveInBundle(Element bundle, List<Element> entries, String ref, String fullUrl, String type, String id) { 881 @SuppressWarnings("unchecked") 882 Map<String, Element> map = (Map<String, Element>) bundle.getUserData("validator.entrymap"); 883 if (map == null) { 884 map = new HashMap<>(); 885 bundle.setUserData("validator.entrymap", map); 886 for (Element entry : entries) { 887 String fu = entry.getNamedChildValue(FULL_URL); 888 map.put(fu, entry); 889 Element resource = entry.getNamedChild(RESOURCE); 890 if (resource != null) { 891 String et = resource.getType(); 892 String eid = resource.getNamedChildValue(ID); 893 map.put(et+"/"+eid, entry); 894 } 895 } 896 } 897 898 if (Utilities.isAbsoluteUrl(ref)) { 899 // if the reference is absolute, then you resolve by fullUrl. No other thinking is required. 900 return map.get(ref); 901// for (Element entry : entries) { 902// String fu = entry.getNamedChildValue(FULL_URL); 903// if (ref.equals(fu)) 904// return entry; 905// } 906// return null; 907 } else { 908 // split into base, type, and id 909 String u = null; 910 if (fullUrl != null && fullUrl.endsWith(type + "/" + id)) 911 // fullUrl = complex 912 u = fullUrl.substring(0, fullUrl.length() - (type + "/" + id).length()) + ref; 913// u = fullUrl.substring((type+"/"+id).length())+ref; 914 String[] parts = ref.split("\\/"); 915 if (parts.length >= 2) { 916 String t = parts[0]; 917 String i = parts[1]; 918 Element res = map.get(u); 919 if (res == null) { 920 res = map.get(t+"/"+i); 921 } 922 return res; 923// for (Element entry : entries) { 924// String fu = entry.getNamedChildValue(FULL_URL); 925// if (fu != null && fu.equals(u)) 926// return entry; 927// if (u == null) { 928// Element resource = entry.getNamedChild(RESOURCE); 929// if (resource != null) { 930// String et = resource.getType(); 931// String eid = resource.getNamedChildValue(ID); 932// if (t.equals(et) && i.equals(eid)) 933// return entry; 934// } 935// } 936// } 937 } 938 return null; 939 } 940 } 941 942 943 protected IndexedElement getFromBundle(Element bundle, String ref, String fullUrl, List<ValidationMessage> errors, String path, String type, boolean isTransaction) { 944 String targetUrl = null; 945 String version = ""; 946 String resourceType = null; 947 if (ref.startsWith("http:") || ref.startsWith("urn:") || Utilities.isAbsoluteUrl(ref)) { 948 // We've got an absolute reference, no need to calculate 949 if (ref.contains("/_history/")) { 950 targetUrl = ref.substring(0, ref.indexOf("/_history/") - 1); 951 version = ref.substring(ref.indexOf("/_history/") + 10); 952 } else 953 targetUrl = ref; 954 955 } else if (fullUrl == null) { 956 //This isn't a problem for signatures - if it's a signature, we won't have a resolution for a relative reference. For anything else, this is an error 957 // but this rule doesn't apply for batches or transactions 958 rule(errors, NO_RULE_DATE, IssueType.REQUIRED, -1, -1, path, Utilities.existsInList(type, "batch-response", "transaction-response") || path.startsWith("Bundle.signature"), I18nConstants.BUNDLE_BUNDLE_FULLURL_MISSING); 959 return null; 960 961 } else if (ref.split("/").length != 2 && ref.split("/").length != 4) { 962 if (isTransaction) { 963 rule(errors, NO_RULE_DATE, IssueType.INVALID, -1, -1, path, isSearchUrl(ref), I18nConstants.REFERENCE_REF_FORMAT1, ref); 964 } else { 965 rule(errors, NO_RULE_DATE, IssueType.INVALID, -1, -1, path, false, I18nConstants.REFERENCE_REF_FORMAT2, ref); 966 } 967 return null; 968 969 } else { 970 String base = ""; 971 if (fullUrl.startsWith("urn")) { 972 String[] parts = fullUrl.split("\\:"); 973 for (int i = 0; i < parts.length - 1; i++) { 974 base = base + parts[i] + ":"; 975 } 976 } else { 977 String[] parts; 978 parts = fullUrl.split("/"); 979 for (int i = 0; i < parts.length - 2; i++) { 980 base = base + parts[i] + "/"; 981 } 982 } 983 984 String id = null; 985 if (ref.contains("/_history/")) { 986 version = ref.substring(ref.indexOf("/_history/") + 10); 987 String[] refBaseParts = ref.substring(0, ref.indexOf("/_history/")).split("/"); 988 resourceType = refBaseParts[0]; 989 id = refBaseParts[1]; 990 } else if (base.startsWith("urn")) { 991 resourceType = ref.split("/")[0]; 992 id = ref.split("/")[1]; 993 } else 994 id = ref; 995 996 targetUrl = base + id; 997 } 998 999 List<Element> entries = new ArrayList<Element>(); 1000 bundle.getNamedChildren(ENTRY, entries); 1001 Element match = null; 1002 int matchIndex = -1; 1003 for (int i = 0; i < entries.size(); i++) { 1004 Element we = entries.get(i); 1005 if (targetUrl.equals(we.getChildValue(FULL_URL))) { 1006 Element r = we.getNamedChild(RESOURCE); 1007 if (version.isEmpty()) { 1008 rule(errors, NO_RULE_DATE, IssueType.FORBIDDEN, -1, -1, path, match == null, I18nConstants.BUNDLE_BUNDLE_MULTIPLEMATCHES, ref); 1009 match = r; 1010 matchIndex = i; 1011 } else { 1012 try { 1013 if (version.equals(r.getChildren(META).get(0).getChildValue("versionId"))) { 1014 rule(errors, NO_RULE_DATE, IssueType.FORBIDDEN, -1, -1, path, match == null, I18nConstants.BUNDLE_BUNDLE_MULTIPLEMATCHES, ref); 1015 match = r; 1016 matchIndex = i; 1017 } 1018 } catch (Exception e) { 1019 warning(errors, NO_RULE_DATE, IssueType.REQUIRED, -1, -1, path, r.getChildren(META).size() == 1 && r.getChildren(META).get(0).getChildValue("versionId") != null, I18nConstants.BUNDLE_BUNDLE_FULLURL_NEEDVERSION, targetUrl); 1020 // If one of these things is null 1021 } 1022 } 1023 } 1024 } 1025 1026 if (match != null && resourceType != null) 1027 rule(errors, NO_RULE_DATE, IssueType.REQUIRED, -1, -1, path, match.getType().equals(resourceType), I18nConstants.REFERENCE_REF_RESOURCETYPE, ref, match.getType()); 1028 if (match == null) { 1029 warning(errors, NO_RULE_DATE, IssueType.REQUIRED, -1, -1, path, !ref.startsWith("urn"), I18nConstants.BUNDLE_BUNDLE_NOT_LOCAL, ref); 1030 if (!Utilities.isAbsoluteUrl(ref)) { 1031 String[] p = ref.split("\\/"); 1032 List<Element> ml = new ArrayList<>(); 1033 if (p.length >= 2 && Utilities.existsInList(p[0], context.getResourceNames()) && Utilities.isValidId(p[1])) { 1034 for (int i = 0; i < entries.size(); i++) { 1035 Element we = entries.get(i); 1036 Element r = we.getNamedChild(RESOURCE); 1037 if (r != null && p[0].equals(r.fhirType()) && p[1].equals(r.getNamedChildValue("id")) ) { 1038 ml.add(we); 1039 } 1040 } 1041 } 1042 if (ml.size() > 1) { 1043 warning(errors, NO_RULE_DATE, IssueType.REQUIRED, -1, -1, path, false, I18nConstants.BUNDLE_POSSSIBLE_MATCHES, ref, targetUrl); 1044 } 1045 for (Element e : ml) { 1046 String fu = e.getChildValue(FULL_URL); 1047 int i = entries.indexOf(e); 1048 if (fu == null) { 1049 warning(errors, NO_RULE_DATE, IssueType.REQUIRED, -1, -1, path, false, I18nConstants.BUNDLE_BUNDLE_POSSIBLE_MATCH_NO_FU, i, ref, targetUrl); 1050 } else { 1051 warning(errors, NO_RULE_DATE, IssueType.REQUIRED, -1, -1, path, false, I18nConstants.BUNDLE_BUNDLE_POSSIBLE_MATCH_WRONG_FU, i, ref, fu, targetUrl); 1052 } 1053 } 1054 } 1055 } 1056 return match == null ? null : new IndexedElement(matchIndex, match, entries.get(matchIndex)); 1057 } 1058 1059 private boolean isSearchUrl(String ref) { 1060 if (Utilities.noString(ref) || !ref.contains("?")) { 1061 return false; 1062 } 1063 String tn = ref.substring(0, ref.indexOf("?")); 1064 String q = ref.substring(ref.indexOf("?") + 1); 1065 if (!context.getResourceNames().contains(tn)) { 1066 return false; 1067 } else { 1068 return q.matches("([_a-zA-Z][_a-zA-Z0-9]*=[^=&]+)(&([_a-zA-Z][_a-zA-Z0-9]*=[^=&]+))*"); 1069 } 1070 } 1071 1072 public Map<String, ValidationControl> getValidationControl() { 1073 return validationControl; 1074 } 1075 1076 public XVerExtensionStatus xverStatus(String url) { 1077 return xverManager.status(url); 1078 } 1079 1080 public boolean isXverUrl(String url) { 1081 return xverManager.matchingUrl(url); 1082 } 1083 1084 public StructureDefinition xverDefn(String url) { 1085 return xverManager.makeDefinition(url); 1086 } 1087 1088 public String xverVersion(String url) { 1089 return xverManager.getVersion(url); 1090 } 1091 1092 public String xverElementId(String url) { 1093 return xverManager.getElementId(url); 1094 } 1095 1096 public StructureDefinition getXverExt(StructureDefinition profile, List<ValidationMessage> errors, String url) { 1097 if (isXverUrl(url)) { 1098 switch (xverStatus(url)) { 1099 case BadVersion: 1100 rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, profile.getId(), false, I18nConstants.EXTENSION_EXT_VERSION_INVALID, url, xverVersion(url)); 1101 return null; 1102 case Unknown: 1103 rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, profile.getId(), false, I18nConstants.EXTENSION_EXT_VERSION_INVALIDID, url, xverElementId(url)); 1104 return null; 1105 case Invalid: 1106 rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, profile.getId(), false, I18nConstants.EXTENSION_EXT_VERSION_NOCHANGE, url, xverElementId(url)); 1107 return null; 1108 case Valid: 1109 StructureDefinition defn = xverDefn(url); 1110 new ContextUtilities(context).generateSnapshot(defn); 1111 context.cacheResource(defn); 1112 return defn; 1113 default: 1114 rule(errors, NO_RULE_DATE, IssueType.INVALID, profile.getId(), false, I18nConstants.EXTENSION_EXT_VERSION_INTERNAL, url); 1115 return null; 1116 } 1117 } else { 1118 return null; 1119 } 1120 } 1121 1122 public StructureDefinition getXverExt(List<ValidationMessage> errors, String path, Element element, String url) { 1123 if (isXverUrl(url)) { 1124 switch (xverStatus(url)) { 1125 case BadVersion: 1126 rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXTENSION_EXT_VERSION_INVALID, url, xverVersion(url)); 1127 break; 1128 case Unknown: 1129 rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXTENSION_EXT_VERSION_INVALIDID, url, xverElementId(url)); 1130 break; 1131 case Invalid: 1132 rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXTENSION_EXT_VERSION_NOCHANGE, url, xverElementId(url)); 1133 break; 1134 case Valid: 1135 StructureDefinition ex = xverDefn(url); 1136 new ContextUtilities(context).generateSnapshot(ex); 1137 context.cacheResource(ex); 1138 return ex; 1139 default: 1140 rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXTENSION_EXT_VERSION_INTERNAL, url); 1141 break; 1142 } 1143 } 1144 return null; 1145 } 1146 1147 protected String versionFromCanonical(String system) { 1148 if (system == null) { 1149 return null; 1150 } else if (system.contains("|")) { 1151 return system.substring(0, system.indexOf("|")); 1152 } else { 1153 return system; 1154 } 1155 } 1156 1157 protected String systemFromCanonical(String system) { 1158 if (system == null) { 1159 return null; 1160 } else if (system.contains("|")) { 1161 return system.substring(system.indexOf("|")+1); 1162 } else { 1163 return system; 1164 } 1165 } 1166 1167 @Override 1168 public Resource loadContainedResource(List<ValidationMessage> errors, String path, Element resource, String id, Class<? extends Resource> class1) throws FHIRException { 1169 for (Element contained : resource.getChildren("contained")) { 1170 if (contained.getIdBase().equals(id)) { 1171 return loadFoundResource(errors, path, contained, class1); 1172 } 1173 } 1174 return null; 1175 } 1176 1177 protected Resource loadFoundResource(List<ValidationMessage> errors, String path, Element resource, Class<? extends Resource> class1) throws FHIRException { 1178 try { 1179 FhirPublication v = FhirPublication.fromCode(context.getVersion()); 1180 ByteArrayOutputStream bs = new ByteArrayOutputStream(); 1181 new JsonParser(context).compose(resource, bs, OutputStyle.NORMAL, resource.getIdBase()); 1182 byte[] json = bs.toByteArray(); 1183 Resource r5 = null; 1184 switch (v) { 1185 case DSTU1: 1186 rule(errors, NO_RULE_DATE, IssueType.INVALID, resource.line(), resource.col(), path, false, I18nConstants.UNSUPPORTED_VERSION_R1, resource.getIdBase()); 1187 return null; // this can't happen 1188 case DSTU2: 1189 org.hl7.fhir.dstu2.model.Resource r2 = new org.hl7.fhir.dstu2.formats.JsonParser().parse(json); 1190 r5 = VersionConvertorFactory_10_50.convertResource(r2); 1191 break; 1192 case DSTU2016May: 1193 org.hl7.fhir.dstu2016may.model.Resource r2a = new org.hl7.fhir.dstu2016may.formats.JsonParser().parse(json); 1194 r5 = VersionConvertorFactory_14_50.convertResource(r2a); 1195 break; 1196 case STU3: 1197 org.hl7.fhir.dstu3.model.Resource r3 = new org.hl7.fhir.dstu3.formats.JsonParser().parse(json); 1198 r5 = VersionConvertorFactory_30_50.convertResource(r3); 1199 break; 1200 case R4: 1201 org.hl7.fhir.r4.model.Resource r4 = new org.hl7.fhir.r4.formats.JsonParser().parse(json); 1202 r5 = VersionConvertorFactory_40_50.convertResource(r4); 1203 break; 1204 case R5: 1205 r5 = new org.hl7.fhir.r5.formats.JsonParser().parse(json); 1206 break; 1207 default: 1208 return null; // this can't happen 1209 } 1210 if (class1.isInstance(r5)) 1211 return (Resource) r5; 1212 else { 1213 rule(errors, NO_RULE_DATE, IssueType.INVALID, resource.line(), resource.col(), path, false, I18nConstants.REFERENCE_REF_WRONGTARGET_LOAD, resource.getIdBase(), class1.toString(), r5.fhirType()); 1214 return null; 1215 } 1216 1217 } catch (IOException e) { 1218 throw new FHIRException(e); 1219 } 1220 } 1221 1222 public void setLevel(ValidationLevel level) { 1223 this.level = level; 1224 } 1225 1226 public ValidationLevel getLevel() { 1227 return level; 1228 } 1229 1230 protected boolean isHL7(Element cr) { 1231 String url = cr.getChildValue("url"); 1232 return url != null && url.contains("hl7"); 1233 } 1234}