001package org.hl7.fhir.validation.instance; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032 033import static org.apache.commons.lang3.StringUtils.isBlank; 034import static org.apache.commons.lang3.StringUtils.isNotBlank; 035 036import java.io.ByteArrayInputStream; 037import java.io.File; 038import java.io.IOException; 039import java.io.InputStream; 040import java.math.BigDecimal; 041import java.nio.charset.StandardCharsets; 042import java.util.ArrayList; 043import java.util.Calendar; 044import java.util.Collection; 045import java.util.HashMap; 046import java.util.HashSet; 047import java.util.List; 048import java.util.Map; 049import java.util.Set; 050import java.util.UUID; 051 052import org.apache.commons.codec.binary.Base64InputStream; 053import org.apache.commons.lang3.NotImplementedException; 054import org.apache.commons.lang3.StringUtils; 055import org.fhir.ucum.Decimal; 056import org.hl7.fhir.exceptions.DefinitionException; 057import org.hl7.fhir.exceptions.FHIRException; 058import org.hl7.fhir.exceptions.PathEngineException; 059import org.hl7.fhir.exceptions.TerminologyServiceException; 060import org.hl7.fhir.r5.conformance.ProfileUtilities; 061import org.hl7.fhir.r5.context.IWorkerContext; 062import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult; 063import org.hl7.fhir.r5.elementmodel.Element; 064import org.hl7.fhir.r5.elementmodel.Element.SpecialElement; 065import org.hl7.fhir.r5.elementmodel.JsonParser; 066import org.hl7.fhir.r5.elementmodel.Manager; 067import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat; 068import org.hl7.fhir.r5.elementmodel.ObjectConverter; 069import org.hl7.fhir.r5.elementmodel.ParserBase; 070import org.hl7.fhir.r5.elementmodel.ParserBase.NamedElement; 071import org.hl7.fhir.r5.elementmodel.ParserBase.ValidationPolicy; 072import org.hl7.fhir.r5.elementmodel.XmlParser; 073import org.hl7.fhir.r5.formats.FormatUtilities; 074import org.hl7.fhir.r5.model.Address; 075import org.hl7.fhir.r5.model.Attachment; 076import org.hl7.fhir.r5.model.Base; 077import org.hl7.fhir.r5.model.BooleanType; 078import org.hl7.fhir.r5.model.CanonicalResource; 079import org.hl7.fhir.r5.model.CanonicalType; 080import org.hl7.fhir.r5.model.CodeSystem; 081import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; 082import org.hl7.fhir.r5.model.CodeableConcept; 083import org.hl7.fhir.r5.model.Coding; 084import org.hl7.fhir.r5.model.ContactPoint; 085import org.hl7.fhir.r5.model.DataType; 086import org.hl7.fhir.r5.model.DateTimeType; 087import org.hl7.fhir.r5.model.DateType; 088import org.hl7.fhir.r5.model.DecimalType; 089import org.hl7.fhir.r5.model.ElementDefinition; 090import org.hl7.fhir.r5.model.ElementDefinition.AggregationMode; 091import org.hl7.fhir.r5.model.ElementDefinition.ConstraintSeverity; 092import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType; 093import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent; 094import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent; 095import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent; 096import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent; 097import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent; 098import org.hl7.fhir.r5.model.ElementDefinition.PropertyRepresentation; 099import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; 100import org.hl7.fhir.r5.model.Enumeration; 101import org.hl7.fhir.r5.model.Enumerations.BindingStrength; 102import org.hl7.fhir.r5.model.ExpressionNode; 103import org.hl7.fhir.r5.model.Extension; 104import org.hl7.fhir.r5.model.HumanName; 105import org.hl7.fhir.r5.model.Identifier; 106import org.hl7.fhir.r5.model.ImplementationGuide; 107import org.hl7.fhir.r5.model.ImplementationGuide.ImplementationGuideGlobalComponent; 108import org.hl7.fhir.r5.model.InstantType; 109import org.hl7.fhir.r5.model.IntegerType; 110import org.hl7.fhir.r5.model.Period; 111import org.hl7.fhir.r5.model.PrimitiveType; 112import org.hl7.fhir.r5.model.Quantity; 113import org.hl7.fhir.r5.model.Range; 114import org.hl7.fhir.r5.model.Ratio; 115import org.hl7.fhir.r5.model.Reference; 116import org.hl7.fhir.r5.model.Resource; 117import org.hl7.fhir.r5.model.SampledData; 118import org.hl7.fhir.r5.model.SearchParameter; 119import org.hl7.fhir.r5.model.StringType; 120import org.hl7.fhir.r5.model.StructureDefinition; 121import org.hl7.fhir.r5.model.StructureDefinition.ExtensionContextType; 122import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionContextComponent; 123import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 124import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionMappingComponent; 125import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionSnapshotComponent; 126import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; 127import org.hl7.fhir.r5.model.TimeType; 128import org.hl7.fhir.r5.model.Timing; 129import org.hl7.fhir.r5.model.TypeDetails; 130import org.hl7.fhir.r5.model.UriType; 131import org.hl7.fhir.r5.model.ValueSet; 132import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 133import org.hl7.fhir.r5.renderers.DataRenderer; 134import org.hl7.fhir.r5.terminologies.ValueSetExpander.TerminologyServiceErrorClass; 135import org.hl7.fhir.r5.utils.FHIRLexer.FHIRLexerException; 136import org.hl7.fhir.r5.utils.FHIRPathEngine; 137import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext; 138import org.hl7.fhir.r5.utils.FHIRPathEngine.TypedElementDefinition; 139import org.hl7.fhir.r5.utils.validation.*; 140import org.hl7.fhir.r5.utils.ToolingExtensions; 141import org.hl7.fhir.r5.utils.XVerExtensionManager; 142import org.hl7.fhir.r5.utils.validation.constants.*; 143import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 144import org.hl7.fhir.utilities.SIDUtilities; 145import org.hl7.fhir.utilities.UnicodeUtilities; 146import org.hl7.fhir.utilities.Utilities; 147import org.hl7.fhir.utilities.Utilities.DecimalStatus; 148import org.hl7.fhir.utilities.VersionUtilities; 149import org.hl7.fhir.utilities.VersionUtilities.VersionURLInfo; 150import org.hl7.fhir.utilities.i18n.I18nConstants; 151import org.hl7.fhir.utilities.validation.ValidationMessage; 152import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 153import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 154import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 155import org.hl7.fhir.utilities.validation.ValidationOptions; 156import org.hl7.fhir.utilities.xhtml.NodeType; 157import org.hl7.fhir.utilities.xhtml.XhtmlNode; 158import org.hl7.fhir.validation.BaseValidator; 159import org.hl7.fhir.validation.cli.utils.QuestionnaireMode; 160import org.hl7.fhir.validation.cli.utils.ValidationLevel; 161import org.hl7.fhir.validation.instance.type.BundleValidator; 162import org.hl7.fhir.validation.instance.type.CodeSystemValidator; 163import org.hl7.fhir.validation.instance.type.MeasureValidator; 164import org.hl7.fhir.validation.instance.type.QuestionnaireValidator; 165import org.hl7.fhir.validation.instance.type.SearchParameterValidator; 166import org.hl7.fhir.validation.instance.type.StructureDefinitionValidator; 167import org.hl7.fhir.validation.instance.type.ValueSetValidator; 168import org.hl7.fhir.validation.instance.utils.ChildIterator; 169import org.hl7.fhir.validation.instance.utils.ElementInfo; 170import org.hl7.fhir.validation.instance.utils.IndexedElement; 171import org.hl7.fhir.validation.instance.utils.NodeStack; 172import org.hl7.fhir.validation.instance.utils.ResolvedReference; 173import org.hl7.fhir.validation.instance.utils.ResourceValidationTracker; 174import org.hl7.fhir.validation.instance.utils.ValidatorHostContext; 175import org.w3c.dom.Document; 176 177import com.google.gson.Gson; 178import com.google.gson.JsonObject; 179 180 181/** 182 * Thinking of using this in a java program? Don't! 183 * You should use one of the wrappers instead. Either in HAPI, or use ValidationEngine 184 * <p> 185 * Validation todo: 186 * - support @default slices 187 * 188 * @author Grahame Grieve 189 */ 190/* 191 * todo: 192 * check urn's don't start oid: or uuid: 193 * check MetadataResource.url is absolute 194 */ 195 196public class InstanceValidator extends BaseValidator implements IResourceValidator { 197 private static final String EXECUTED_CONSTRAINT_LIST = "validator.executed.invariant.list"; 198 private static final String EXECUTION_ID = "validator.execution.id"; 199 private static final String HTML_FRAGMENT_REGEX = "[a-zA-Z]\\w*(((\\s+)(\\S)*)*)"; 200 private static final boolean STACK_TRACE = false; 201 202 private class ValidatorHostServices implements IEvaluationContext { 203 204 @Override 205 public List<Base> resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException { 206 ValidatorHostContext c = (ValidatorHostContext) appContext; 207 if (externalHostServices != null) 208 return externalHostServices.resolveConstant(c.getAppContext(), name, beforeContext); 209 else 210 return new ArrayList<Base>(); 211 } 212 213 @Override 214 public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException { 215 ValidatorHostContext c = (ValidatorHostContext) appContext; 216 if (externalHostServices != null) 217 return externalHostServices.resolveConstantType(c.getAppContext(), name); 218 else 219 return null; 220 } 221 222 @Override 223 public boolean log(String argument, List<Base> focus) { 224 if (externalHostServices != null) 225 return externalHostServices.log(argument, focus); 226 else 227 return false; 228 } 229 230 @Override 231 public FunctionDetails resolveFunction(String functionName) { 232 throw new Error(context.formatMessage(I18nConstants.NOT_DONE_YET_VALIDATORHOSTSERVICESRESOLVEFUNCTION_, functionName)); 233 } 234 235 @Override 236 public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) throws PathEngineException { 237 throw new Error(context.formatMessage(I18nConstants.NOT_DONE_YET_VALIDATORHOSTSERVICESCHECKFUNCTION)); 238 } 239 240 @Override 241 public List<Base> executeFunction(Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) { 242 throw new Error(context.formatMessage(I18nConstants.NOT_DONE_YET_VALIDATORHOSTSERVICESEXECUTEFUNCTION)); 243 } 244 245 @Override 246 public Base resolveReference(Object appContext, String url, Base refContext) throws FHIRException { 247 ValidatorHostContext c = (ValidatorHostContext) appContext; 248 249 if (refContext != null && refContext.hasUserData("validator.bundle.resolution")) { 250 return (Base) refContext.getUserData("validator.bundle.resolution"); 251 } 252 253 if (c.getAppContext() instanceof Element) { 254 Element element = (Element) c.getAppContext(); 255 while (element != null) { 256 Base res = resolveInBundle(url, element); 257 if (res != null) { 258 return res; 259 } 260 element = element.getParentForValidator(); 261 } 262 } 263 Base res = resolveInBundle(url, c.getResource()); 264 if (res != null) { 265 return res; 266 } 267 Element element = c.getRootResource(); 268 while (element != null) { 269 res = resolveInBundle(url, element); 270 if (res != null) { 271 return res; 272 } 273 element = element.getParentForValidator(); 274 } 275 276 if (externalHostServices != null) { 277 return externalHostServices.resolveReference(c.getAppContext(), url, refContext); 278 } else if (fetcher != null) { 279 try { 280 return fetcher.fetch(InstanceValidator.this, c.getAppContext(), url); 281 } catch (IOException e) { 282 throw new FHIRException(e); 283 } 284 } else { 285 throw new Error(context.formatMessage(I18nConstants.NOT_DONE_YET__RESOLVE__LOCALLY_2, url)); 286 } 287 } 288 289 290 @Override 291 public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException { 292 ValidatorHostContext ctxt = (ValidatorHostContext) appContext; 293 StructureDefinition sd = context.fetchResource(StructureDefinition.class, url); 294 if (sd == null) { 295 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_, url)); 296 } 297 InstanceValidator self = InstanceValidator.this; 298 List<ValidationMessage> valerrors = new ArrayList<ValidationMessage>(); 299 if (item instanceof Resource) { 300 try { 301 Element e = new ObjectConverter(context).convert((Resource) item); 302 setParents(e); 303 self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage)); 304 } catch (IOException e1) { 305 throw new FHIRException(e1); 306 } 307 } else if (item instanceof Element) { 308 Element e = (Element) item; 309 if (e.getSpecial() == SpecialElement.CONTAINED) { 310 self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e, ctxt.getRootResource(), ctxt.getGroupingResource()), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage)); 311 } else if (e.getSpecial() != null) { 312 self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e, e, ctxt.getRootResource()), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage)); 313 } else { 314 self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage)); 315 } 316 } else 317 throw new NotImplementedException(context.formatMessage(I18nConstants.NOT_DONE_YET_VALIDATORHOSTSERVICESCONFORMSTOPROFILE_WHEN_ITEM_IS_NOT_AN_ELEMENT)); 318 boolean ok = true; 319 List<ValidationMessage> record = new ArrayList<>(); 320 for (ValidationMessage v : valerrors) { 321 ok = ok && !v.getLevel().isError(); 322 if (v.getLevel().isError() || v.isSlicingHint()) { 323 record.add(v); 324 } 325 } 326 if (!ok && !record.isEmpty()) { 327 ctxt.sliceNotes(url, record); 328 } 329 return ok; 330 } 331 332 @Override 333 public ValueSet resolveValueSet(Object appContext, String url) { 334 ValidatorHostContext c = (ValidatorHostContext) appContext; 335 if (c.getProfile() != null && url.startsWith("#")) { 336 for (Resource r : c.getProfile().getContained()) { 337 if (r.getId().equals(url.substring(1))) { 338 if (r instanceof ValueSet) 339 return (ValueSet) r; 340 else 341 throw new FHIRException(context.formatMessage(I18nConstants.REFERENCE__REFERS_TO_A__NOT_A_VALUESET, url, r.fhirType())); 342 } 343 } 344 return null; 345 } 346 return context.fetchResource(ValueSet.class, url); 347 } 348 349 } 350 private FHIRPathEngine fpe; 351 352 public FHIRPathEngine getFHIRPathEngine() { 353 return fpe; 354 } 355 356 // configuration items 357 private CheckDisplayOption checkDisplay; 358 private boolean anyExtensionsAllowed; 359 private boolean errorForUnknownProfiles; 360 private boolean noInvariantChecks; 361 private boolean wantInvariantInMessage; 362 private boolean noTerminologyChecks; 363 private boolean hintAboutNonMustSupport; 364 private boolean showMessagesFromReferences; 365 private BestPracticeWarningLevel bpWarnings; 366 private String validationLanguage; 367 private boolean baseOnly; 368 private boolean noCheckAggregation; 369 private boolean wantCheckSnapshotUnchanged; 370 private boolean noUnicodeBiDiControlChars; 371 372 private List<ImplementationGuide> igs = new ArrayList<>(); 373 private List<String> extensionDomains = new ArrayList<String>(); 374 375 private IdStatus resourceIdRule; 376 private boolean allowXsiLocation; 377 378 // used during the build process to keep the overall volume of messages down 379 private boolean suppressLoincSnomedMessages; 380 381 // time tracking 382 private boolean noBindingMsgSuppressed; 383 private boolean debug; 384 private Map<String, Element> fetchCache = new HashMap<>(); 385 private HashMap<Element, ResourceValidationTracker> resourceTracker = new HashMap<>(); 386 private IValidatorResourceFetcher fetcher; 387 private IValidationPolicyAdvisor policyAdvisor; 388 long time = 0; 389 private IEvaluationContext externalHostServices; 390 private boolean noExtensibleWarnings; 391 private String serverBase; 392 393 private EnableWhenEvaluator myEnableWhenEvaluator = new EnableWhenEvaluator(); 394 private String executionId; 395 private IValidationProfileUsageTracker tracker; 396 private ValidatorHostServices validatorServices; 397 private boolean assumeValidRestReferences; 398 private boolean allowExamples; 399 private boolean securityChecks; 400 private ProfileUtilities profileUtilities; 401 private boolean crumbTrails; 402 private List<BundleValidationRule> bundleValidationRules = new ArrayList<>(); 403 private boolean validateValueSetCodesOnTxServer = true; 404 private QuestionnaireMode questionnaireMode; 405 private ValidationOptions baseOptions = new ValidationOptions(); 406 407 public InstanceValidator(IWorkerContext theContext, IEvaluationContext hostServices, XVerExtensionManager xverManager) { 408 super(theContext, xverManager); 409 this.externalHostServices = hostServices; 410 this.profileUtilities = new ProfileUtilities(theContext, null, null); 411 fpe = new FHIRPathEngine(context); 412 validatorServices = new ValidatorHostServices(); 413 fpe.setHostServices(validatorServices); 414 if (theContext.getVersion().startsWith("3.0") || theContext.getVersion().startsWith("1.0")) 415 fpe.setLegacyMode(true); 416 source = Source.InstanceValidator; 417 } 418 419 @Override 420 public boolean isNoExtensibleWarnings() { 421 return noExtensibleWarnings; 422 } 423 424 @Override 425 public IResourceValidator setNoExtensibleWarnings(boolean noExtensibleWarnings) { 426 this.noExtensibleWarnings = noExtensibleWarnings; 427 return this; 428 } 429 430 @Override 431 public boolean isShowMessagesFromReferences() { 432 return showMessagesFromReferences; 433 } 434 435 @Override 436 public void setShowMessagesFromReferences(boolean showMessagesFromReferences) { 437 this.showMessagesFromReferences = showMessagesFromReferences; 438 } 439 440 @Override 441 public boolean isNoInvariantChecks() { 442 return noInvariantChecks; 443 } 444 445 @Override 446 public IResourceValidator setNoInvariantChecks(boolean value) { 447 this.noInvariantChecks = value; 448 return this; 449 } 450 451 @Override 452 public boolean isWantInvariantInMessage() { 453 return wantInvariantInMessage; 454 } 455 456 @Override 457 public IResourceValidator setWantInvariantInMessage(boolean wantInvariantInMessage) { 458 this.wantInvariantInMessage = wantInvariantInMessage; 459 return this; 460 } 461 462 public IValidatorResourceFetcher getFetcher() { 463 return this.fetcher; 464 } 465 466 public IResourceValidator setFetcher(IValidatorResourceFetcher value) { 467 this.fetcher = value; 468 return this; 469 } 470 471 @Override 472 public IValidationPolicyAdvisor getPolicyAdvisor() { 473 return policyAdvisor; 474 } 475 476 @Override 477 public IResourceValidator setPolicyAdvisor(IValidationPolicyAdvisor advisor) { 478 this.policyAdvisor = advisor; 479 return this; 480 } 481 482 public IValidationProfileUsageTracker getTracker() { 483 return this.tracker; 484 } 485 486 public IResourceValidator setTracker(IValidationProfileUsageTracker value) { 487 this.tracker = value; 488 return this; 489 } 490 491 492 public boolean isHintAboutNonMustSupport() { 493 return hintAboutNonMustSupport; 494 } 495 496 public void setHintAboutNonMustSupport(boolean hintAboutNonMustSupport) { 497 this.hintAboutNonMustSupport = hintAboutNonMustSupport; 498 } 499 500 public boolean isAssumeValidRestReferences() { 501 return this.assumeValidRestReferences; 502 } 503 504 public void setAssumeValidRestReferences(boolean value) { 505 this.assumeValidRestReferences = value; 506 } 507 508 public boolean isAllowExamples() { 509 return this.allowExamples; 510 } 511 512 public void setAllowExamples(boolean value) { 513 this.allowExamples = value; 514 } 515 516 public boolean isCrumbTrails() { 517 return crumbTrails; 518 } 519 520 public void setCrumbTrails(boolean crumbTrails) { 521 this.crumbTrails = crumbTrails; 522 } 523 524 private boolean allowUnknownExtension(String url) { 525 if ((allowExamples && (url.contains("example.org") || url.contains("acme.com"))) || url.contains("nema.org") || url.startsWith("http://hl7.org/fhir/tools/StructureDefinition/") || url.equals("http://hl7.org/fhir/StructureDefinition/structuredefinition-expression")) 526 // Added structuredefinition-expression explicitly because it wasn't defined in the version of the spec it needs to be used with 527 return true; 528 for (String s : extensionDomains) 529 if (url.startsWith(s)) 530 return true; 531 return anyExtensionsAllowed; 532 } 533 534 private boolean isKnownExtension(String url) { 535 // Added structuredefinition-expression and following extensions explicitly because they weren't defined in the version of the spec they need to be used with 536 if ((allowExamples && (url.contains("example.org") || url.contains("acme.com"))) || url.contains("nema.org") || 537 url.startsWith("http://hl7.org/fhir/tools/StructureDefinition/") || url.equals("http://hl7.org/fhir/StructureDefinition/structuredefinition-expression") || 538 url.equals("http://hl7.org/fhir/StructureDefinition/codesystem-properties-mode")) 539 return true; 540 for (String s : extensionDomains) 541 if (url.startsWith(s)) 542 return true; 543 return false; 544 } 545 546 private void bpCheck(List<ValidationMessage> errors, IssueType invalid, int line, int col, String literalPath, boolean test, String message, Object... theMessageArguments) { 547 if (bpWarnings != null) { 548 switch (bpWarnings) { 549 case Error: 550 rule(errors, invalid, line, col, literalPath, test, message, theMessageArguments); 551 break; 552 case Warning: 553 warning(errors, invalid, line, col, literalPath, test, message, theMessageArguments); 554 break; 555 case Hint: 556 hint(errors, invalid, line, col, literalPath, test, message, theMessageArguments); 557 break; 558 default: // do nothing 559 break; 560 } 561 } 562 } 563 564 @Override 565 public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, InputStream stream, FhirFormat format) throws FHIRException { 566 return validate(appContext, errors, stream, format, new ArrayList<>()); 567 } 568 569 @Override 570 public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, InputStream stream, FhirFormat format, String profile) throws FHIRException { 571 ArrayList<StructureDefinition> profiles = new ArrayList<>(); 572 if (profile != null) { 573 profiles.add(getSpecifiedProfile(profile)); 574 } 575 return validate(appContext, errors, stream, format, profiles); 576 } 577 578 private StructureDefinition getSpecifiedProfile(String profile) { 579 StructureDefinition sd = context.fetchResource(StructureDefinition.class, profile); 580 if (sd == null) { 581 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_LOCATE_THE_PROFILE__IN_ORDER_TO_VALIDATE_AGAINST_IT, profile)); 582 } 583 return sd; 584 } 585 586 @Override 587 public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, InputStream stream, FhirFormat format, List<StructureDefinition> profiles) throws FHIRException { 588 ParserBase parser = Manager.makeParser(context, format); 589 if (parser instanceof XmlParser) 590 ((XmlParser) parser).setAllowXsiLocation(allowXsiLocation); 591 parser.setupValidation(ValidationPolicy.EVERYTHING, errors); 592 long t = System.nanoTime(); 593 List<NamedElement> list = null; 594 try { 595 list = parser.parse(stream); 596 } catch (IOException e1) { 597 throw new FHIRException(e1); 598 } 599 timeTracker.load(t); 600 if (list != null && !list.isEmpty()) { 601 String url = parser.getImpliedProfile(); 602 if (url != null) { 603 StructureDefinition sd = context.fetchResource(StructureDefinition.class, url); 604 if (sd == null) { 605 rule(errors, IssueType.NOTFOUND, "Payload", false, "Implied profile "+url+" not known to validator"); 606 } else { 607 profiles.add(sd); 608 } 609 } 610 for (NamedElement ne : list) { 611 validate(appContext, errors, ne.getName(), ne.getElement(), profiles); 612 } 613 } 614 return (list == null || list.isEmpty()) ? null : list.get(0).getElement(); // todo: this is broken, but fixing it really complicates things elsewhere, so we do this for now 615 } 616 617 @Override 618 public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Resource resource) throws FHIRException { 619 return validate(appContext, errors, resource, new ArrayList<>()); 620 } 621 622 @Override 623 public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Resource resource, String profile) throws FHIRException { 624 ArrayList<StructureDefinition> profiles = new ArrayList<>(); 625 if (profile != null) { 626 profiles.add(getSpecifiedProfile(profile)); 627 } 628 return validate(appContext, errors, resource, profiles); 629 } 630 631 @Override 632 public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Resource resource, List<StructureDefinition> profiles) throws FHIRException { 633 long t = System.nanoTime(); 634 Element e; 635 try { 636 e = new ObjectConverter(context).convert(resource); 637 } catch (IOException e1) { 638 throw new FHIRException(e1); 639 } 640 timeTracker.load(t); 641 validate(appContext, errors, null, e, profiles); 642 return e; 643 } 644 645 @Override 646 public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, org.w3c.dom.Element element) throws FHIRException { 647 return validate(appContext, errors, element, new ArrayList<>()); 648 } 649 650 @Override 651 public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, org.w3c.dom.Element element, String profile) throws FHIRException { 652 ArrayList<StructureDefinition> profiles = new ArrayList<>(); 653 if (profile != null) { 654 profiles.add(getSpecifiedProfile(profile)); 655 } 656 return validate(appContext, errors, element, profiles); 657 } 658 659 @Override 660 public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, org.w3c.dom.Element element, List<StructureDefinition> profiles) throws FHIRException { 661 XmlParser parser = new XmlParser(context); 662 parser.setupValidation(ValidationPolicy.EVERYTHING, errors); 663 long t = System.nanoTime(); 664 Element e; 665 try { 666 e = parser.parse(element); 667 } catch (IOException e1) { 668 throw new FHIRException(e1); 669 } 670 timeTracker.load(t); 671 if (e != null) { 672 validate(appContext, errors, null, e, profiles); 673 } 674 return e; 675 } 676 677 @Override 678 public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Document document) throws FHIRException { 679 return validate(appContext, errors, document, new ArrayList<>()); 680 } 681 682 @Override 683 public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Document document, String profile) throws FHIRException { 684 ArrayList<StructureDefinition> profiles = new ArrayList<>(); 685 if (profile != null) { 686 profiles.add(getSpecifiedProfile(profile)); 687 } 688 return validate(appContext, errors, document, profiles); 689 } 690 691 @Override 692 public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Document document, List<StructureDefinition> profiles) throws FHIRException { 693 XmlParser parser = new XmlParser(context); 694 parser.setupValidation(ValidationPolicy.EVERYTHING, errors); 695 long t = System.nanoTime(); 696 Element e; 697 try { 698 e = parser.parse(document); 699 } catch (IOException e1) { 700 throw new FHIRException(e1); 701 } 702 timeTracker.load(t); 703 if (e != null) 704 validate(appContext, errors, null, e, profiles); 705 return e; 706 } 707 708 @Override 709 public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, JsonObject object) throws FHIRException { 710 return validate(appContext, errors, object, new ArrayList<>()); 711 } 712 713 @Override 714 public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, JsonObject object, String profile) throws FHIRException { 715 ArrayList<StructureDefinition> profiles = new ArrayList<>(); 716 if (profile != null) { 717 profiles.add(getSpecifiedProfile(profile)); 718 } 719 return validate(appContext, errors, object, profiles); 720 } 721 722 @Override 723 public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, JsonObject object, List<StructureDefinition> profiles) throws FHIRException { 724 JsonParser parser = new JsonParser(context, new ProfileUtilities(context, null, null, fpe)); 725 parser.setupValidation(ValidationPolicy.EVERYTHING, errors); 726 long t = System.nanoTime(); 727 Element e = parser.parse(object); 728 timeTracker.load(t); 729 if (e != null) 730 validate(appContext, errors, null, e, profiles); 731 return e; 732 } 733 734 @Override 735 public void validate(Object appContext, List<ValidationMessage> errors, String initialPath, Element element) throws FHIRException { 736 validate(appContext, errors, initialPath, element, new ArrayList<>()); 737 } 738 739 @Override 740 public void validate(Object appContext, List<ValidationMessage> errors, String initialPath, Element element, String profile) throws FHIRException { 741 ArrayList<StructureDefinition> profiles = new ArrayList<>(); 742 if (profile != null) { 743 profiles.add(getSpecifiedProfile(profile)); 744 } 745 validate(appContext, errors, initialPath, element, profiles); 746 } 747 748 @Override 749 public void validate(Object appContext, List<ValidationMessage> errors, String path, Element element, List<StructureDefinition> profiles) throws FHIRException { 750 // this is the main entry point; all the other public entry points end up here coming here... 751 // so the first thing to do is to clear the internal state 752 fetchCache.clear(); 753 fetchCache.put(element.fhirType() + "/" + element.getIdBase(), element); 754 resourceTracker.clear(); 755 trackedMessages.clear(); 756 messagesToRemove.clear(); 757 executionId = UUID.randomUUID().toString(); 758 baseOnly = profiles.isEmpty(); 759 setParents(element); 760 761 long t = System.nanoTime(); 762 if (profiles == null || profiles.isEmpty()) { 763 validateResource(new ValidatorHostContext(appContext, element), errors, element, element, null, resourceIdRule, new NodeStack(context, path, element, validationLanguage).resetIds()); 764 } else { 765 for (StructureDefinition defn : profiles) { 766 validateResource(new ValidatorHostContext(appContext, element), errors, element, element, defn, resourceIdRule, new NodeStack(context, path, element, validationLanguage).resetIds()); 767 } 768 } 769 if (hintAboutNonMustSupport) { 770 checkElementUsage(errors, element, new NodeStack(context, path, element, validationLanguage)); 771 } 772 errors.removeAll(messagesToRemove); 773 timeTracker.overall(t); 774 } 775 776 777 private void checkElementUsage(List<ValidationMessage> errors, Element element, NodeStack stack) { 778 String elementUsage = element.getUserString("elementSupported"); 779 hint(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), elementUsage == null || elementUsage.equals("Y"), I18nConstants.MUSTSUPPORT_VAL_MUSTSUPPORT, element.getName(), element.getProperty().getStructure().getUrl()); 780 781 if (element.hasChildren()) { 782 String prevName = ""; 783 int elementCount = 0; 784 for (Element ce : element.getChildren()) { 785 if (ce.getName().equals(prevName)) 786 elementCount++; 787 else { 788 elementCount = 1; 789 prevName = ce.getName(); 790 } 791 checkElementUsage(errors, ce, stack.push(ce, elementCount, null, null)); 792 } 793 } 794 } 795 796 private boolean check(String v1, String v2) { 797 return v1 == null ? Utilities.noString(v1) : v1.equals(v2); 798 } 799 800 private void checkAddress(List<ValidationMessage> errors, String path, Element focus, Address fixed, String fixedSource, boolean pattern) { 801 checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), fixedSource, "use", focus, pattern); 802 checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), fixedSource, "text", focus, pattern); 803 checkFixedValue(errors, path + ".city", focus.getNamedChild("city"), fixed.getCityElement(), fixedSource, "city", focus, pattern); 804 checkFixedValue(errors, path + ".state", focus.getNamedChild("state"), fixed.getStateElement(), fixedSource, "state", focus, pattern); 805 checkFixedValue(errors, path + ".country", focus.getNamedChild("country"), fixed.getCountryElement(), fixedSource, "country", focus, pattern); 806 checkFixedValue(errors, path + ".zip", focus.getNamedChild("zip"), fixed.getPostalCodeElement(), fixedSource, "postalCode", focus, pattern); 807 808 List<Element> lines = new ArrayList<Element>(); 809 focus.getNamedChildren("line", lines); 810 boolean lineSizeCheck; 811 812 if (pattern) { 813 lineSizeCheck = lines.size() >= fixed.getLine().size(); 814 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, lineSizeCheck, I18nConstants.FIXED_TYPE_CHECKS_DT_ADDRESS_LINE, Integer.toString(fixed.getLine().size()), 815 Integer.toString(lines.size()))) { 816 for (int i = 0; i < fixed.getLine().size(); i++) { 817 StringType fixedLine = fixed.getLine().get(i); 818 boolean found = false; 819 List<ValidationMessage> allErrorsFixed = new ArrayList<>(); 820 List<ValidationMessage> errorsFixed = null; 821 for (int j = 0; j < lines.size() && !found; ++j) { 822 errorsFixed = new ArrayList<>(); 823 checkFixedValue(errorsFixed, path + ".line", lines.get(j), fixedLine, fixedSource, "line", focus, pattern); 824 if (!hasErrors(errorsFixed)) { 825 found = true; 826 } else { 827 errorsFixed.stream().filter(t -> t.getLevel().ordinal() >= IssueSeverity.ERROR.ordinal()).forEach(t -> allErrorsFixed.add(t)); 828 } 829 } 830 if (!found) { 831 rule(errorsFixed, IssueType.VALUE, focus.line(), focus.col(), path, false, I18nConstants.PATTERN_CHECK_STRING, fixedLine.getValue(), fixedSource, allErrorsFixed); 832 } 833 } 834 } 835 } else if (!pattern) { 836 lineSizeCheck = lines.size() == fixed.getLine().size(); 837 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, lineSizeCheck, I18nConstants.FIXED_TYPE_CHECKS_DT_ADDRESS_LINE, 838 Integer.toString(fixed.getLine().size()), Integer.toString(lines.size()))) { 839 for (int i = 0; i < lines.size(); i++) { 840 checkFixedValue(errors, path + ".line", lines.get(i), fixed.getLine().get(i), fixedSource, "line", focus, pattern); 841 } 842 } 843 } 844 } 845 846 private void checkAttachment(List<ValidationMessage> errors, String path, Element focus, Attachment fixed, String fixedSource, boolean pattern) { 847 checkFixedValue(errors, path + ".contentType", focus.getNamedChild("contentType"), fixed.getContentTypeElement(), fixedSource, "contentType", focus, pattern); 848 checkFixedValue(errors, path + ".language", focus.getNamedChild("language"), fixed.getLanguageElement(), fixedSource, "language", focus, pattern); 849 checkFixedValue(errors, path + ".data", focus.getNamedChild("data"), fixed.getDataElement(), fixedSource, "data", focus, pattern); 850 checkFixedValue(errors, path + ".url", focus.getNamedChild("url"), fixed.getUrlElement(), fixedSource, "url", focus, pattern); 851 checkFixedValue(errors, path + ".size", focus.getNamedChild("size"), fixed.getSizeElement(), fixedSource, "size", focus, pattern); 852 checkFixedValue(errors, path + ".hash", focus.getNamedChild("hash"), fixed.getHashElement(), fixedSource, "hash", focus, pattern); 853 checkFixedValue(errors, path + ".title", focus.getNamedChild("title"), fixed.getTitleElement(), fixedSource, "title", focus, pattern); 854 } 855 856 // public API 857 private boolean checkCode(List<ValidationMessage> errors, Element element, String path, String code, String system, String version, String display, boolean checkDisplay, NodeStack stack) throws TerminologyServiceException { 858 long t = System.nanoTime(); 859 boolean ss = context.supportsSystem(system); 860 timeTracker.tx(t, "ss "+system); 861 if (ss) { 862 t = System.nanoTime(); 863 ValidationResult s = checkCodeOnServer(stack, code, system, version, display, checkDisplay); 864 timeTracker.tx(t, "vc "+system+"#"+code+" '"+display+"'"); 865 if (s == null) 866 return true; 867 if (s.isOk()) { 868 if (s.getMessage() != null) 869 txWarning(errors, s.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, s == null, I18nConstants.TERMINOLOGY_PASSTHROUGH_TX_MESSAGE, s.getMessage(), system, code); 870 return true; 871 } 872 if (s.getErrorClass() != null && s.getErrorClass().isInfrastructure()) 873 txWarning(errors, s.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage()); 874 else if (s.getSeverity() == IssueSeverity.INFORMATION) 875 txHint(errors, s.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage()); 876 else if (s.getSeverity() == IssueSeverity.WARNING) 877 txWarning(errors, s.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage()); 878 else 879 return txRule(errors, s.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, s == null, I18nConstants.TERMINOLOGY_PASSTHROUGH_TX_MESSAGE, s.getMessage(), system, code); 880 return true; 881 } else if (system.startsWith("http://build.fhir.org") || system.startsWith("https://build.fhir.org")) { 882 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_WRONG_BUILD, system, suggestSystemForBuild(system)); 883 return false; 884 } else if (system.startsWith("http://hl7.org/fhir") || system.startsWith("https://hl7.org/fhir") || system.startsWith("http://www.hl7.org/fhir") || system.startsWith("https://www.hl7.org/fhir")) { 885 if (SIDUtilities.isknownCodeSystem(system)) { 886 return true; // else don't check these (for now) 887 } else if (system.startsWith("http://hl7.org/fhir/test")) { 888 return true; // we don't validate these 889 } else if (system.endsWith(".html")) { 890 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_WRONG_HTML, system, suggestSystemForPage(system)); 891 return false; 892 } else { 893 CodeSystem cs = getCodeSystem(system); 894 if (rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, cs != null, I18nConstants.TERMINOLOGY_TX_SYSTEM_UNKNOWN, system)) { 895 ConceptDefinitionComponent def = getCodeDefinition(cs, code); 896 if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, def != null, I18nConstants.TERMINOLOGY_TX_CODE_UNKNOWN, system, code)) 897 return warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, display == null || display.equals(def.getDisplay()), I18nConstants.TERMINOLOGY_TX_DISPLAY_WRONG, def.getDisplay()); 898 } 899 return false; 900 } 901 } else if (context.isNoTerminologyServer() && Utilities.existsInList(system, "http://loinc.org", "http://unitsofmeasure.org", "http://hl7.org/fhir/sid/icd-9-cm", "http://snomed.info/sct", "http://www.nlm.nih.gov/research/umls/rxnorm")) { 902 return true; // no checks in this case 903 } else if (startsWithButIsNot(system, "http://snomed.info/sct", "http://loinc.org", "http://unitsofmeasure.org", "http://www.nlm.nih.gov/research/umls/rxnorm")) { 904 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_INVALID, system); 905 return false; 906 } else { 907 try { 908 if (context.fetchResourceWithException(ValueSet.class, system) != null) { 909 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_VALUESET, system); 910 // Lloyd: This error used to prohibit checking for downstream issues, but there are some cases where that checking needs to occur. Please talk to me before changing the code back. 911 } 912 boolean done = false; 913 if (system.startsWith("https:") && system.length() > 7) { 914 String ns = "http:"+system.substring(6); 915 CodeSystem cs = getCodeSystem(ns); 916 if (cs != null || Utilities.existsInList(system, "https://loinc.org", "https://unitsofmeasure.org", "https://snomed.info/sct", "https://www.nlm.nih.gov/research/umls/rxnorm")) { 917 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_HTTPS, system); 918 done = true; 919 } 920 } 921 hint(errors, IssueType.UNKNOWN, element.line(), element.col(), path, done, I18nConstants.TERMINOLOGY_TX_SYSTEM_NOTKNOWN, system); 922 return true; 923 } catch (Exception e) { 924 return true; 925 } 926 } 927 } 928 929 private Object suggestSystemForPage(String system) { 930 if (system.contains("/codesystem-")) { 931 String s = system.substring(system.indexOf("/codesystem-")+12); 932 String url = "http://hl7.org/fhir/"+s.replace(".html", ""); 933 if (context.fetchCodeSystem(url) != null) { 934 return url; 935 } else { 936 return "{unable to determine intended url}"; 937 } 938 } 939 if (system.contains("/valueset-")) { 940 String s = system.substring(system.indexOf("/valueset-")+8); 941 String url = "http://hl7.org/fhir/"+s.replace(".html", ""); 942 if (context.fetchCodeSystem(url) != null) { 943 return url; 944 } else { 945 return "{unable to determine intended url}"; 946 } 947 } 948 return "{unable to determine intended url}"; 949 } 950 951 private Object suggestSystemForBuild(String system) { 952 if (system.contains("/codesystem-")) { 953 String s = system.substring(system.indexOf("/codesystem-")+12); 954 String url = "http://hl7.org/fhir/"+s.replace(".html", ""); 955 if (context.fetchCodeSystem(url) != null) { 956 return url; 957 } else { 958 return "{unable to determine intended url}"; 959 } 960 } 961 if (system.contains("/valueset-")) { 962 String s = system.substring(system.indexOf("/valueset-")+8); 963 String url = "http://hl7.org/fhir/"+s.replace(".html", ""); 964 if (context.fetchCodeSystem(url) != null) { 965 return url; 966 } else { 967 return "{unable to determine intended url}"; 968 } 969 } 970 system = system.replace("https://", "http://"); 971 if (system.length() < 22) { 972 return "{unable to determine intended url}"; 973 } 974 system = "http://hl7.org/fhir/"+system.substring(22).replace(".html", ""); 975 if (context.fetchCodeSystem(system) != null) { 976 return system; 977 } else { 978 return "{unable to determine intended url}"; 979 } 980 } 981 982 private boolean startsWithButIsNot(String system, String... uri) { 983 for (String s : uri) 984 if (!system.equals(s) && system.startsWith(s)) 985 return true; 986 return false; 987 } 988 989 990 private boolean hasErrors(List<ValidationMessage> errors) { 991 if (errors != null) { 992 for (ValidationMessage vm : errors) { 993 if (vm.getLevel() == IssueSeverity.FATAL || vm.getLevel() == IssueSeverity.ERROR) { 994 return true; 995 } 996 } 997 } 998 return false; 999 } 1000 1001 private void checkCodeableConcept(List<ValidationMessage> errors, String path, Element focus, CodeableConcept fixed, String fixedSource, boolean pattern) { 1002 checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), fixedSource, "text", focus, pattern); 1003 List<Element> codings = new ArrayList<Element>(); 1004 focus.getNamedChildren("coding", codings); 1005 if (pattern) { 1006 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, codings.size() >= fixed.getCoding().size(), I18nConstants.TERMINOLOGY_TX_CODING_COUNT, Integer.toString(fixed.getCoding().size()), Integer.toString(codings.size()))) { 1007 for (int i = 0; i < fixed.getCoding().size(); i++) { 1008 Coding fixedCoding = fixed.getCoding().get(i); 1009 boolean found = false; 1010 List<ValidationMessage> allErrorsFixed = new ArrayList<>(); 1011 List<ValidationMessage> errorsFixed; 1012 for (int j = 0; j < codings.size() && !found; ++j) { 1013 errorsFixed = new ArrayList<>(); 1014 checkFixedValue(errorsFixed, path + ".coding", codings.get(j), fixedCoding, fixedSource, "coding", focus, pattern); 1015 if (!hasErrors(errorsFixed)) { 1016 found = true; 1017 } else { 1018 errorsFixed 1019 .stream() 1020 .filter(t -> t.getLevel().ordinal() >= IssueSeverity.ERROR.ordinal()) 1021 .forEach(t -> allErrorsFixed.add(t)); 1022 } 1023 } 1024 if (!found) { 1025 // The argonaut DSTU2 labs profile requires userSelected=false on the category.coding and this 1026 // needs to produce an understandable error message 1027// String message = "Expected CodeableConcept " + (pattern ? "pattern" : "fixed value") + " not found for" + 1028// " system: " + fixedCoding.getSystemElement().asStringValue() + 1029// " code: " + fixedCoding.getCodeElement().asStringValue() + 1030// " display: " + fixedCoding.getDisplayElement().asStringValue(); 1031// if (fixedCoding.hasUserSelected()) { 1032// message += " userSelected: " + fixedCoding.getUserSelected(); 1033// } 1034// message += " - Issues: " + allErrorsFixed; 1035// TYPE_CHECKS_PATTERN_CC = The pattern [system {0}, code {1}, and display "{2}"] defined in the profile {3} not found. Issues: {4} 1036// TYPE_CHECKS_PATTERN_CC_US = The pattern [system {0}, code {1}, display "{2}" and userSelected {5}] defined in the profile {3} not found. Issues: {4} 1037// TYPE_CHECKS_FIXED_CC = The pattern [system {0}, code {1}, and display "{2}"] defined in the profile {3} not found. Issues: {4} 1038// TYPE_CHECKS_FIXED_CC_US = The pattern [system {0}, code {1}, display "{2}" and userSelected {5}] defined in the profile {3} not found. Issues: {4} 1039 1040 if (fixedCoding.hasUserSelected()) { 1041 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, false, pattern ? I18nConstants.TYPE_CHECKS_PATTERN_CC_US : I18nConstants.TYPE_CHECKS_FIXED_CC_US, 1042 fixedCoding.getSystemElement().asStringValue(), fixedCoding.getCodeElement().asStringValue(), fixedCoding.getDisplayElement().asStringValue(), 1043 fixedSource, allErrorsFixed, fixedCoding.getUserSelected()); 1044 1045 } else { 1046 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, false, pattern ? I18nConstants.TYPE_CHECKS_PATTERN_CC : I18nConstants.TYPE_CHECKS_FIXED_CC, 1047 fixedCoding.getSystemElement().asStringValue(), fixedCoding.getCodeElement().asStringValue(), fixedCoding.getDisplayElement().asStringValue(), 1048 fixedSource, allErrorsFixed); 1049 } 1050 } 1051 } 1052 } 1053 } else { 1054 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, codings.size() == fixed.getCoding().size(), I18nConstants.TERMINOLOGY_TX_CODING_COUNT, Integer.toString(fixed.getCoding().size()), Integer.toString(codings.size()))) { 1055 for (int i = 0; i < codings.size(); i++) 1056 checkFixedValue(errors, path + ".coding", codings.get(i), fixed.getCoding().get(i), fixedSource, "coding", focus, false); 1057 } 1058 } 1059 } 1060 1061 private boolean checkCodeableConcept(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, NodeStack stack) { 1062 boolean res = true; 1063 if (!noTerminologyChecks && theElementCntext != null && theElementCntext.hasBinding()) { 1064 ElementDefinitionBindingComponent binding = theElementCntext.getBinding(); 1065 if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, I18nConstants.TERMINOLOGY_TX_BINDING_MISSING, path)) { 1066 if (binding.hasValueSet()) { 1067 ValueSet valueset = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl()); 1068 if (valueset == null) { 1069 CodeSystem cs = context.fetchCodeSystem(binding.getValueSet()); 1070 if (rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(binding.getValueSet()))) { 1071 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(binding.getValueSet())); 1072 } 1073 } else { 1074 try { 1075 CodeableConcept cc = ObjectConverter.readAsCodeableConcept(element); 1076 if (!cc.hasCoding()) { 1077 if (binding.getStrength() == BindingStrength.REQUIRED) 1078 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESET, describeValueSet(binding.getValueSet())); 1079 else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { 1080 if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) 1081 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESETMAX, describeReference(ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")), valueset.getUrl()); 1082 else 1083 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESET_EXT, describeValueSet(binding.getValueSet())); 1084 } 1085 } else { 1086 long t = System.nanoTime(); 1087 1088 // Check whether the codes are appropriate for the type of binding we have 1089 boolean bindingsOk = true; 1090 if (binding.getStrength() != BindingStrength.EXAMPLE) { 1091 if (binding.getStrength() == BindingStrength.REQUIRED) { 1092 removeTrackedMessagesForLocation(errors, element, path); 1093 } 1094 boolean atLeastOneSystemIsSupported = false; 1095 for (Coding nextCoding : cc.getCoding()) { 1096 String nextSystem = nextCoding.getSystem(); 1097 if (isNotBlank(nextSystem) && context.supportsSystem(nextSystem)) { 1098 atLeastOneSystemIsSupported = true; 1099 break; 1100 } 1101 } 1102 1103 if (!atLeastOneSystemIsSupported && binding.getStrength() == BindingStrength.EXAMPLE) { 1104 // ignore this since we can't validate but it doesn't matter.. 1105 } else { 1106 ValidationResult vr = checkCodeOnServer(stack, valueset, cc, true); // we're going to validate the codings directly, so only check the valueset 1107 if (!vr.isOk()) { 1108 bindingsOk = false; 1109 if (vr.getErrorClass() != null && vr.getErrorClass() == TerminologyServiceErrorClass.NOSERVICE) { 1110 if (binding.getStrength() == BindingStrength.REQUIRED || (binding.getStrength() == BindingStrength.EXTENSIBLE && binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))) { 1111 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOSVC_BOUND_REQ, describeReference(binding.getValueSet())); 1112 } else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { 1113 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOSVC_BOUND_EXT, describeReference(binding.getValueSet())); 1114 } 1115 } else if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) { 1116 if (binding.getStrength() == BindingStrength.REQUIRED) 1117 txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_1_CC, describeReference(binding.getValueSet()), vr.getErrorClass().toString()); 1118 else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { 1119 if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) 1120 checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), cc, stack); 1121 else if (!noExtensibleWarnings) 1122 txWarningForLaterRemoval(element, errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_2_CC, describeReference(binding.getValueSet()), vr.getErrorClass().toString()); 1123 } else if (binding.getStrength() == BindingStrength.PREFERRED) { 1124 if (baseOnly) { 1125 txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_3_CC, describeReference(binding.getValueSet()), vr.getErrorClass().toString()); 1126 } 1127 } 1128 } else { 1129 if (binding.getStrength() == BindingStrength.REQUIRED) { 1130 txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_1_CC, describeValueSet(binding.getValueSet()), ccSummary(cc)); 1131 } else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { 1132 if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) 1133 checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), cc, stack); 1134 if (!noExtensibleWarnings) 1135 txWarningForLaterRemoval(element, errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_2_CC, describeValueSet(binding.getValueSet()), ccSummary(cc)); 1136 } else if (binding.getStrength() == BindingStrength.PREFERRED) { 1137 if (baseOnly) { 1138 txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_3_CC, describeValueSet(binding.getValueSet()), ccSummary(cc)); 1139 } 1140 } 1141 } 1142 } else if (vr.getMessage() != null) { 1143 res = false; 1144 txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage()); 1145 } else { 1146 if (binding.getStrength() == BindingStrength.EXTENSIBLE) { 1147 removeTrackedMessagesForLocation(errors, element, path); 1148 } 1149 res = false; 1150 } 1151 } 1152 // Then, for any codes that are in code systems we are able 1153 // to validate, we'll validate that the codes actually exist 1154 if (bindingsOk) { 1155 for (Coding nextCoding : cc.getCoding()) { 1156 checkBindings(errors, path, element, stack, valueset, nextCoding); 1157 } 1158 } 1159 timeTracker.tx(t, "vc "+DataRenderer.display(context, cc)); 1160 } 1161 } 1162 } catch (Exception e) { 1163 if (STACK_TRACE) e.printStackTrace(); 1164 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT, e.getMessage()); 1165 } 1166 } 1167 } else if (binding.hasValueSet()) { 1168 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_CANTCHECK); 1169 } else if (!noBindingMsgSuppressed) { 1170 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSOURCE, path); 1171 } 1172 } 1173 } 1174 return res; 1175 } 1176 1177 public void checkBindings(List<ValidationMessage> errors, String path, Element element, NodeStack stack, ValueSet valueset, Coding nextCoding) { 1178 if (isNotBlank(nextCoding.getCode()) && isNotBlank(nextCoding.getSystem()) && context.supportsSystem(nextCoding.getSystem())) { 1179 ValidationResult vr = checkCodeOnServer(stack, valueset, nextCoding, false); 1180 if (vr.getSeverity() != null/* && vr.hasMessage()*/) { 1181 if (vr.getSeverity() == IssueSeverity.INFORMATION) { 1182 txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage()); 1183 } else if (vr.getSeverity() == IssueSeverity.WARNING) { 1184 txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage()); 1185 } else { 1186 txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage()); 1187 } 1188 } 1189 } 1190 } 1191 1192 1193 private boolean checkTerminologyCodeableConcept(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, NodeStack stack, StructureDefinition logical) { 1194 boolean res = true; 1195 if (!noTerminologyChecks && theElementCntext != null && theElementCntext.hasBinding()) { 1196 ElementDefinitionBindingComponent binding = theElementCntext.getBinding(); 1197 if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, I18nConstants.TERMINOLOGY_TX_BINDING_MISSING, path)) { 1198 if (binding.hasValueSet()) { 1199 ValueSet valueset = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl()); 1200 if (valueset == null) { 1201 CodeSystem cs = context.fetchCodeSystem(binding.getValueSet()); 1202 if (rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(binding.getValueSet()))) { 1203 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(binding.getValueSet())); 1204 } 1205 } else { 1206 try { 1207 CodeableConcept cc = convertToCodeableConcept(element, logical); 1208 if (!cc.hasCoding()) { 1209 if (binding.getStrength() == BindingStrength.REQUIRED) 1210 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()); 1211 else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { 1212 if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) 1213 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESETMAX, describeReference(ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")), valueset.getUrl()); 1214 else 1215 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESET_EXT, describeValueSet(binding.getValueSet())); 1216 } 1217 } else { 1218 long t = System.nanoTime(); 1219 1220 // Check whether the codes are appropriate for the type of binding we have 1221 boolean bindingsOk = true; 1222 if (binding.getStrength() != BindingStrength.EXAMPLE) { 1223 if (binding.getStrength() == BindingStrength.REQUIRED) { 1224 removeTrackedMessagesForLocation(errors, element, path); 1225 } 1226 1227 boolean atLeastOneSystemIsSupported = false; 1228 for (Coding nextCoding : cc.getCoding()) { 1229 String nextSystem = nextCoding.getSystem(); 1230 if (isNotBlank(nextSystem) && context.supportsSystem(nextSystem)) { 1231 atLeastOneSystemIsSupported = true; 1232 break; 1233 } 1234 } 1235 1236 if (!atLeastOneSystemIsSupported && binding.getStrength() == BindingStrength.EXAMPLE) { 1237 // ignore this since we can't validate but it doesn't matter.. 1238 } else { 1239 ValidationResult vr = checkCodeOnServer(stack, valueset, cc, false); // we're going to validate the codings directly 1240 if (!vr.isOk()) { 1241 bindingsOk = false; 1242 if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) { 1243 if (binding.getStrength() == BindingStrength.REQUIRED) 1244 txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_1_CC, describeReference(binding.getValueSet()), vr.getErrorClass().toString()); 1245 else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { 1246 if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) 1247 checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), cc, stack); 1248 else if (!noExtensibleWarnings) 1249 txWarningForLaterRemoval(element, errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_2_CC, describeReference(binding.getValueSet()), vr.getErrorClass().toString()); 1250 } else if (binding.getStrength() == BindingStrength.PREFERRED) { 1251 if (baseOnly) { 1252 txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_3_CC, describeReference(binding.getValueSet()), vr.getErrorClass().toString()); 1253 } 1254 } 1255 } else { 1256 if (binding.getStrength() == BindingStrength.REQUIRED) 1257 txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_1_CC, describeValueSet(binding.getValueSet()), ccSummary(cc)); 1258 else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { 1259 if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) 1260 checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), cc, stack); 1261 if (!noExtensibleWarnings) 1262 txWarningForLaterRemoval(element, errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_2_CC, describeValueSet(binding.getValueSet()), ccSummary(cc)); 1263 } else if (binding.getStrength() == BindingStrength.PREFERRED) { 1264 if (baseOnly) { 1265 txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_3_CC, describeValueSet(binding.getValueSet()), ccSummary(cc)); 1266 } 1267 } 1268 } 1269 } else if (vr.getMessage() != null) { 1270 res = false; 1271 txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage()); 1272 } else { 1273 res = false; 1274 } 1275 } 1276 // Then, for any codes that are in code systems we are able 1277 // to validate, we'll validate that the codes actually exist 1278 if (bindingsOk) { 1279 for (Coding nextCoding : cc.getCoding()) { 1280 String nextCode = nextCoding.getCode(); 1281 String nextSystem = nextCoding.getSystem(); 1282 String nextVersion = nextCoding.getVersion(); 1283 if (isNotBlank(nextCode) && isNotBlank(nextSystem) && context.supportsSystem(nextSystem)) { 1284 ValidationResult vr = checkCodeOnServer(stack, nextCode, nextSystem, nextVersion, null, false); 1285 if (!vr.isOk()) { 1286 txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_NOTVALID, nextCode, nextSystem); 1287 } 1288 } 1289 } 1290 } 1291 timeTracker.tx(t, DataRenderer.display(context, cc)); 1292 } 1293 } 1294 } catch (Exception e) { 1295 if (STACK_TRACE) e.printStackTrace(); 1296 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT, e.getMessage()); 1297 } 1298 // special case: if the logical model has both CodeableConcept and Coding mappings, we'll also check the first coding. 1299 if (getMapping("http://hl7.org/fhir/terminology-pattern", logical, logical.getSnapshot().getElementFirstRep()).contains("Coding")) { 1300 checkTerminologyCoding(errors, path, element, profile, theElementCntext, true, true, stack, logical); 1301 } 1302 } 1303 } else if (binding.hasValueSet()) { 1304 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_CANTCHECK); 1305 } else if (!noBindingMsgSuppressed) { 1306 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSOURCE, path); 1307 } 1308 } 1309 } 1310 return res; 1311 } 1312 1313 private void checkTerminologyCoding(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept, boolean checkDisplay, NodeStack stack, StructureDefinition logical) { 1314 Coding c = convertToCoding(element, logical); 1315 String code = c.getCode(); 1316 String system = c.getSystem(); 1317 String display = c.getDisplay(); 1318 String version = c.getVersion(); 1319 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, system == null || isCodeSystemReferenceValid(system), I18nConstants.TERMINOLOGY_TX_SYSTEM_RELATIVE); 1320 1321 if (system != null && code != null && !noTerminologyChecks) { 1322 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, !isValueSet(system), I18nConstants.TERMINOLOGY_TX_SYSTEM_VALUESET2, system); 1323 try { 1324 if (checkCode(errors, element, path, code, system, version, display, checkDisplay, stack)) 1325 if (theElementCntext != null && theElementCntext.hasBinding()) { 1326 ElementDefinitionBindingComponent binding = theElementCntext.getBinding(); 1327 if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, I18nConstants.TERMINOLOGY_TX_BINDING_MISSING2, path)) { 1328 if (binding.hasValueSet()) { 1329 ValueSet valueset = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl()); 1330 if (valueset == null) { 1331 CodeSystem cs = context.fetchCodeSystem(binding.getValueSet()); 1332 if (rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(binding.getValueSet()))) { 1333 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(binding.getValueSet())); 1334 } 1335 } else { 1336 try { 1337 long t = System.nanoTime(); 1338 ValidationResult vr = null; 1339 if (binding.getStrength() != BindingStrength.EXAMPLE) { 1340 vr = checkCodeOnServer(stack, valueset, c, true); 1341 } 1342 if (binding.getStrength() == BindingStrength.REQUIRED) { 1343 removeTrackedMessagesForLocation(errors, element, path); 1344 } 1345 1346 timeTracker.tx(t, "vc "+system+"#"+code+" '"+display+"'"); 1347 if (vr != null && !vr.isOk()) { 1348 if (vr.IsNoService()) 1349 txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSERVER); 1350 else if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) { 1351 if (binding.getStrength() == BindingStrength.REQUIRED) 1352 txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_4a, describeReference(binding.getValueSet(), valueset), vr.getMessage(), system+"#"+code); 1353 else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { 1354 if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) 1355 checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), c, stack); 1356 else if (!noExtensibleWarnings) 1357 txWarningForLaterRemoval(element, errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_5, describeReference(binding.getValueSet(), valueset)); 1358 } else if (binding.getStrength() == BindingStrength.PREFERRED) { 1359 if (baseOnly) { 1360 txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_6, describeReference(binding.getValueSet(), valueset)); 1361 } 1362 } 1363 } else if (binding.getStrength() == BindingStrength.REQUIRED) 1364 txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_4, describeReference(binding.getValueSet(), valueset), (vr.getMessage() != null ? " (error message = " + vr.getMessage() + ")" : ""), system+"#"+code); 1365 else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { 1366 if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) 1367 checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), c, stack); 1368 else 1369 txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_5, describeReference(binding.getValueSet(), valueset), (vr.getMessage() != null ? " (error message = " + vr.getMessage() + ")" : ""), system+"#"+code); 1370 } else if (binding.getStrength() == BindingStrength.PREFERRED) { 1371 if (baseOnly) { 1372 txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_6, describeReference(binding.getValueSet(), valueset), (vr.getMessage() != null ? " (error message = " + vr.getMessage() + ")" : ""), system+"#"+code); 1373 } 1374 } 1375 } 1376 } catch (Exception e) { 1377 if (STACK_TRACE) e.printStackTrace(); 1378 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODING1, e.getMessage()); 1379 } 1380 } 1381 } else if (binding.hasValueSet()) { 1382 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_CANTCHECK); 1383 } else if (!inCodeableConcept && !noBindingMsgSuppressed) { 1384 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSOURCE, path); 1385 } 1386 } 1387 } 1388 } catch (Exception e) { 1389 if (STACK_TRACE) e.printStackTrace(); 1390 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODING2, e.getMessage(), e.toString()); 1391 } 1392 } 1393 } 1394 1395 private CodeableConcept convertToCodeableConcept(Element element, StructureDefinition logical) { 1396 CodeableConcept res = new CodeableConcept(); 1397 for (ElementDefinition ed : logical.getSnapshot().getElement()) { 1398 if (Utilities.charCount(ed.getPath(), '.') == 1) { 1399 List<String> maps = getMapping("http://hl7.org/fhir/terminology-pattern", logical, ed); 1400 for (String m : maps) { 1401 String name = tail(ed.getPath()); 1402 List<Element> list = new ArrayList<>(); 1403 element.getNamedChildren(name, list); 1404 if (!list.isEmpty()) { 1405 if ("Coding.code".equals(m)) { 1406 res.getCodingFirstRep().setCode(list.get(0).primitiveValue()); 1407 } else if ("Coding.system[fmt:OID]".equals(m)) { 1408 String oid = list.get(0).primitiveValue(); 1409 String url = context.oid2Uri(oid); 1410 if (url != null) { 1411 res.getCodingFirstRep().setSystem(url); 1412 } else { 1413 res.getCodingFirstRep().setSystem("urn:oid:" + oid); 1414 } 1415 } else if ("Coding.version".equals(m)) { 1416 res.getCodingFirstRep().setVersion(list.get(0).primitiveValue()); 1417 } else if ("Coding.display".equals(m)) { 1418 res.getCodingFirstRep().setDisplay(list.get(0).primitiveValue()); 1419 } else if ("CodeableConcept.text".equals(m)) { 1420 res.setText(list.get(0).primitiveValue()); 1421 } else if ("CodeableConcept.coding".equals(m)) { 1422 StructureDefinition c = context.fetchTypeDefinition(ed.getTypeFirstRep().getCode()); 1423 for (Element e : list) { 1424 res.addCoding(convertToCoding(e, c)); 1425 } 1426 } 1427 } 1428 } 1429 } 1430 } 1431 return res; 1432 } 1433 1434 private Coding convertToCoding(Element element, StructureDefinition logical) { 1435 Coding res = new Coding(); 1436 for (ElementDefinition ed : logical.getSnapshot().getElement()) { 1437 if (Utilities.charCount(ed.getPath(), '.') == 1) { 1438 List<String> maps = getMapping("http://hl7.org/fhir/terminology-pattern", logical, ed); 1439 for (String m : maps) { 1440 String name = tail(ed.getPath()); 1441 List<Element> list = new ArrayList<>(); 1442 element.getNamedChildren(name, list); 1443 if (!list.isEmpty()) { 1444 if ("Coding.code".equals(m)) { 1445 res.setCode(list.get(0).primitiveValue()); 1446 } else if ("Coding.system[fmt:OID]".equals(m)) { 1447 String oid = list.get(0).primitiveValue(); 1448 String url = context.oid2Uri(oid); 1449 if (url != null) { 1450 res.setSystem(url); 1451 } else { 1452 res.setSystem("urn:oid:" + oid); 1453 } 1454 } else if ("Coding.version".equals(m)) { 1455 res.setVersion(list.get(0).primitiveValue()); 1456 } else if ("Coding.display".equals(m)) { 1457 res.setDisplay(list.get(0).primitiveValue()); 1458 } 1459 } 1460 } 1461 } 1462 } 1463 return res; 1464 } 1465 1466 private void checkMaxValueSet(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, String maxVSUrl, CodeableConcept cc, NodeStack stack) { 1467 ValueSet valueset = resolveBindingReference(profile, maxVSUrl, profile.getUrl()); 1468 if (valueset == null) { 1469 CodeSystem cs = context.fetchCodeSystem(maxVSUrl); 1470 if (rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(maxVSUrl))) { 1471 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(maxVSUrl)); 1472 } 1473 } else { 1474 try { 1475 long t = System.nanoTime(); 1476 ValidationResult vr = checkCodeOnServer(stack, valueset, cc, false); 1477 timeTracker.tx(t, "vc "+cc.toString()); 1478 if (!vr.isOk()) { 1479 if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) 1480 txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_7, describeValueSet(maxVSUrl), vr.getMessage()); 1481 else 1482 txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_8, describeValueSet(maxVSUrl), ccSummary(cc)); 1483 } 1484 } catch (Exception e) { 1485 if (STACK_TRACE) e.printStackTrace(); 1486 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT_MAX, e.getMessage()); 1487 } 1488 } 1489 } 1490 1491 private String describeValueSet(String url) { 1492 ValueSet vs = context.fetchResource(ValueSet.class, url); 1493 if (vs != null) { 1494 return "'"+vs.present()+"' ("+url+")"; 1495 } else { 1496 return "("+url+")"; 1497 } 1498 } 1499 1500 private void checkMaxValueSet(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, String maxVSUrl, Coding c, NodeStack stack) { 1501 ValueSet valueset = resolveBindingReference(profile, maxVSUrl, profile.getUrl()); 1502 if (valueset == null) { 1503 CodeSystem cs = context.fetchCodeSystem(maxVSUrl); 1504 if (rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(maxVSUrl))) { 1505 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(maxVSUrl)); 1506 } 1507 } else { 1508 try { 1509 long t = System.nanoTime(); 1510 ValidationResult vr = checkCodeOnServer(stack, valueset, c, true); 1511 timeTracker.tx(t, "vc "+c.getSystem()+"#"+c.getCode()+" '"+c.getDisplay()+"'"); 1512 if (!vr.isOk()) { 1513 if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) 1514 txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_9, describeValueSet(maxVSUrl), vr.getMessage()); 1515 else 1516 txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_10, describeValueSet(maxVSUrl), c.getSystem(), c.getCode()); 1517 } 1518 } catch (Exception e) { 1519 if (STACK_TRACE) e.printStackTrace(); 1520 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT_MAX, e.getMessage()); 1521 } 1522 } 1523 } 1524 1525 private void checkMaxValueSet(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, String maxVSUrl, String value, NodeStack stack) { 1526 ValueSet valueset = resolveBindingReference(profile, maxVSUrl, profile.getUrl()); 1527 if (valueset == null) { 1528 CodeSystem cs = context.fetchCodeSystem(maxVSUrl); 1529 if (rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(maxVSUrl))) { 1530 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(maxVSUrl)); 1531 } 1532 } else { 1533 try { 1534 long t = System.nanoTime(); 1535 ValidationResult vr = checkCodeOnServer(stack, valueset, value, baseOptions.setLanguage(stack.getWorkingLang())); 1536 timeTracker.tx(t, "vc "+value); 1537 if (!vr.isOk()) { 1538 if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) 1539 txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_9, describeValueSet(maxVSUrl), vr.getMessage()); 1540 else 1541 txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_11, describeValueSet(maxVSUrl), vr.getMessage()); 1542 } 1543 } catch (Exception e) { 1544 if (STACK_TRACE) e.printStackTrace(); 1545 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT_MAX, e.getMessage()); 1546 } 1547 } 1548 } 1549 1550 private String ccSummary(CodeableConcept cc) { 1551 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1552 for (Coding c : cc.getCoding()) 1553 b.append(c.getSystem() + "#" + c.getCode()); 1554 return b.toString(); 1555 } 1556 1557 private void checkCoding(List<ValidationMessage> errors, String path, Element focus, Coding fixed, String fixedSource, boolean pattern) { 1558 checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), fixedSource, "system", focus, pattern); 1559 checkFixedValue(errors, path + ".version", focus.getNamedChild("version"), fixed.getVersionElement(), fixedSource, "version", focus, pattern); 1560 checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), fixedSource, "code", focus, pattern); 1561 checkFixedValue(errors, path + ".display", focus.getNamedChild("display"), fixed.getDisplayElement(), fixedSource, "display", focus, pattern); 1562 checkFixedValue(errors, path + ".userSelected", focus.getNamedChild("userSelected"), fixed.getUserSelectedElement(), fixedSource, "userSelected", focus, pattern); 1563 } 1564 1565 private void checkCoding(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept, boolean checkDisplay, NodeStack stack) { 1566 String code = element.getNamedChildValue("code"); 1567 String system = element.getNamedChildValue("system"); 1568 String version = element.getNamedChildValue("version"); 1569 String display = element.getNamedChildValue("display"); 1570 checkCodedElement(errors, path, element, profile, theElementCntext, inCodeableConcept, checkDisplay, stack, code, system, version, display); 1571 } 1572 1573 private void checkCodedElement(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept, boolean checkDisplay, NodeStack stack, 1574 String theCode, String theSystem, String theVersion, String theDisplay) { 1575 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, theSystem == null || isCodeSystemReferenceValid(theSystem), I18nConstants.TERMINOLOGY_TX_SYSTEM_RELATIVE); 1576 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, Utilities.noString(theCode) || !Utilities.noString(theSystem), I18nConstants.TERMINOLOGY_TX_SYSTEM_NO_CODE); 1577 1578 if (theSystem != null && theCode != null && !noTerminologyChecks) { 1579 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, !isValueSet(theSystem), I18nConstants.TERMINOLOGY_TX_SYSTEM_VALUESET2, theSystem); 1580 try { 1581 if (checkCode(errors, element, path, theCode, theSystem, theVersion, theDisplay, checkDisplay, stack)) 1582 if (theElementCntext != null && theElementCntext.hasBinding()) { 1583 ElementDefinitionBindingComponent binding = theElementCntext.getBinding(); 1584 if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, I18nConstants.TERMINOLOGY_TX_BINDING_MISSING2, path)) { 1585 if (binding.hasValueSet()) { 1586 ValueSet valueset = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl()); 1587 if (valueset == null) { 1588 CodeSystem cs = context.fetchCodeSystem(binding.getValueSet()); 1589 if (rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(binding.getValueSet()))) { 1590 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(binding.getValueSet())); 1591 } 1592 } else { 1593 try { 1594 Coding c = ObjectConverter.readAsCoding(element); 1595 long t = System.nanoTime(); 1596 ValidationResult vr = null; 1597 if (binding.getStrength() != BindingStrength.EXAMPLE) { 1598 vr = checkCodeOnServer(stack, valueset, c, true); 1599 } 1600 timeTracker.tx(t, "vc "+c.getSystem()+"#"+c.getCode()+" '"+c.getDisplay()+"'"); 1601 if (binding.getStrength() == BindingStrength.REQUIRED) { 1602 removeTrackedMessagesForLocation(errors, element, path); 1603 } 1604 1605 if (vr != null && !vr.isOk()) { 1606 if (vr.IsNoService()) 1607 txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSERVER); 1608 else if (vr.getErrorClass() != null && !vr.getErrorClass().isInfrastructure()) { 1609 if (binding.getStrength() == BindingStrength.REQUIRED) 1610 txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_4a, describeReference(binding.getValueSet(), valueset), vr.getMessage(), theSystem+"#"+theCode); 1611 else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { 1612 if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) 1613 checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), c, stack); 1614 else if (!noExtensibleWarnings) 1615 txWarningForLaterRemoval(element, errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_5, describeReference(binding.getValueSet(), valueset)); 1616 } else if (binding.getStrength() == BindingStrength.PREFERRED) { 1617 if (baseOnly) { 1618 txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_6, describeReference(binding.getValueSet(), valueset)); 1619 } 1620 } 1621 } else if (binding.getStrength() == BindingStrength.REQUIRED) 1622 txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_12, describeReference(binding.getValueSet(), valueset), getErrorMessage(vr.getMessage()), theSystem+"#"+theCode); 1623 else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { 1624 if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) 1625 checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), c, stack); 1626 else if (!noExtensibleWarnings) { 1627 txWarningForLaterRemoval(element, errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_13, describeReference(binding.getValueSet(), valueset), getErrorMessage(vr.getMessage()), c.getSystem()+"#"+c.getCode()); 1628 } 1629 } else if (binding.getStrength() == BindingStrength.PREFERRED) { 1630 if (baseOnly) { 1631 txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_14, describeReference(binding.getValueSet(), valueset), getErrorMessage(vr.getMessage()), theSystem+"#"+theCode); 1632 } 1633 } 1634 } 1635 } catch (Exception e) { 1636 if (STACK_TRACE) e.printStackTrace(); 1637 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODING1, e.getMessage()); 1638 } 1639 } 1640 } else if (binding.hasValueSet()) { 1641 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_CANTCHECK); 1642 } else if (!inCodeableConcept && !noBindingMsgSuppressed) { 1643 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSOURCE, path); 1644 } 1645 } 1646 } 1647 } catch (Exception e) { 1648 if (STACK_TRACE) e.printStackTrace(); 1649 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODING2, e.getMessage(), e.toString()); 1650 } 1651 } 1652 } 1653 1654 private boolean isValueSet(String url) { 1655 try { 1656 ValueSet vs = context.fetchResourceWithException(ValueSet.class, url); 1657 return vs != null; 1658 } catch (Exception e) { 1659 return false; 1660 } 1661 } 1662 1663 private void checkContactPoint(List<ValidationMessage> errors, String path, Element focus, ContactPoint fixed, String fixedSource, boolean pattern) { 1664 checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), fixedSource, "system", focus, pattern); 1665 checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), fixedSource, "value", focus, pattern); 1666 checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), fixedSource, "use", focus, pattern); 1667 checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), fixedSource, "period", focus, pattern); 1668 1669 } 1670 1671 private StructureDefinition checkExtension(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, Element resource, Element container, Element element, ElementDefinition def, StructureDefinition profile, NodeStack stack, NodeStack containerStack, String extensionUrl) throws FHIRException { 1672 String url = element.getNamedChildValue("url"); 1673 boolean isModifier = element.getName().equals("modifierExtension"); 1674 assert def.getIsModifier() == isModifier; 1675 1676 long t = System.nanoTime(); 1677 StructureDefinition ex = Utilities.isAbsoluteUrl(url) ? context.fetchResource(StructureDefinition.class, url) : null; 1678 timeTracker.sd(t); 1679 if (ex == null) { 1680 ex = getXverExt(errors, path, element, url); 1681 } 1682 if (ex == null) { 1683 if (extensionUrl != null && !isAbsolute(url)) { 1684 if (extensionUrl.equals(profile.getUrl())) { 1685 rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", hasExtensionSlice(profile, url), I18nConstants.EXTENSION_EXT_SUBEXTENSION_INVALID, url, profile.getUrl()); 1686 } 1687 } else if (SpecialExtensions.isKnownExtension(url)) { 1688 ex = SpecialExtensions.getDefinition(url); 1689 } else if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, allowUnknownExtension(url), I18nConstants.EXTENSION_EXT_UNKNOWN_NOTHERE, url)) { 1690 hint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, isKnownExtension(url), I18nConstants.EXTENSION_EXT_UNKNOWN, url); 1691 } 1692 } 1693 if (ex != null) { 1694 trackUsage(ex, hostContext, element); 1695 // check internal definitions are coherent 1696 if (isModifier) { 1697 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", def.getIsModifier() == isModifier, I18nConstants.EXTENSION_EXT_MODIFIER_MISMATCHY); 1698 } else { 1699 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", def.getIsModifier() == isModifier, I18nConstants.EXTENSION_EXT_MODIFIER_MISMATCHN); 1700 } 1701 // two questions 1702 // 1. can this extension be used here? 1703 checkExtensionContext(errors, resource, container, ex, containerStack, hostContext, isModifier); 1704 1705 if (isModifier) 1706 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", ex.getSnapshot().getElement().get(0).getIsModifier(), I18nConstants.EXTENSION_EXT_MODIFIER_Y, url); 1707 else 1708 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", !ex.getSnapshot().getElement().get(0).getIsModifier(), I18nConstants.EXTENSION_EXT_MODIFIER_N, url); 1709 1710 // check the type of the extension: 1711 Set<String> allowedTypes = listExtensionTypes(ex); 1712 String actualType = getExtensionType(element); 1713 if (actualType == null) 1714 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, allowedTypes.isEmpty(), I18nConstants.EXTENSION_EXT_SIMPLE, url); 1715 else 1716 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, allowedTypes.contains(actualType), I18nConstants.EXTENSION_EXT_TYPE, url, allowedTypes.toString(), actualType); 1717 1718 // 3. is the content of the extension valid? 1719 validateElement(hostContext, errors, ex, ex.getSnapshot().getElement().get(0), null, null, resource, element, "Extension", stack, false, true, url); 1720 1721 } 1722 return ex; 1723 } 1724 1725 1726 1727 private boolean hasExtensionSlice(StructureDefinition profile, String sliceName) { 1728 for (ElementDefinition ed : profile.getSnapshot().getElement()) { 1729 if (ed.getPath().equals("Extension.extension.url") && ed.hasFixed() && sliceName.equals(ed.getFixed().primitiveValue())) { 1730 return true; 1731 } 1732 } 1733 return false; 1734 } 1735 1736 private String getExtensionType(Element element) { 1737 for (Element e : element.getChildren()) { 1738 if (e.getName().startsWith("value")) { 1739 String tn = e.getName().substring(5); 1740 String ltn = Utilities.uncapitalize(tn); 1741 if (isPrimitiveType(ltn)) 1742 return ltn; 1743 else 1744 return tn; 1745 } 1746 } 1747 return null; 1748 } 1749 1750 private Set<String> listExtensionTypes(StructureDefinition ex) { 1751 ElementDefinition vd = null; 1752 for (ElementDefinition ed : ex.getSnapshot().getElement()) { 1753 if (ed.getPath().startsWith("Extension.value")) { 1754 vd = ed; 1755 break; 1756 } 1757 } 1758 Set<String> res = new HashSet<String>(); 1759 if (vd != null && !"0".equals(vd.getMax())) { 1760 for (TypeRefComponent tr : vd.getType()) { 1761 res.add(tr.getWorkingCode()); 1762 } 1763 } 1764 return res; 1765 } 1766 1767 private boolean checkExtensionContext(List<ValidationMessage> errors, Element resource, Element container, StructureDefinition definition, NodeStack stack, ValidatorHostContext hostContext, boolean modifier) { 1768 String extUrl = definition.getUrl(); 1769 boolean ok = false; 1770 CommaSeparatedStringBuilder contexts = new CommaSeparatedStringBuilder(); 1771 List<String> plist = new ArrayList<>(); 1772 plist.add(stripIndexes(stack.getLiteralPath())); 1773 for (String s : stack.getLogicalPaths()) { 1774 String p = stripIndexes(s); 1775 // all extensions are always allowed in ElementDefinition.example.value, and in fixed and pattern values. TODO: determine the logical paths from the path stated in the element definition.... 1776 if (Utilities.existsInList(p, "ElementDefinition.example.value", "ElementDefinition.pattern", "ElementDefinition.fixed")) { 1777 return true; 1778 } 1779 plist.add(p); 1780 1781 } 1782 1783 for (StructureDefinitionContextComponent ctxt : fixContexts(extUrl, definition.getContext())) { 1784 if (ok) { 1785 break; 1786 } 1787 if (ctxt.getType() == ExtensionContextType.ELEMENT) { 1788 String en = ctxt.getExpression(); 1789 contexts.append("e:" + en); 1790 if (Utilities.existsInList(en, "Element", "Any")) { 1791 ok = true; 1792 } else if (en.equals("Resource") && container.isResource()) { 1793 ok = true; 1794 } 1795 for (String p : plist) { 1796 if (ok) { 1797 break; 1798 } 1799 if (p.equals(en)) { 1800 ok = true; 1801 } else { 1802 String pn = p; 1803 String pt = ""; 1804 if (p.contains(".")) { 1805 pn = p.substring(0, p.indexOf(".")); 1806 pt = p.substring(p.indexOf(".")); 1807 } 1808 StructureDefinition sd = context.fetchTypeDefinition(pn); 1809 while (sd != null) { 1810 if ((sd.getType() + pt).equals(en)) { 1811 ok = true; 1812 break; 1813 } 1814 if (sd.getBaseDefinition() != null) { 1815 sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 1816 } else { 1817 sd = null; 1818 } 1819 } 1820 } 1821 } 1822 } else if (ctxt.getType() == ExtensionContextType.EXTENSION) { 1823 contexts.append("x:" + ctxt.getExpression()); 1824 NodeStack estack = stack.getParent(); 1825 if (estack != null && estack.getElement().fhirType().equals("Extension")) { 1826 String ext = estack.getElement().getNamedChildValue("url"); 1827 if (ctxt.getExpression().equals(ext)) { 1828 ok = true; 1829 } 1830 } 1831 } else if (ctxt.getType() == ExtensionContextType.FHIRPATH) { 1832 contexts.append("p:" + ctxt.getExpression()); 1833 // The context is all elements that match the FHIRPath query found in the expression. 1834 List<Base> res = fpe.evaluate(hostContext, resource, hostContext.getRootResource(), resource, fpe.parse(ctxt.getExpression())); 1835 if (res.contains(container)) { 1836 ok = true; 1837 } 1838 } else { 1839 throw new Error(context.formatMessage(I18nConstants.UNRECOGNISED_EXTENSION_CONTEXT_, ctxt.getTypeElement().asStringValue())); 1840 } 1841 } 1842 if (!ok) { 1843 if (definition.hasUserData(XVerExtensionManager.XVER_EXT_MARKER)) { 1844 warning(errors, IssueType.STRUCTURE, container.line(), container.col(), stack.getLiteralPath(), false, 1845 modifier ? I18nConstants.EXTENSION_EXTM_CONTEXT_WRONG_XVER : I18nConstants.EXTENSION_EXTP_CONTEXT_WRONG_XVER, extUrl, contexts.toString(), plist.toString()); 1846 } else { 1847 rule(errors, IssueType.STRUCTURE, container.line(), container.col(), stack.getLiteralPath(), false, 1848 modifier ? I18nConstants.EXTENSION_EXTP_CONTEXT_WRONG : I18nConstants.EXTENSION_EXTM_CONTEXT_WRONG, extUrl, contexts.toString(), plist.toString()); 1849 } 1850 return false; 1851 } else { 1852 if (definition.hasContextInvariant()) { 1853 for (StringType s : definition.getContextInvariant()) { 1854 if (!fpe.evaluateToBoolean(hostContext, resource, hostContext.getRootResource(), container, fpe.parse(s.getValue()))) { 1855 if (definition.hasUserData(XVerExtensionManager.XVER_EXT_MARKER)) { 1856 warning(errors, IssueType.STRUCTURE, container.line(), container.col(), stack.getLiteralPath(), false, I18nConstants.PROFILE_EXT_NOT_HERE, extUrl, s.getValue()); 1857 return true; 1858 } else { 1859 rule(errors, IssueType.STRUCTURE, container.line(), container.col(), stack.getLiteralPath(), false, I18nConstants.PROFILE_EXT_NOT_HERE, extUrl, s.getValue()); 1860 return false; 1861 } 1862 } 1863 } 1864 } 1865 return true; 1866 } 1867 } 1868 1869 private List<StructureDefinitionContextComponent> fixContexts(String extUrl, List<StructureDefinitionContextComponent> list) { 1870 List<StructureDefinitionContextComponent> res = new ArrayList<>(); 1871 for (StructureDefinitionContextComponent ctxt : list) { 1872 res.add(ctxt.copy()); 1873 } 1874 if ("http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type".equals(extUrl)) { 1875 list.get(0).setExpression("ElementDefinition.type"); 1876 } 1877 // the history of this is a mess - see https://jira.hl7.org/browse/FHIR-13328 1878 // we in practice we will support it in either place, but the specification says on ElementDefinition, not on ElementDefinition.type 1879 // but this creates validation errors people can't fix all over the place if we don't do this. 1880 if ("http://hl7.org/fhir/StructureDefinition/regex".equals(extUrl)) { 1881 StructureDefinitionContextComponent e = new StructureDefinitionContextComponent(); 1882 e.setExpression("ElementDefinition.type"); 1883 e.setType(ExtensionContextType.ELEMENT); 1884 list.add(e); 1885 } 1886 if ("http://hl7.org/fhir/StructureDefinition/structuredefinition-normative-version".equals(extUrl)) { 1887 list.get(0).setExpression("Element"); // well, it can't be used anywhere but the list of places it can be used is quite long 1888 } 1889 if (!VersionUtilities.isThisOrLater("4.6", context.getVersion())) { 1890 if (Utilities.existsInList(extUrl, "http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation", "http://hl7.org/fhir/StructureDefinition/capabilitystatement-prohibited")) { 1891 list.get(0).setExpression("Element"); // well, they can't be used anywhere but the list of places they can be used is quite long 1892 } 1893 } 1894 return list; 1895 } 1896 1897 private String stripIndexes(String path) { 1898 boolean skip = false; 1899 StringBuilder b = new StringBuilder(); 1900 for (char c : path.toCharArray()) { 1901 if (skip) { 1902 if (c == ']') { 1903 skip = false; 1904 } 1905 } else if (c == '[') { 1906 skip = true; 1907 } else { 1908 b.append(c); 1909 } 1910 } 1911 return b.toString(); 1912 } 1913 1914 @SuppressWarnings("rawtypes") 1915 private void checkFixedValue(List<ValidationMessage> errors, String path, Element focus, org.hl7.fhir.r5.model.Element fixed, String fixedSource, String propName, Element parent, boolean pattern) { 1916 if ((fixed == null || fixed.isEmpty()) && focus == null) { 1917 ; // this is all good 1918 } else if ((fixed == null || fixed.isEmpty()) && focus != null) { 1919 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, pattern, I18nConstants.PROFILE_VAL_NOTALLOWED, focus.getName(), (pattern ? "pattern" : "fixed value")); 1920 } else if (fixed != null && !fixed.isEmpty() && focus == null) { 1921 rule(errors, IssueType.VALUE, parent == null ? -1 : parent.line(), parent == null ? -1 : parent.col(), path, false, I18nConstants.PROFILE_VAL_MISSINGELEMENT, propName, fixedSource); 1922 } else { 1923 String value = focus.primitiveValue(); 1924 if (fixed instanceof org.hl7.fhir.r5.model.BooleanType) 1925 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.BooleanType) fixed).asStringValue(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.BooleanType) fixed).asStringValue()); 1926 else if (fixed instanceof org.hl7.fhir.r5.model.IntegerType) 1927 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.IntegerType) fixed).asStringValue(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.IntegerType) fixed).asStringValue()); 1928 else if (fixed instanceof org.hl7.fhir.r5.model.DecimalType) 1929 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.DecimalType) fixed).asStringValue(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.DecimalType) fixed).asStringValue()); 1930 else if (fixed instanceof org.hl7.fhir.r5.model.Base64BinaryType) 1931 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.Base64BinaryType) fixed).asStringValue(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.Base64BinaryType) fixed).asStringValue()); 1932 else if (fixed instanceof org.hl7.fhir.r5.model.InstantType) 1933 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.InstantType) fixed).getValue().toString(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.InstantType) fixed).asStringValue()); 1934 else if (fixed instanceof org.hl7.fhir.r5.model.CodeType) 1935 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.CodeType) fixed).getValue(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.CodeType) fixed).getValue()); 1936 else if (fixed instanceof org.hl7.fhir.r5.model.Enumeration) 1937 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.Enumeration) fixed).asStringValue(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.Enumeration) fixed).asStringValue()); 1938 else if (fixed instanceof org.hl7.fhir.r5.model.StringType) 1939 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.StringType) fixed).getValue(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.StringType) fixed).getValue()); 1940 else if (fixed instanceof org.hl7.fhir.r5.model.UriType) 1941 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.UriType) fixed).getValue(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.UriType) fixed).getValue()); 1942 else if (fixed instanceof org.hl7.fhir.r5.model.DateType) 1943 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.DateType) fixed).getValue().toString(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.DateType) fixed).getValue()); 1944 else if (fixed instanceof org.hl7.fhir.r5.model.DateTimeType) 1945 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.DateTimeType) fixed).getValue().toString(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.DateTimeType) fixed).getValue()); 1946 else if (fixed instanceof org.hl7.fhir.r5.model.OidType) 1947 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.OidType) fixed).getValue(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.OidType) fixed).getValue()); 1948 else if (fixed instanceof org.hl7.fhir.r5.model.UuidType) 1949 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.UuidType) fixed).getValue(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.UuidType) fixed).getValue()); 1950 else if (fixed instanceof org.hl7.fhir.r5.model.IdType) 1951 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.IdType) fixed).getValue(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.IdType) fixed).getValue()); 1952 else if (fixed instanceof Quantity) 1953 checkQuantity(errors, path, focus, (Quantity) fixed, fixedSource, pattern); 1954 else if (fixed instanceof Address) 1955 checkAddress(errors, path, focus, (Address) fixed, fixedSource, pattern); 1956 else if (fixed instanceof ContactPoint) 1957 checkContactPoint(errors, path, focus, (ContactPoint) fixed, fixedSource, pattern); 1958 else if (fixed instanceof Attachment) 1959 checkAttachment(errors, path, focus, (Attachment) fixed, fixedSource, pattern); 1960 else if (fixed instanceof Identifier) 1961 checkIdentifier(errors, path, focus, (Identifier) fixed, fixedSource, pattern); 1962 else if (fixed instanceof Coding) 1963 checkCoding(errors, path, focus, (Coding) fixed, fixedSource, pattern); 1964 else if (fixed instanceof HumanName) 1965 checkHumanName(errors, path, focus, (HumanName) fixed, fixedSource, pattern); 1966 else if (fixed instanceof CodeableConcept) 1967 checkCodeableConcept(errors, path, focus, (CodeableConcept) fixed, fixedSource, pattern); 1968 else if (fixed instanceof Timing) 1969 checkTiming(errors, path, focus, (Timing) fixed, fixedSource, pattern); 1970 else if (fixed instanceof Period) 1971 checkPeriod(errors, path, focus, (Period) fixed, fixedSource, pattern); 1972 else if (fixed instanceof Range) 1973 checkRange(errors, path, focus, (Range) fixed, fixedSource, pattern); 1974 else if (fixed instanceof Ratio) 1975 checkRatio(errors, path, focus, (Ratio) fixed, fixedSource, pattern); 1976 else if (fixed instanceof SampledData) 1977 checkSampledData(errors, path, focus, (SampledData) fixed, fixedSource, pattern); 1978 else if (fixed instanceof Reference) 1979 checkReference(errors, path, focus, (Reference) fixed, fixedSource, pattern); 1980 1981 else 1982 rule(errors, IssueType.EXCEPTION, focus.line(), focus.col(), path, false, I18nConstants.INTERNAL_INT_BAD_TYPE, fixed.fhirType()); 1983 List<Element> extensions = new ArrayList<Element>(); 1984 focus.getNamedChildren("extension", extensions); 1985 if (fixed.getExtension().size() == 0) { 1986 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, extensions.size() == 0 || pattern == true, I18nConstants.EXTENSION_EXT_FIXED_BANNED); 1987 } else if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, extensions.size() == fixed.getExtension().size(), I18nConstants.EXTENSION_EXT_COUNT_MISMATCH, Integer.toString(fixed.getExtension().size()), Integer.toString(extensions.size()))) { 1988 for (Extension e : fixed.getExtension()) { 1989 Element ex = getExtensionByUrl(extensions, e.getUrl()); 1990 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, ex != null, I18nConstants.EXTENSION_EXT_COUNT_NOTFOUND, e.getUrl())) { 1991 checkFixedValue(errors, path, ex.getNamedChild("extension").getNamedChild("value"), e.getValue(), fixedSource, "extension.value", ex.getNamedChild("extension"), false); 1992 } 1993 } 1994 } 1995 } 1996 } 1997 1998 private void checkHumanName(List<ValidationMessage> errors, String path, Element focus, HumanName fixed, String fixedSource, boolean pattern) { 1999 checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), fixedSource, "use", focus, pattern); 2000 checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), fixedSource, "text", focus, pattern); 2001 checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), fixedSource, "period", focus, pattern); 2002 2003 List<Element> parts = new ArrayList<Element>(); 2004 if (!pattern || fixed.hasFamily()) { 2005 focus.getNamedChildren("family", parts); 2006 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() > 0 == fixed.hasFamily(), I18nConstants.FIXED_TYPE_CHECKS_DT_NAME_FAMILY, (fixed.hasFamily() ? "1" : "0"), Integer.toString(parts.size()))) { 2007 for (int i = 0; i < parts.size(); i++) 2008 checkFixedValue(errors, path + ".family", parts.get(i), fixed.getFamilyElement(), fixedSource, "family", focus, pattern); 2009 } 2010 } 2011 if (!pattern || fixed.hasGiven()) { 2012 focus.getNamedChildren("given", parts); 2013 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getGiven().size(), I18nConstants.FIXED_TYPE_CHECKS_DT_NAME_GIVEN, Integer.toString(fixed.getGiven().size()), Integer.toString(parts.size()))) { 2014 for (int i = 0; i < parts.size(); i++) 2015 checkFixedValue(errors, path + ".given", parts.get(i), fixed.getGiven().get(i), fixedSource, "given", focus, pattern); 2016 } 2017 } 2018 if (!pattern || fixed.hasPrefix()) { 2019 focus.getNamedChildren("prefix", parts); 2020 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getPrefix().size(), I18nConstants.FIXED_TYPE_CHECKS_DT_NAME_PREFIX, Integer.toString(fixed.getPrefix().size()), Integer.toString(parts.size()))) { 2021 for (int i = 0; i < parts.size(); i++) 2022 checkFixedValue(errors, path + ".prefix", parts.get(i), fixed.getPrefix().get(i), fixedSource, "prefix", focus, pattern); 2023 } 2024 } 2025 if (!pattern || fixed.hasSuffix()) { 2026 focus.getNamedChildren("suffix", parts); 2027 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getSuffix().size(), I18nConstants.FIXED_TYPE_CHECKS_DT_NAME_SUFFIX, Integer.toString(fixed.getSuffix().size()), Integer.toString(parts.size()))) { 2028 for (int i = 0; i < parts.size(); i++) 2029 checkFixedValue(errors, path + ".suffix", parts.get(i), fixed.getSuffix().get(i), fixedSource, "suffix", focus, pattern); 2030 } 2031 } 2032 } 2033 2034 private void checkIdentifier(List<ValidationMessage> errors, String path, Element element, ElementDefinition context) { 2035 String system = element.getNamedChildValue("system"); 2036 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, system == null || isIdentifierSystemReferenceValid(system), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_IDENTIFIER_SYSTEM); 2037 if ("urn:ietf:rfc:3986".equals(system)) { 2038 String value = element.getNamedChildValue("value"); 2039 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, value == null || isAbsolute(value), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_IDENTIFIER_IETF_SYSTEM_VALUE); 2040 } 2041 } 2042 2043 private void checkIdentifier(List<ValidationMessage> errors, String path, Element focus, Identifier fixed, String fixedSource, boolean pattern) { 2044 checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), fixedSource, "use", focus, pattern); 2045 checkFixedValue(errors, path + ".type", focus.getNamedChild(TYPE), fixed.getType(), fixedSource, TYPE, focus, pattern); 2046 checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), fixedSource, "system", focus, pattern); 2047 checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), fixedSource, "value", focus, pattern); 2048 checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), fixedSource, "period", focus, pattern); 2049 checkFixedValue(errors, path + ".assigner", focus.getNamedChild("assigner"), fixed.getAssigner(), fixedSource, "assigner", focus, pattern); 2050 } 2051 2052 private void checkPeriod(List<ValidationMessage> errors, String path, Element focus, Period fixed, String fixedSource, boolean pattern) { 2053 checkFixedValue(errors, path + ".start", focus.getNamedChild("start"), fixed.getStartElement(), fixedSource, "start", focus, pattern); 2054 checkFixedValue(errors, path + ".end", focus.getNamedChild("end"), fixed.getEndElement(), fixedSource, "end", focus, pattern); 2055 } 2056 2057 private void checkPrimitive(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, String type, ElementDefinition context, Element e, StructureDefinition profile, NodeStack node) throws FHIRException { 2058 if (isBlank(e.primitiveValue())) { 2059 if (e.primitiveValue() == null) 2060 rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_VALUEEXT); 2061 else if (e.primitiveValue().length() == 0) 2062 rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_NOTEMPTY); 2063 else if (StringUtils.isWhitespace(e.primitiveValue())) 2064 warning(errors, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_WS); 2065 if (context.hasBinding()) { 2066 rule(errors, IssueType.CODEINVALID, e.line(), e.col(), path, context.getBinding().getStrength() != BindingStrength.REQUIRED, I18nConstants.Terminology_TX_Code_ValueSet_MISSING); 2067 } 2068 return; 2069 } else { 2070 boolean hasBiDiControls = UnicodeUtilities.hasBiDiChars(e.primitiveValue()); 2071 if (hasBiDiControls) { 2072 if (rule(errors, IssueType.CODEINVALID, e.line(), e.col(), path, !noUnicodeBiDiControlChars, I18nConstants.UNICODE_BIDI_CONTROLS_CHARS_DISALLOWED, UnicodeUtilities.replaceBiDiChars(e.primitiveValue()))) { 2073 String msg = UnicodeUtilities.checkUnicodeWellFormed(e.primitiveValue()); 2074 warning(errors, IssueType.INVALID, e.line(), e.col(), path, msg == null, I18nConstants.UNICODE_BIDI_CONTROLS_CHARS_MATCH, msg); 2075 } 2076 } 2077 } 2078 String regex = context.getExtensionString(ToolingExtensions.EXT_REGEX); 2079 // there's a messy history here - this extension snhould only be on the element definition itself, but for historical reasons 2080 //( see task 13328) it might also be found on one the types 2081 if (regex != null) { 2082 for (TypeRefComponent tr : context.getType()) { 2083 if (tr.hasExtension(ToolingExtensions.EXT_REGEX)) { 2084 regex = tr.getExtensionString(ToolingExtensions.EXT_REGEX); 2085 break; 2086 } 2087 } 2088 } 2089 if (regex != null) { 2090 rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().matches(regex), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX, e.primitiveValue(), regex); 2091 } 2092 if (!"xhtml".equals(type)) { 2093 if (securityChecks) { 2094 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !containsHtmlTags(e.primitiveValue()), I18nConstants.SECURITY_STRING_CONTENT_ERROR); 2095 } else { 2096 hint(errors, IssueType.INVALID, e.line(), e.col(), path, !containsHtmlTags(e.primitiveValue()), I18nConstants.SECURITY_STRING_CONTENT_WARNING); 2097 } 2098 } 2099 2100 2101 if (type.equals("boolean")) { 2102 rule(errors, IssueType.INVALID, e.line(), e.col(), path, "true".equals(e.primitiveValue()) || "false".equals(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BOOLEAN_VALUE); 2103 } 2104 if (type.equals("uri") || type.equals("oid") || type.equals("uuid") || type.equals("url") || type.equals("canonical")) { 2105 String url = e.primitiveValue(); 2106 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !url.startsWith("oid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URI_OID); 2107 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !url.startsWith("uuid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URI_UUID); 2108 rule(errors, IssueType.INVALID, e.line(), e.col(), path, url.equals(url.trim().replace(" ", "")) 2109 // work around an old invalid example in a core package 2110 || "http://www.acme.com/identifiers/patient or urn:ietf:rfc:3986 if the Identifier.value itself is a full uri".equals(url), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URI_WS, url); 2111 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || url.length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()); 2112 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()); 2113 2114 if (type.equals("oid")) { 2115 rule(errors, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("urn:oid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_OID_START); 2116 } 2117 if (type.equals("uuid")) { 2118 rule(errors, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("urn:uuid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_UUID_STRAT); 2119 } 2120 if (type.equals("canonical")) { 2121 rule(errors, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("#") || Utilities.isAbsoluteUrl(url), I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_ABSOLUTE, url); 2122 } 2123 2124 if (url != null && url.startsWith("urn:uuid:")) { 2125 rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isValidUUID(url.substring(9)), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_UUID_VALID); 2126 } 2127 if (url != null && url.startsWith("urn:oid:")) { 2128 String cc = url.substring(8); 2129 // OIDs shorter than 5 chars are almost never valid for namespaces, except for the special OID 1.3.88 2130 rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isOid(cc) && (cc.lastIndexOf('.') >= 5 || "1.3.88".equals(cc)), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_OID_VALID, cc); 2131 } 2132 2133 if (isCanonicalURLElement(e)) { 2134 // we get to here if this is a defining canonical URL (e.g. CodeSystem.url) 2135 // the URL must be an IRI if present 2136 rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isAbsoluteUrl(url), 2137 node.isContained() ? I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_CONTAINED : I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_ABSOLUTE, url); 2138 } else { 2139 validateReference(hostContext, errors, path, type, context, e, url); 2140 } 2141 } 2142 if (type.equals(ID) && !"Resource.id".equals(context.getBase().getPath())) { 2143 // work around an old issue with ElementDefinition.id 2144 if (!context.getPath().equals("ElementDefinition.id")) { 2145 rule(errors, IssueType.INVALID, e.line(), e.col(), path, FormatUtilities.isValidId(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ID_VALID, e.primitiveValue()); 2146 } 2147 } 2148 if (type.equalsIgnoreCase("string") && e.hasPrimitiveValue()) { 2149 if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || e.primitiveValue().length() > 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_NOTEMPTY)) { 2150 warning(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || e.primitiveValue().trim().equals(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_STRING_WS, prepWSPresentation(e.primitiveValue())); 2151 if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().length() <= 1048576, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_STRING_LENGTH)) { 2152 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()); 2153 } 2154 } 2155 } 2156 if (type.equals("dateTime")) { 2157 warning(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue()); 2158 rule(errors, IssueType.INVALID, e.line(), e.col(), path, 2159 e.primitiveValue() 2160 .matches("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?)?)?"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_VALID, e.primitiveValue()); 2161 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !hasTime(e.primitiveValue()) || hasTimeZone(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_TZ); 2162 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()); 2163 try { 2164 DateTimeType dt = new DateTimeType(e.primitiveValue()); 2165 } catch (Exception ex) { 2166 rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_VALID, ex.getMessage()); 2167 } 2168 } 2169 if (type.equals("time")) { 2170 rule(errors, IssueType.INVALID, e.line(), e.col(), path, 2171 e.primitiveValue() 2172 .matches("([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_TIME_VALID); 2173 try { 2174 TimeType dt = new TimeType(e.primitiveValue()); 2175 } catch (Exception ex) { 2176 rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_TIME_VALID, ex.getMessage()); 2177 } 2178 } 2179 if (type.equals("date")) { 2180 warning(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue()); 2181 rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().matches("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1]))?)?"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATE_VALID); 2182 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()); 2183 try { 2184 DateType dt = new DateType(e.primitiveValue()); 2185 } catch (Exception ex) { 2186 rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATE_VALID, ex.getMessage()); 2187 } 2188 } 2189 if (type.equals("base64Binary")) { 2190 String encoded = e.primitiveValue(); 2191 if (isNotBlank(encoded)) { 2192 boolean ok = isValidBase64(encoded); 2193 if (!ok) { 2194 String value = encoded.length() < 100 ? encoded : "(snip)"; 2195 rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_VALID, value); 2196 } else { 2197 boolean wsok = !base64HasWhitespace(encoded); 2198 if (VersionUtilities.isR5VerOrLater(this.context.getVersion())) { 2199 rule(errors, IssueType.INVALID, e.line(), e.col(), path, wsok, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_ERROR); 2200 } else { 2201 warning(errors, IssueType.INVALID, e.line(), e.col(), path, wsok, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_WARNING); 2202 } 2203 } 2204 if (ok && context.hasExtension("http://hl7.org/fhir/StructureDefinition/maxSize")) { 2205 int size = countBase64DecodedBytes(encoded); 2206 long def = Long.parseLong(ToolingExtensions.readStringExtension(context, "http://hl7.org/fhir/StructureDefinition/maxSize")); 2207 rule(errors, IssueType.STRUCTURE, e.line(), e.col(), path, size <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_TOO_LONG, size, def); 2208 } 2209 2210 } 2211 } 2212 if (type.equals("integer") || type.equals("unsignedInt") || type.equals("positiveInt")) { 2213 if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isInteger(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_VALID, e.primitiveValue())) { 2214 Integer v = new Integer(e.getValue()).intValue(); 2215 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxValueIntegerType() || !context.getMaxValueIntegerType().hasValue() || (context.getMaxValueIntegerType().getValue() >= v), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_GT, (context.hasMaxValueIntegerType() ? context.getMaxValueIntegerType() : "")); 2216 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMinValueIntegerType() || !context.getMinValueIntegerType().hasValue() || (context.getMinValueIntegerType().getValue() <= v), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT, (context.hasMinValueIntegerType() ? context.getMinValueIntegerType() : "")); 2217 if (type.equals("unsignedInt")) 2218 rule(errors, IssueType.INVALID, e.line(), e.col(), path, v >= 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT0); 2219 if (type.equals("positiveInt")) 2220 rule(errors, IssueType.INVALID, e.line(), e.col(), path, v > 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT1); 2221 } 2222 } 2223 if (type.equals("integer64")) { 2224 if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isLong(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER64_VALID, e.primitiveValue())) { 2225 Long v = new Long(e.getValue()).longValue(); 2226 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxValueInteger64Type() || !context.getMaxValueInteger64Type().hasValue() || (context.getMaxValueInteger64Type().getValue() >= v), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_GT, (context.hasMaxValueInteger64Type() ? context.getMaxValueInteger64Type() : "")); 2227 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMinValueInteger64Type() || !context.getMinValueInteger64Type().hasValue() || (context.getMinValueInteger64Type().getValue() <= v), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT, (context.hasMinValueInteger64Type() ? context.getMinValueInteger64Type() : "")); 2228 if (type.equals("unsignedInt")) 2229 rule(errors, IssueType.INVALID, e.line(), e.col(), path, v >= 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT0); 2230 if (type.equals("positiveInt")) 2231 rule(errors, IssueType.INVALID, e.line(), e.col(), path, v > 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT1); 2232 } 2233 } 2234 if (type.equals("decimal")) { 2235 if (e.primitiveValue() != null) { 2236 DecimalStatus ds = Utilities.checkDecimal(e.primitiveValue(), true, false); 2237 if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, ds == DecimalStatus.OK || ds == DecimalStatus.RANGE, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_VALID, e.primitiveValue())) { 2238 warning(errors, IssueType.VALUE, e.line(), e.col(), path, ds != DecimalStatus.RANGE, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_RANGE, e.primitiveValue()); 2239 try { 2240 Decimal v = new Decimal(e.getValue()); 2241 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxValueIntegerType() || 2242 !context.getMaxValueIntegerType().hasValue() || checkDecimalMaxValue(v, context.getMaxValueDecimalType().getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_GT, (context.hasMaxValueIntegerType() ? context.getMaxValueIntegerType() : "")); 2243 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMinValueIntegerType() || 2244 !context.getMinValueIntegerType().hasValue() || checkDecimalMinValue(v, context.getMaxValueDecimalType().getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_LT, (context.hasMinValueIntegerType() ? context.getMinValueIntegerType() : "")); 2245 } catch (Exception ex) { 2246 // should never happen? 2247 } 2248 } 2249 } 2250 if (context.hasExtension("http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces")) { 2251 int dp = e.primitiveValue().contains(".") ? e.primitiveValue().substring(e.primitiveValue().indexOf(".")+1).length() : 0; 2252 int def = Integer.parseInt(ToolingExtensions.readStringExtension(context, "http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces")); 2253 rule(errors, IssueType.STRUCTURE, e.line(), e.col(), path, dp <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_CHARS, dp, def); 2254 } 2255 } 2256 if (type.equals("instant")) { 2257 rule(errors, IssueType.INVALID, e.line(), e.col(), path, 2258 e.primitiveValue().matches("-?[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REGEX, e.primitiveValue()); 2259 warning(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue()); 2260 try { 2261 InstantType dt = new InstantType(e.primitiveValue()); 2262 } catch (Exception ex) { 2263 rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INSTANT_VALID, ex.getMessage()); 2264 } 2265 } 2266 2267 if (type.equals("code") && e.primitiveValue() != null) { 2268 // 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 2269 // other than single spaces in the contents 2270 rule(errors, IssueType.INVALID, e.line(), e.col(), path, passesCodeWhitespaceRules(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CODE_WS, e.primitiveValue()); 2271 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()); 2272 } 2273 2274 if (context.hasBinding() && e.primitiveValue() != null) { 2275 checkPrimitiveBinding(hostContext, errors, path, type, context, e, profile, node); 2276 } 2277 2278 if (type.equals("xhtml")) { 2279 XhtmlNode xhtml = e.getXhtml(); 2280 if (xhtml != null) { // if it is null, this is an error already noted in the parsers 2281 // check that the namespace is there and correct. 2282 String ns = xhtml.getNsDecl(); 2283 rule(errors, IssueType.INVALID, e.line(), e.col(), path, FormatUtilities.XHTML_NS.equals(ns), I18nConstants.XHTML_XHTML_NS_INVALID, ns, FormatUtilities.XHTML_NS); 2284 // check that inner namespaces are all correct 2285 checkInnerNS(errors, e, path, xhtml.getChildNodes()); 2286 rule(errors, IssueType.INVALID, e.line(), e.col(), path, "div".equals(xhtml.getName()), I18nConstants.XHTML_XHTML_NAME_INVALID, ns); 2287 // check that no illegal elements and attributes have been used 2288 checkInnerNames(errors, e, path, xhtml.getChildNodes(), false); 2289 checkUrls(errors, e, path, xhtml.getChildNodes()); 2290 } 2291 } 2292 2293 if (context.hasFixed()) { 2294 checkFixedValue(errors, path, e, context.getFixed(), profile.getUrl(), context.getSliceName(), null, false); 2295 } 2296 if (context.hasPattern()) { 2297 checkFixedValue(errors, path, e, context.getPattern(), profile.getUrl(), context.getSliceName(), null, true); 2298 } 2299 2300 // for nothing to check 2301 } 2302 2303 private Object prepWSPresentation(String s) { 2304 if (Utilities.noString(s)) { 2305 return ""; 2306 } 2307 if (!StringUtils.containsWhitespace(s.trim())) { 2308 return s; 2309 } 2310 int b = 0; 2311 while (Character.isWhitespace(s.charAt(b))) { 2312 b++; 2313 } 2314 while (!Character.isWhitespace(s.charAt(b))) { 2315 b++; 2316 } 2317 int e = s.length() - 1; 2318 while (Character.isWhitespace(s.charAt(e))) { 2319 e--; 2320 } 2321 while (!Character.isWhitespace(s.charAt(e))) { 2322 e--; 2323 } 2324 return s.substring(0, b)+"..."+s.substring(e+1); 2325 } 2326 2327 public void validateReference(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, String type, ElementDefinition context, Element e, String url) { 2328 // now, do we check the URI target? 2329 if (fetcher != null && !type.equals("uuid")) { 2330 boolean found; 2331 try { 2332 found = isDefinitionURL(url) || (allowExamples && (url.contains("example.org") || url.contains("acme.com")) || url.contains("acme.org")) || (url.startsWith("http://hl7.org/fhir/tools")) || 2333 SpecialExtensions.isKnownExtension(url) || isXverUrl(url); 2334 if (!found) { 2335 found = fetcher.resolveURL(this, hostContext, path, url, type); 2336 } 2337 } catch (IOException e1) { 2338 found = false; 2339 } 2340 if (!found) { 2341 if (type.equals("canonical")) { 2342 ReferenceValidationPolicy rp = policyAdvisor == null ? ReferenceValidationPolicy.CHECK_VALID : policyAdvisor.policyForReference(this, hostContext, path, url); 2343 if (rp == ReferenceValidationPolicy.CHECK_EXISTS || rp == ReferenceValidationPolicy.CHECK_EXISTS_AND_TYPE) { 2344 rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE, url); 2345 } else { 2346 hint(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE, url); 2347 } 2348 } else { 2349 if (url.contains("hl7.org") || url.contains("fhir.org")) { 2350 rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URL_RESOLVE, url); 2351 } else if (url.contains("example.org") || url.contains("acme.com")) { 2352 rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URL_EXAMPLE, url); 2353 } else { 2354 warning(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URL_RESOLVE, url); 2355 } 2356 } 2357 } else { 2358 if (type.equals("canonical")) { 2359 ReferenceValidationPolicy rp = policyAdvisor == null ? ReferenceValidationPolicy.CHECK_VALID : policyAdvisor.policyForReference(this, hostContext, path, url); 2360 if (rp == ReferenceValidationPolicy.CHECK_EXISTS_AND_TYPE || rp == ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS || rp == ReferenceValidationPolicy.CHECK_VALID) { 2361 try { 2362 Resource r = null; 2363 if (url.startsWith("#")) { 2364 r = loadContainedResource(errors, path, hostContext.getRootResource(), url.substring(1), Resource.class); 2365 } 2366 if (r == null) { 2367 fetcher.fetchCanonicalResource(this, url); 2368 } 2369 if (r == null) { 2370 r = this.context.fetchResource(Resource.class, url); 2371 } 2372 if (r == null) { 2373 warning(errors, IssueType.INVALID, e.line(), e.col(), path, rp != ReferenceValidationPolicy.CHECK_VALID, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE_NC, url); 2374 } else if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, isCorrectCanonicalType(r, context), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_TYPE, url, r.fhirType(), listExpectedCanonicalTypes(context))) { 2375 if (rp == ReferenceValidationPolicy.CHECK_VALID) { 2376 // todo.... 2377 } 2378 } 2379 } catch (Exception ex) { 2380 // won't happen 2381 } 2382 } 2383 } 2384 } 2385 } 2386 } 2387 2388 private Set<String> listExpectedCanonicalTypes(ElementDefinition context) { 2389 Set<String> res = new HashSet<>(); 2390 TypeRefComponent tr = context.getType("canonical"); 2391 if (tr != null) { 2392 for (CanonicalType p : tr.getTargetProfile()) { 2393 String url = p.getValue(); 2394 StructureDefinition sd = this.context.fetchResource(StructureDefinition.class, url); 2395 if (sd != null) { 2396 res.add(sd.getType()); 2397 } else { 2398 if (url != null && url.startsWith("http://hl7.org/fhir/StructureDefinition/")) { 2399 res.add(url.substring("http://hl7.org/fhir/StructureDefinition/".length())); 2400 } 2401 } 2402 } 2403 } 2404 return res; 2405 } 2406 2407 private boolean isCorrectCanonicalType(Resource r, ElementDefinition context) { 2408 TypeRefComponent tr = context.getType("canonical"); 2409 if (tr != null) { 2410 for (CanonicalType p : tr.getTargetProfile()) { 2411 if (isCorrectCanonicalType(r, p)) { 2412 return true; 2413 } 2414 } 2415 if (tr.getTargetProfile().isEmpty()) { 2416 return true; 2417 } 2418 } 2419 return false; 2420 } 2421 2422 private boolean isCorrectCanonicalType(Resource r, CanonicalType p) { 2423 String url = p.getValue(); 2424 String t = null; 2425 StructureDefinition sd = context.fetchResource(StructureDefinition.class, url); 2426 if (sd != null) { 2427 t = sd.getType(); 2428 } else if (url.startsWith("http://hl7.org/fhir/StructureDefinition/")) { 2429 t = url.substring("http://hl7.org/fhir/StructureDefinition/".length()); 2430 } else { 2431 return false; 2432 } 2433 return Utilities.existsInList(t, "Resource", "CanonicalResource") || t.equals(r.fhirType()); 2434 } 2435 2436 private boolean isCanonicalURLElement(Element e) { 2437 if (e.getProperty() == null || e.getProperty().getDefinition() == null) { 2438 return false; 2439 } 2440 String path = e.getProperty().getDefinition().getBase().getPath(); 2441 if (path == null) { 2442 return false; 2443 } 2444 String[] p = path.split("\\."); 2445 if (p.length != 2) { 2446 return false; 2447 } 2448 if (!"url".equals(p[1])) { 2449 return false; 2450 } 2451 return Utilities.existsInList(p[0], VersionUtilities.getCanonicalResourceNames(context.getVersion())); 2452 } 2453 2454 private boolean containsHtmlTags(String cnt) { 2455 int i = cnt.indexOf("<"); 2456 while (i > -1) { 2457 cnt = cnt.substring(i+1); 2458 i = cnt.indexOf("<"); 2459 int e = cnt.indexOf(">"); 2460 if (e > -1 && e < i) { 2461 String s = cnt.substring(0, e); 2462 if (s.matches(HTML_FRAGMENT_REGEX)) { 2463 return true; 2464 } 2465 } 2466 } 2467 return false; 2468 } 2469 2470 /** 2471 * Technically this is not bulletproof as some invalid base64 won't be caught, 2472 * but I think it's good enough. The original code used Java8 Base64 decoder 2473 * but I've replaced it with a regex for 2 reasons: 2474 * 1. This code will run on any version of Java 2475 * 2. This code doesn't actually decode, which is much easier on memory use for big payloads 2476 */ 2477 private boolean isValidBase64(String theEncoded) { 2478 if (theEncoded == null) { 2479 return false; 2480 } 2481 int charCount = 0; 2482 boolean ok = true; 2483 for (int i = 0; i < theEncoded.length(); i++) { 2484 char nextChar = theEncoded.charAt(i); 2485 if (Character.isWhitespace(nextChar)) { 2486 continue; 2487 } 2488 if (Character.isLetterOrDigit(nextChar)) { 2489 charCount++; 2490 } 2491 if (nextChar == '/' || nextChar == '=' || nextChar == '+') { 2492 charCount++; 2493 } 2494 } 2495 2496 if (charCount > 0 && charCount % 4 != 0) { 2497 ok = false; 2498 } 2499 return ok; 2500 } 2501 2502 private boolean base64HasWhitespace(String theEncoded) { 2503 if (theEncoded == null) { 2504 return false; 2505 } 2506 for (int i = 0; i < theEncoded.length(); i++) { 2507 char nextChar = theEncoded.charAt(i); 2508 if (Character.isWhitespace(nextChar)) { 2509 return true; 2510 } 2511 } 2512 return false; 2513 2514 } 2515 2516 2517 private int countBase64DecodedBytes(String theEncoded) { 2518 Base64InputStream inputStream = new Base64InputStream(new ByteArrayInputStream(theEncoded.getBytes(StandardCharsets.UTF_8))); 2519 try { 2520 try { 2521 for (int counter = 0; ; counter++) { 2522 if (inputStream.read() == -1) { 2523 return counter; 2524 } 2525 } 2526 } finally { 2527 inputStream.close(); 2528 } 2529 } catch (IOException e) { 2530 throw new IllegalStateException(e); // should not happen 2531 } 2532 } 2533 2534 private boolean isDefinitionURL(String url) { 2535 return Utilities.existsInList(url, "http://hl7.org/fhirpath/System.Boolean", "http://hl7.org/fhirpath/System.String", "http://hl7.org/fhirpath/System.Integer", 2536 "http://hl7.org/fhirpath/System.Decimal", "http://hl7.org/fhirpath/System.Date", "http://hl7.org/fhirpath/System.Time", "http://hl7.org/fhirpath/System.DateTime", "http://hl7.org/fhirpath/System.Quantity"); 2537 } 2538 2539 private void checkInnerNames(List<ValidationMessage> errors, Element e, String path, List<XhtmlNode> list, boolean inPara) { 2540 for (XhtmlNode node : list) { 2541 if (node.getNodeType() == NodeType.Comment) { 2542 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !node.getContent().startsWith("DOCTYPE"), I18nConstants.XHTML_XHTML_DOCTYPE_ILLEGAL); 2543 } 2544 if (node.getNodeType() == NodeType.Element) { 2545 rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.existsInList(node.getName(), 2546 "p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", "b", "em", "i", "strong", 2547 "small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup", 2548 "ul", "ol", "li", "dl", "dt", "dd", "pre", "table", "caption", "colgroup", "col", "thead", "tr", "tfoot", "tbody", "th", "td", 2549 "code", "samp", "img", "map", "area"), I18nConstants.XHTML_XHTML_ELEMENT_ILLEGAL, node.getName()); 2550 2551 for (String an : node.getAttributes().keySet()) { 2552 boolean ok = an.startsWith("xmlns") || Utilities.existsInList(an, 2553 "title", "style", "class", ID, "lang", "xml:lang", "dir", "accesskey", "tabindex", 2554 // tables 2555 "span", "width", "align", "valign", "char", "charoff", "abbr", "axis", "headers", "scope", "rowspan", "colspan") || 2556 2557 Utilities.existsInList(node.getName() + "." + an, "a.href", "a.name", "img.src", "img.border", "div.xmlns", "blockquote.cite", "q.cite", 2558 "a.charset", "a.type", "a.name", "a.href", "a.hreflang", "a.rel", "a.rev", "a.shape", "a.coords", "img.src", 2559 "img.alt", "img.longdesc", "img.height", "img.width", "img.usemap", "img.ismap", "map.name", "area.shape", 2560 "area.coords", "area.href", "area.nohref", "area.alt", "table.summary", "table.width", "table.border", 2561 "table.frame", "table.rules", "table.cellspacing", "table.cellpadding", "pre.space", "td.nowrap" 2562 ); 2563 if (!ok) { 2564 rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.XHTML_XHTML_ATTRIBUTE_ILLEGAL, an, node.getName()); 2565 } 2566 } 2567 2568 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !(inPara && Utilities.existsInList(node.getName(), "div", "blockquote", "table", "ol", "ul", "p")) , I18nConstants.XHTML_XHTML_ELEMENT_ILLEGAL_IN_PARA, node.getName()); 2569 2570 checkInnerNames(errors, e, path, node.getChildNodes(), inPara || "p".equals(node.getName())); 2571 } 2572 } 2573 } 2574 2575 private void checkUrls(List<ValidationMessage> errors, Element e, String path, List<XhtmlNode> list) { 2576 for (XhtmlNode node : list) { 2577 if (node.getNodeType() == NodeType.Element) { 2578 if ("a".equals(node.getName())) { 2579 String msg = checkValidUrl(node.getAttribute("href")); 2580 rule(errors, IssueType.INVALID, e.line(), e.col(), path, msg == null, I18nConstants.XHTML_URL_INVALID, node.getAttribute("href"), msg); 2581 } else if ("img".equals(node.getName())) { 2582 String msg = checkValidUrl(node.getAttribute("src")); 2583 rule(errors, IssueType.INVALID, e.line(), e.col(), path, msg == null, I18nConstants.XHTML_URL_INVALID, node.getAttribute("src"), msg); 2584 } 2585 checkUrls(errors, e, path, node.getChildNodes()); 2586 } 2587 } 2588 } 2589 2590 private String checkValidUrl(String value) { 2591 if (value == null) { 2592 return null; 2593 } 2594 if (Utilities.noString(value)) { 2595 return context.formatMessage(I18nConstants.XHTML_URL_EMPTY); 2596 } 2597 2598 if (value.startsWith("data:")) { 2599 String[] p = value.substring(5).split("\\,"); 2600 if (p.length < 2) { 2601 return context.formatMessage(I18nConstants.XHTML_URL_DATA_NO_DATA, value); 2602 } else if (p.length > 2) { 2603 return context.formatMessage(I18nConstants.XHTML_URL_DATA_DATA_INVALID_COMMA, value); 2604 } else if (!p[0].endsWith(";base64") || !isValidBase64(p[1])) { 2605 return context.formatMessage(I18nConstants.XHTML_URL_DATA_DATA_INVALID, value); 2606 } else { 2607 if (p[0].startsWith(" ")) { 2608 p[0] = p[0].trim(); 2609 } 2610 String mMsg = checkValidMimeType(p[0].substring(0, p[0].lastIndexOf(";"))); 2611 if (mMsg != null) { 2612 return context.formatMessage(I18nConstants.XHTML_URL_DATA_MIMETYPE, value, mMsg); 2613 } 2614 } 2615 return null; 2616 } else { 2617 Set<Character> invalidChars = new HashSet<>(); 2618 for (char ch : value.toCharArray()) { 2619 if (!(Character.isDigit(ch) || Character.isAlphabetic(ch) || Utilities.existsInList(ch, ';', '?', ':', '@', '&', '=', '+', '$', '.', ',', '/', '%', '-', '_', '~', '#', '[', ']', '!', '\'', '(', ')', '*' ))) { 2620 invalidChars.add(ch); 2621 } 2622 } 2623 if (invalidChars.isEmpty()) { 2624 return null; 2625 } else { 2626 return context.formatMessage(I18nConstants.XHTML_URL_INVALID_CHARS, invalidChars.toString()); 2627 } 2628 } 2629 } 2630 2631 private String checkValidMimeType(String mt) { 2632 if (!mt.matches("^(\\w+|\\*)\\/(\\w+|\\*)((;\\s*(\\w+)=\\s*(\\S+))?)$")) { 2633 return "Mime type invalid"; 2634 } 2635 return null; 2636 } 2637 2638 private void checkInnerNS(List<ValidationMessage> errors, Element e, String path, List<XhtmlNode> list) { 2639 for (XhtmlNode node : list) { 2640 if (node.getNodeType() == NodeType.Element) { 2641 String ns = node.getNsDecl(); 2642 rule(errors, IssueType.INVALID, e.line(), e.col(), path, ns == null || FormatUtilities.XHTML_NS.equals(ns), I18nConstants.XHTML_XHTML_NS_INVALID, ns, FormatUtilities.XHTML_NS); 2643 checkInnerNS(errors, e, path, node.getChildNodes()); 2644 } 2645 } 2646 } 2647 2648 private void checkPrimitiveBinding(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, String type, ElementDefinition elementContext, Element element, StructureDefinition profile, NodeStack stack) { 2649 // We ignore bindings that aren't on string, uri or code 2650 if (!element.hasPrimitiveValue() || !("code".equals(type) || "string".equals(type) || "uri".equals(type) || "url".equals(type) || "canonical".equals(type))) { 2651 return; 2652 } 2653 if (noTerminologyChecks) 2654 return; 2655 2656 String value = element.primitiveValue(); 2657 // System.out.println("check "+value+" in "+path); 2658 2659 // firstly, resolve the value set 2660 ElementDefinitionBindingComponent binding = elementContext.getBinding(); 2661 if (binding.hasValueSet()) { 2662 ValueSet vs = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl()); 2663 if (vs == null) { 2664 CodeSystem cs = context.fetchCodeSystem(binding.getValueSet()); 2665 if (rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(binding.getValueSet()))) { 2666 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(binding.getValueSet())); 2667 } 2668 } else { 2669 CodedContentValidationPolicy validationPolicy = getPolicyAdvisor() == null ? 2670 CodedContentValidationPolicy.VALUESET : getPolicyAdvisor().policyForCodedContent(this, hostContext, stack.getLiteralPath(), elementContext, profile, BindingKind.PRIMARY, vs, new ArrayList<>()); 2671 2672 if (validationPolicy != CodedContentValidationPolicy.IGNORE) { 2673 long t = System.nanoTime(); 2674 ValidationResult vr = null; 2675 if (binding.getStrength() != BindingStrength.EXAMPLE) { 2676 ValidationOptions options = baseOptions.setLanguage(stack.getWorkingLang()).guessSystem(); 2677 if (validationPolicy == CodedContentValidationPolicy.CODE) { 2678 options = options.noCheckValueSetMembership(); 2679 } 2680 vr = checkCodeOnServer(stack, vs, value, options); 2681 } 2682 timeTracker.tx(t, "vc "+value+""); 2683 if (binding.getStrength() == BindingStrength.REQUIRED) { 2684 removeTrackedMessagesForLocation(errors, element, path); 2685 } 2686 if (vr != null && !vr.isOk()) { 2687 if (vr.IsNoService()) 2688 txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_15, value); 2689 else if (binding.getStrength() == BindingStrength.REQUIRED) 2690 txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_16, value, describeValueSet(binding.getValueSet()), getErrorMessage(vr.getMessage())); 2691 else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { 2692 if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) 2693 checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), value, stack); 2694 else if (!noExtensibleWarnings && !isOkExtension(value, vs)) 2695 txWarningForLaterRemoval(element, errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_17, value, describeValueSet(binding.getValueSet()), getErrorMessage(vr.getMessage())); 2696 } else if (binding.getStrength() == BindingStrength.PREFERRED) { 2697 if (baseOnly) { 2698 txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_18, value, describeValueSet(binding.getValueSet()), getErrorMessage(vr.getMessage())); 2699 } 2700 } 2701 } 2702 } 2703 } 2704 } else if (!noBindingMsgSuppressed) 2705 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, !type.equals("code"), I18nConstants.TERMINOLOGY_TX_BINDING_NOSOURCE2); 2706 } 2707 2708 private boolean isOkExtension(String value, ValueSet vs) { 2709 if ("http://hl7.org/fhir/ValueSet/defined-types".equals(vs.getUrl())) { 2710 return value.startsWith("http://hl7.org/fhirpath/System."); 2711 } 2712 return false; 2713 } 2714 2715 private void checkQuantity(List<ValidationMessage> errors, String path, Element focus, Quantity fixed, String fixedSource, boolean pattern) { 2716 checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), fixedSource, "value", focus, pattern); 2717 checkFixedValue(errors, path + ".comparator", focus.getNamedChild("comparator"), fixed.getComparatorElement(), fixedSource, "comparator", focus, pattern); 2718 checkFixedValue(errors, path + ".units", focus.getNamedChild("unit"), fixed.getUnitElement(), fixedSource, "units", focus, pattern); 2719 checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), fixedSource, "system", focus, pattern); 2720 checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), fixedSource, "code", focus, pattern); 2721 } 2722 2723 private void checkQuantity(List<ValidationMessage> errors, String path, Element element, StructureDefinition theProfile, ElementDefinition definition, NodeStack theStack) { 2724 String value = element.hasChild("value") ? element.getNamedChild("value").getValue() : null; 2725 String unit = element.hasChild("unit") ? element.getNamedChild("unit").getValue() : null; 2726 String system = element.hasChild("system") ? element.getNamedChild("system").getValue() : null; 2727 String code = element.hasChild("code") ? element.getNamedChild("code").getValue() : null; 2728 2729 // todo: allowedUnits http://hl7.org/fhir/StructureDefinition/elementdefinition-allowedUnits - codeableConcept, or canonical(ValueSet) 2730 // todo: http://hl7.org/fhir/StructureDefinition/iso21090-PQ-translation 2731 2732 if (!Utilities.noString(value) && definition.hasExtension("http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces")) { 2733 int dp = value.contains(".") ? value.substring(value.indexOf(".")+1).length() : 0; 2734 int def = Integer.parseInt(ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces")); 2735 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, dp <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_CHARS, dp, def); 2736 } 2737 2738 if (system != null || code != null ) { 2739 checkCodedElement(errors, path, element, theProfile, definition, false, false, theStack, code, system, null, unit); 2740 } 2741 2742 if (code != null && "http://unitsofmeasure.org".equals(system)) { 2743 int b = code.indexOf("{"); 2744 int e = code.indexOf("}"); 2745 if (b >= 0 && e > 0 && b < e) { 2746 bpCheck(errors, IssueType.BUSINESSRULE, element.line(), element.col(), path, !code.contains("{"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_NO_ANNOTATIONS, code.substring(b, e+1)); 2747 } 2748 } 2749 2750 if (definition.hasMinValue()) { 2751 if (warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(value), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_NO_VALUE) && 2752 rule(errors, IssueType.INVALID, element.line(), element.col(), path, definition.getMinValue() instanceof Quantity, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_NO_QTY, definition.getMinValue().fhirType())) { 2753 Quantity min = definition.getMinValueQuantity(); 2754 if (warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(min.getSystem()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_SYSTEM) && 2755 warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(system), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_NO_SYSTEM) && 2756 warning(errors, IssueType.INVALID, element.line(), element.col(), path, system.equals(min.getSystem()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_SYSTEM_MISMATCH, system, min.getSystem()) && 2757 warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(min.getCode()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_CODE) && 2758 warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(code), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_NO_CODE) && 2759 rule(errors, IssueType.INVALID, element.line(), element.col(), path, definition.getMinValueQuantity().hasValue(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_VALUE)) { 2760 if (code.equals(min.getCode())) { 2761 // straight value comparison 2762 rule(errors, IssueType.INVALID, element.line(), element.col(), path, checkDecimalMinValue(value, min.getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_WRONG, value, min.getValue().toString()); 2763 } else if ("http://unitsofmeasure.org".equals(system)) { 2764 if (warning(errors, IssueType.INVALID, element.line(), element.col(), path, context.getUcumService() != null, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_NO_UCUM_SVC)) { 2765 Decimal v = convertUcumValue(value, code, min.getCode()); 2766 if (rule(errors, IssueType.INVALID, element.line(), element.col(), path, v != null, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_CONVERT, value, code, min.getCode())) { 2767 rule(errors, IssueType.INVALID, element.line(), element.col(), path, checkDecimalMinValue(v, min.getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_WRONG_UCUM, value, code, min.getValue().toString(), min.getCode()); 2768 } 2769 } 2770 } else { 2771 warning(errors, IssueType.INVALID, element.line(), element.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_CODE_MISMATCH, code, min.getCode()); 2772 } 2773 } 2774 } 2775 } 2776 2777 if (definition.hasMaxValue()) { 2778 if (warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(value), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_NO_VALUE) && 2779 rule(errors, IssueType.INVALID, element.line(), element.col(), path, definition.getMaxValue() instanceof Quantity, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_NO_QTY, definition.getMaxValue().fhirType())) { 2780 Quantity max = definition.getMaxValueQuantity(); 2781 if (warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(max.getSystem()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_SYSTEM) && 2782 warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(system), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_NO_SYSTEM) && 2783 warning(errors, IssueType.INVALID, element.line(), element.col(), path, system.equals(max.getSystem()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_SYSTEM_MISMATCH, system, max.getSystem()) && 2784 warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(max.getCode()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_CODE) && 2785 warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(code), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_NO_CODE) && 2786 rule(errors, IssueType.INVALID, element.line(), element.col(), path, definition.getMaxValueQuantity().hasValue(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_VALUE)) { 2787 if (code.equals(max.getCode())) { 2788 // straight value comparison 2789 rule(errors, IssueType.INVALID, element.line(), element.col(), path, checkDecimalMaxValue(value, max.getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_WRONG, value, max.getValue().toString()); 2790 } else if ("http://unitsofmeasure.org".equals(system)) { 2791 if (warning(errors, IssueType.INVALID, element.line(), element.col(), path, context.getUcumService() != null, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_NO_UCUM_SVC)) { 2792 Decimal v = convertUcumValue(value, code, max.getCode()); 2793 if (rule(errors, IssueType.INVALID, element.line(), element.col(), path, v != null, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_CONVERT, value, code, max.getCode())) { 2794 rule(errors, IssueType.INVALID, element.line(), element.col(), path, checkDecimalMaxValue(v, max.getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_WRONG_UCUM, value, code, max.getValue().toString(), max.getCode()); 2795 } 2796 } 2797 } else { 2798 warning(errors, IssueType.INVALID, element.line(), element.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_CODE_MISMATCH, code, max.getCode()); 2799 } 2800 } 2801 } 2802 } 2803 } 2804 2805 private Decimal convertUcumValue(String value, String code, String minCode) { 2806 try { 2807 Decimal v = new Decimal(value); 2808 return context.getUcumService().convert(v, code, minCode); 2809 } catch (Exception e) { 2810 return null; 2811 } 2812 } 2813 2814 private boolean checkDecimalMaxValue(Decimal value, BigDecimal min) { 2815 try { 2816 Decimal m = new Decimal(min.toString()); 2817 return value.comparesTo(m) <= 0; 2818 } catch (Exception e) { 2819 return false; // this will be another error somewhere else? 2820 } 2821 } 2822 2823 private boolean checkDecimalMaxValue(String value, BigDecimal min) { 2824 try { 2825 BigDecimal v = new BigDecimal(value); 2826 return v.compareTo(min) <= 0; 2827 } catch (Exception e) { 2828 return false; // this will be another error somewhere else 2829 } 2830 } 2831 2832 private boolean checkDecimalMinValue(Decimal value, BigDecimal min) { 2833 try { 2834 Decimal m = new Decimal(min.toString()); 2835 return value.comparesTo(m) >= 0; 2836 } catch (Exception e) { 2837 return false; // this will be another error somewhere else? 2838 } 2839 } 2840 2841 private boolean checkDecimalMinValue(String value, BigDecimal min) { 2842 try { 2843 BigDecimal v = new BigDecimal(value); 2844 return v.compareTo(min) >= 0; 2845 } catch (Exception e) { 2846 return false; // this will be another error somewhere else 2847 } 2848 } 2849 2850 private void checkAttachment(List<ValidationMessage> errors, String path, Element element, StructureDefinition theProfile, ElementDefinition definition, boolean theInCodeableConcept, boolean theCheckDisplayInContext, NodeStack theStack) { 2851 long size = -1; 2852 // first check size 2853 String fetchError = null; 2854 if (element.hasChild("data")) { 2855 String b64 = element.getChildValue("data"); 2856 // Note: If the value isn't valid, we're not adding an error here, as the test to the 2857 // child Base64Binary will catch it and we don't want to log it twice 2858 boolean ok = isValidBase64(b64); 2859 if (ok && element.hasChild("size")) { 2860 size = countBase64DecodedBytes(b64); 2861 String sz = element.getChildValue("size"); 2862 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, Long.toString(size).equals(sz), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_CORRECT, sz, size); 2863 } 2864 } else if (element.hasChild("size")) { 2865 String sz = element.getChildValue("size"); 2866 if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, Utilities.isLong(sz), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_INVALID, sz)) { 2867 size = Long.parseLong(sz); 2868 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, size >= 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_INVALID, sz); 2869 } 2870 } else if (element.hasChild("url")) { 2871 String url = element.getChildValue("url"); 2872 if (definition.hasExtension("http://hl7.org/fhir/StructureDefinition/maxSize")) { 2873 try { 2874 if (url.startsWith("http://") || url.startsWith("https://")) { 2875 if (fetcher == null) { 2876 fetchError = context.formatMessage(I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_NO_FETCHER, url); 2877 } else { 2878 byte[] cnt = fetcher.fetchRaw(this, url); 2879 size = cnt.length; 2880 } 2881 } else if (url.startsWith("file:")) { 2882 size = new File(url.substring(5)).length(); 2883 } else { 2884 fetchError = context.formatMessage(I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_UNKNOWN_URL_SCHEME, url); } 2885 } catch (Exception e) { 2886 if (STACK_TRACE) e.printStackTrace(); 2887 fetchError = context.formatMessage(I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_URL_ERROR, url, e.getMessage()); 2888 } 2889 } 2890 } 2891 if (definition.hasExtension("http://hl7.org/fhir/StructureDefinition/maxSize")) { 2892 if (warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, size >= 0, fetchError)) { 2893 long def = Long.parseLong(ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/maxSize")); 2894 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, size <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_TOO_LONG, size, def); 2895 } 2896 } 2897 warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, (element.hasChild("data") || element.hasChild("url")) || (element.hasChild("contentType") || element.hasChild("language")), 2898 I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_NO_CONTENT); 2899 } 2900 2901 // implementation 2902 2903 private void checkRange(List<ValidationMessage> errors, String path, Element focus, Range fixed, String fixedSource, boolean pattern) { 2904 checkFixedValue(errors, path + ".low", focus.getNamedChild("low"), fixed.getLow(), fixedSource, "low", focus, pattern); 2905 checkFixedValue(errors, path + ".high", focus.getNamedChild("high"), fixed.getHigh(), fixedSource, "high", focus, pattern); 2906 2907 } 2908 2909 private void checkRatio(List<ValidationMessage> errors, String path, Element focus, Ratio fixed, String fixedSource, boolean pattern) { 2910 checkFixedValue(errors, path + ".numerator", focus.getNamedChild("numerator"), fixed.getNumerator(), fixedSource, "numerator", focus, pattern); 2911 checkFixedValue(errors, path + ".denominator", focus.getNamedChild("denominator"), fixed.getDenominator(), fixedSource, "denominator", focus, pattern); 2912 } 2913 2914 private void checkReference(ValidatorHostContext hostContext, 2915 List<ValidationMessage> errors, 2916 String path, 2917 Element element, 2918 StructureDefinition profile, 2919 ElementDefinition container, 2920 String parentType, 2921 NodeStack stack) throws FHIRException { 2922 Reference reference = ObjectConverter.readAsReference(element); 2923 2924 String ref = reference.getReference(); 2925 if (Utilities.noString(ref)) { 2926 if (!path.contains("element.pattern")) { // this business rule doesn't apply to patterns 2927 if (Utilities.noString(reference.getIdentifier().getSystem()) && Utilities.noString(reference.getIdentifier().getValue())) { 2928 warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, 2929 !Utilities.noString(element.getNamedChildValue("display")), I18nConstants.REFERENCE_REF_NODISPLAY); 2930 } 2931 } 2932 return; 2933 } else if (Utilities.existsInList(ref, "http://tools.ietf.org/html/bcp47")) { 2934 // special known URLs that can't be validated but are known to be valid 2935 return; 2936 } 2937 warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, !isSuspiciousReference(ref), I18nConstants.REFERENCE_REF_SUSPICIOUS, ref); 2938 2939 ResolvedReference we = localResolve(ref, stack, errors, path, hostContext.getRootResource(), hostContext.getGroupingResource(), element); 2940 String refType; 2941 if (ref.startsWith("#")) { 2942 refType = "contained"; 2943 } else { 2944 if (we == null) { 2945 refType = "remote"; 2946 } else { 2947 refType = "bundled"; 2948 } 2949 } 2950 ReferenceValidationPolicy pol; 2951 if (refType.equals("contained") || refType.equals("bundled")) { 2952 pol = ReferenceValidationPolicy.CHECK_VALID; 2953 } else { 2954 if (policyAdvisor == null) pol = ReferenceValidationPolicy.IGNORE; 2955 else pol = policyAdvisor.policyForReference(this, hostContext.getAppContext(), path, ref); 2956 } 2957 2958 if (pol.checkExists()) { 2959 if (we == null) { 2960 if (!refType.equals("contained")) { 2961 if (fetcher == null) { 2962 throw new FHIRException(context.formatMessage(I18nConstants.RESOURCE_RESOLUTION_SERVICES_NOT_PROVIDED)); 2963 } else { 2964 Element ext = null; 2965 if (fetchCache.containsKey(ref)) { 2966 ext = fetchCache.get(ref); 2967 } else { 2968 try { 2969 ext = fetcher.fetch(this, hostContext.getAppContext(), ref); 2970 } catch (IOException e) { 2971 if (STACK_TRACE) e.printStackTrace(); 2972 throw new FHIRException(e); 2973 } 2974 if (ext != null) { 2975 setParents(ext); 2976 fetchCache.put(ref, ext); 2977 } 2978 } 2979 we = ext == null ? null : makeExternalRef(ext, path); 2980 } 2981 } 2982 } 2983 boolean ok = (allowExamples && (ref.contains("example.org") || ref.contains("acme.com"))) 2984 || (we != null || pol == ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS); 2985 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ok, I18nConstants.REFERENCE_REF_CANTRESOLVE, ref); 2986 } 2987 2988 String ft; 2989 if (we != null) { 2990 ft = we.getType(); 2991 } else { 2992 ft = tryParse(ref); 2993 } 2994 2995 if (reference.hasType()) { // R4 onwards... 2996 // the type has to match the specified 2997 String tu = isAbsolute(reference.getType()) ? reference.getType() : "http://hl7.org/fhir/StructureDefinition/" + reference.getType(); 2998 TypeRefComponent containerType = container.getType("Reference"); 2999 if (!containerType.hasTargetProfile(tu) 3000 && !containerType.hasTargetProfile("http://hl7.org/fhir/StructureDefinition/Resource") 3001 && !containerType.getTargetProfile().isEmpty() 3002 ) { 3003 boolean matchingResource = false; 3004 for (CanonicalType target : containerType.getTargetProfile()) { 3005 StructureDefinition sd = resolveProfile(profile, target.asStringValue()); 3006 if (rule(errors, IssueType.NOTFOUND, element.line(), element.col(), path, sd != null, 3007 I18nConstants.REFERENCE_REF_CANTRESOLVEPROFILE, target.asStringValue())) { 3008 if (("http://hl7.org/fhir/StructureDefinition/" + sd.getType()).equals(tu)) { 3009 matchingResource = true; 3010 break; 3011 } 3012 } 3013 } 3014 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, matchingResource, 3015 I18nConstants.REFERENCE_REF_WRONGTARGET, reference.getType(), container.getType("Reference").getTargetProfile()); 3016 3017 } 3018 // the type has to match the actual 3019 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, 3020 ft == null || ft.equals(reference.getType()), I18nConstants.REFERENCE_REF_BADTARGETTYPE, reference.getType(), ft); 3021 } 3022 3023 if (we != null && pol.checkType()) { 3024 if (warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ft != null, 3025 I18nConstants.REFERENCE_REF_NOTYPE)) { 3026 // we validate as much as we can. First, can we infer a type from the profile? 3027 boolean ok = false; 3028 TypeRefComponent type = getReferenceTypeRef(container.getType()); 3029 if (type.hasTargetProfile() && !type.hasTargetProfile("http://hl7.org/fhir/StructureDefinition/Resource")) { 3030 Set<String> types = new HashSet<>(); 3031 List<StructureDefinition> profiles = new ArrayList<>(); 3032 for (UriType u : type.getTargetProfile()) { 3033 StructureDefinition sd = resolveProfile(profile, u.getValue()); 3034 if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, sd != null, 3035 I18nConstants.REFERENCE_REF_CANTRESOLVEPROFILE, u.getValue())) { 3036 types.add(sd.getType()); 3037 if (ft.equals(sd.getType())) { 3038 ok = true; 3039 profiles.add(sd); 3040 } 3041 } 3042 } 3043 if (!pol.checkValid()) { 3044 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, profiles.size() > 0, 3045 I18nConstants.REFERENCE_REF_CANTMATCHTYPE, ref, StringUtils.join("; ", type.getTargetProfile())); 3046 } else { 3047 Map<StructureDefinition, List<ValidationMessage>> badProfiles = new HashMap<>(); 3048 Map<StructureDefinition, List<ValidationMessage>> goodProfiles = new HashMap<>(); 3049 int goodCount = 0; 3050 for (StructureDefinition pr : profiles) { 3051 List<ValidationMessage> profileErrors = new ArrayList<ValidationMessage>(); 3052 validateResource(we.hostContext(hostContext, pr), profileErrors, we.getResource(), we.getFocus(), pr, 3053 IdStatus.OPTIONAL, we.getStack().resetIds()); 3054 if (!hasErrors(profileErrors)) { 3055 goodCount++; 3056 goodProfiles.put(pr, profileErrors); 3057 trackUsage(pr, hostContext, element); 3058 } else { 3059 badProfiles.put(pr, profileErrors); 3060 } 3061 } 3062 if (goodCount == 1) { 3063 if (showMessagesFromReferences) { 3064 for (ValidationMessage vm : goodProfiles.values().iterator().next()) { 3065 if (!errors.contains(vm)) { 3066 errors.add(vm); 3067 } 3068 } 3069 } 3070 3071 } else if (goodProfiles.size() == 0) { 3072 if (!isShowMessagesFromReferences()) { 3073 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, areAllBaseProfiles(profiles), 3074 I18nConstants.REFERENCE_REF_CANTMATCHCHOICE, ref, asList(type.getTargetProfile())); 3075 for (StructureDefinition sd : badProfiles.keySet()) { 3076 slicingHint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, false, 3077 context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, ref, sd.getUrl()), 3078 errorSummaryForSlicingAsHtml(badProfiles.get(sd)), errorSummaryForSlicingAsText(badProfiles.get(sd))); 3079 } 3080 } else { 3081 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, profiles.size() == 1, 3082 I18nConstants.REFERENCE_REF_CANTMATCHCHOICE, ref, asList(type.getTargetProfile())); 3083 for (List<ValidationMessage> messages : badProfiles.values()) { 3084 for (ValidationMessage vm : messages) { 3085 if (!errors.contains(vm)) { 3086 errors.add(vm); 3087 } 3088 } 3089 } 3090 } 3091 } else { 3092 if (!isShowMessagesFromReferences()) { 3093 warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, 3094 I18nConstants.REFERENCE_REF_MULTIPLEMATCHES, ref, asListByUrl(goodProfiles.keySet())); 3095 for (StructureDefinition sd : badProfiles.keySet()) { 3096 slicingHint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, 3097 false, context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, ref, sd.getUrl()), 3098 errorSummaryForSlicingAsHtml(badProfiles.get(sd)), errorSummaryForSlicingAsText(badProfiles.get(sd))); 3099 } 3100 } else { 3101 warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, 3102 I18nConstants.REFERENCE_REF_MULTIPLEMATCHES, ref, asListByUrl(goodProfiles.keySet())); 3103 for (List<ValidationMessage> messages : goodProfiles.values()) { 3104 for (ValidationMessage vm : messages) { 3105 if (!errors.contains(vm)) { 3106 errors.add(vm); 3107 } 3108 } 3109 } 3110 } 3111 } 3112 } 3113 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ok, 3114 I18nConstants.REFERENCE_REF_BADTARGETTYPE, ft, types.toString()); 3115 } 3116 if (type.hasAggregation() && !noCheckAggregation) { 3117 boolean modeOk = false; 3118 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 3119 for (Enumeration<AggregationMode> mode : type.getAggregation()) { 3120 b.append(mode.getCode()); 3121 if (mode.getValue().equals(AggregationMode.CONTAINED) && refType.equals("contained")) 3122 modeOk = true; 3123 else if (mode.getValue().equals(AggregationMode.BUNDLED) && refType.equals("bundled")) 3124 modeOk = true; 3125 else if (mode.getValue().equals(AggregationMode.REFERENCED) && (refType.equals("bundled") || refType.equals("remote"))) 3126 modeOk = true; 3127 } 3128 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, modeOk, 3129 I18nConstants.REFERENCE_REF_AGGREGATION, refType, b.toString()); 3130 } 3131 } 3132 } 3133 if (we == null) { 3134 TypeRefComponent type = getReferenceTypeRef(container.getType()); 3135 boolean okToRef = !type.hasAggregation() || type.hasAggregation(AggregationMode.REFERENCED); 3136 rule(errors, IssueType.REQUIRED, -1, -1, path, okToRef, I18nConstants.REFERENCE_REF_NOTFOUND_BUNDLE, ref); 3137 } 3138 if (we == null && ft != null && assumeValidRestReferences) { 3139 // if we == null, we inferred ft from the reference. if we are told to treat this as gospel 3140 TypeRefComponent type = getReferenceTypeRef(container.getType()); 3141 Set<String> types = new HashSet<>(); 3142 StructureDefinition sdFT = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+ft); 3143 boolean ok = false; 3144 for (CanonicalType tp : type.getTargetProfile()) { 3145 StructureDefinition sd = context.fetchResource(StructureDefinition.class, tp.getValue()); 3146 if (sd != null) { 3147 types.add(sd.getType()); 3148 } 3149 StructureDefinition sdF = sdFT; 3150 while (sdF != null) { 3151 if (sdF.getType().equals(sd.getType())) { 3152 ok = true; 3153 break; 3154 } 3155 sdF = sdF.hasBaseDefinition() ? context.fetchResource(StructureDefinition.class, sdF.getBaseDefinition()) : null; 3156 } 3157 } 3158 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, types.isEmpty() || ok, 3159 I18nConstants.REFERENCE_REF_BADTARGETTYPE2, ft, ref, types); 3160 3161 } 3162 if (pol == ReferenceValidationPolicy.CHECK_VALID) { 3163 // todo.... 3164 } 3165 } 3166 3167 private boolean isSuspiciousReference(String url) { 3168 if (!assumeValidRestReferences || url == null || Utilities.isAbsoluteUrl(url) || url.startsWith("#")) { 3169 return false; 3170 } 3171 String[] parts = url.split("\\/"); 3172 if (parts.length == 2 && context.getResourceNames().contains(parts[0]) && Utilities.isValidId(parts[1])) { 3173 return false; 3174 } 3175 if (parts.length == 4 && context.getResourceNames().contains(parts[0]) && Utilities.isValidId(parts[1]) && "_history".equals(parts[2]) && Utilities.isValidId(parts[3])) { 3176 return false; 3177 } 3178 return true; 3179 } 3180 3181 private String asListByUrl(Collection<StructureDefinition> list) { 3182 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 3183 for (StructureDefinition sd : list) { 3184 b.append(sd.getUrl()); 3185 } 3186 return b.toString(); 3187 } 3188 3189 private String asList(Collection<CanonicalType> list) { 3190 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 3191 for (CanonicalType c : list) { 3192 b.append(c.getValue()); 3193 } 3194 return b.toString(); 3195 } 3196 3197 private boolean areAllBaseProfiles(List<StructureDefinition> profiles) { 3198 for (StructureDefinition sd : profiles) { 3199 if (!sd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/")) { 3200 return false; 3201 } 3202 } 3203 return true; 3204 } 3205 3206 private String errorSummaryForSlicing(List<ValidationMessage> list) { 3207 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 3208 for (ValidationMessage vm : list) { 3209 if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL || vm.isSlicingHint()) { 3210 b.append(vm.getLocation() + ": " + vm.getMessage()); 3211 } 3212 } 3213 return b.toString(); 3214 } 3215 3216 private String errorSummaryForSlicingAsHtml(List<ValidationMessage> list) { 3217 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 3218 for (ValidationMessage vm : list) { 3219 if (vm.isSlicingHint()) { 3220 b.append("<li>" + vm.getLocation() + ": " + vm.getSliceHtml() + "</li>"); 3221 } else if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL) { 3222 b.append("<li>" + vm.getLocation() + ": " + vm.getHtml() + "</li>"); 3223 } 3224 } 3225 return "<ul>" + b.toString() + "</ul>"; 3226 } 3227 3228 private boolean isCritical(List<ValidationMessage> list) { 3229 for (ValidationMessage vm : list) { 3230 if (vm.isSlicingHint() && vm.isCriticalSignpost()) { 3231 return true; 3232 } 3233 } 3234 return false; 3235 } 3236 3237 private String[] errorSummaryForSlicingAsText(List<ValidationMessage> list) { 3238 List<String> res = new ArrayList<String>(); 3239 for (ValidationMessage vm : list) { 3240 if (vm.isSlicingHint()) { 3241 if (vm.sliceText != null) { 3242 for (String s : vm.sliceText) { 3243 res.add(vm.getLocation() + ": " + s); 3244 } 3245 } else { 3246 res.add(vm.getLocation() + ": " + vm.getMessage()); 3247 } 3248 } else if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL) { 3249 res.add(vm.getLocation() + ": " + vm.getHtml()); 3250 } 3251 } 3252 return res.toArray(new String[0]); 3253 } 3254 3255 private TypeRefComponent getReferenceTypeRef(List<TypeRefComponent> types) { 3256 for (TypeRefComponent tr : types) { 3257 if ("Reference".equals(tr.getCode())) { 3258 return tr; 3259 } 3260 } 3261 return null; 3262 } 3263 3264 private String checkResourceType(String type) { 3265 long t = System.nanoTime(); 3266 try { 3267 if (context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + type) != null) 3268 return type; 3269 else 3270 return null; 3271 } finally { 3272 timeTracker.sd(t); 3273 } 3274 } 3275 3276 private void checkSampledData(List<ValidationMessage> errors, String path, Element focus, SampledData fixed, String fixedSource, boolean pattern) { 3277 checkFixedValue(errors, path + ".origin", focus.getNamedChild("origin"), fixed.getOrigin(), fixedSource, "origin", focus, pattern); 3278 checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriodElement(), fixedSource, "period", focus, pattern); 3279 checkFixedValue(errors, path + ".factor", focus.getNamedChild("factor"), fixed.getFactorElement(), fixedSource, "factor", focus, pattern); 3280 checkFixedValue(errors, path + ".lowerLimit", focus.getNamedChild("lowerLimit"), fixed.getLowerLimitElement(), fixedSource, "lowerLimit", focus, pattern); 3281 checkFixedValue(errors, path + ".upperLimit", focus.getNamedChild("upperLimit"), fixed.getUpperLimitElement(), fixedSource, "upperLimit", focus, pattern); 3282 checkFixedValue(errors, path + ".dimensions", focus.getNamedChild("dimensions"), fixed.getDimensionsElement(), fixedSource, "dimensions", focus, pattern); 3283 checkFixedValue(errors, path + ".data", focus.getNamedChild("data"), fixed.getDataElement(), fixedSource, "data", focus, pattern); 3284 } 3285 3286 private void checkReference(List<ValidationMessage> errors, String path, Element focus, Reference fixed, String fixedSource, boolean pattern) { 3287 checkFixedValue(errors, path + ".reference", focus.getNamedChild("reference"), fixed.getReferenceElement_(), fixedSource, "reference", focus, pattern); 3288 checkFixedValue(errors, path + ".type", focus.getNamedChild("type"), fixed.getTypeElement(), fixedSource, "type", focus, pattern); 3289 checkFixedValue(errors, path + ".identifier", focus.getNamedChild("identifier"), fixed.getIdentifier(), fixedSource, "identifier", focus, pattern); 3290 checkFixedValue(errors, path + ".display", focus.getNamedChild("display"), fixed.getDisplayElement(), fixedSource, "display", focus, pattern); 3291 } 3292 3293 private void checkTiming(List<ValidationMessage> errors, String path, Element focus, Timing fixed, String fixedSource, boolean pattern) { 3294 checkFixedValue(errors, path + ".repeat", focus.getNamedChild("repeat"), fixed.getRepeat(), fixedSource, "value", focus, pattern); 3295 3296 List<Element> events = new ArrayList<Element>(); 3297 focus.getNamedChildren("event", events); 3298 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, events.size() == fixed.getEvent().size(), I18nConstants.BUNDLE_MSG_EVENT_COUNT, Integer.toString(fixed.getEvent().size()), Integer.toString(events.size()))) { 3299 for (int i = 0; i < events.size(); i++) 3300 checkFixedValue(errors, path + ".event", events.get(i), fixed.getEvent().get(i), fixedSource, "event", focus, pattern); 3301 } 3302 } 3303 3304 private boolean codeinExpansion(ValueSetExpansionContainsComponent cnt, String system, String code) { 3305 for (ValueSetExpansionContainsComponent c : cnt.getContains()) { 3306 if (code.equals(c.getCode()) && system.equals(c.getSystem().toString())) 3307 return true; 3308 if (codeinExpansion(c, system, code)) 3309 return true; 3310 } 3311 return false; 3312 } 3313 3314 private boolean codeInExpansion(ValueSet vs, String system, String code) { 3315 for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) { 3316 if (code.equals(c.getCode()) && (system == null || system.equals(c.getSystem()))) 3317 return true; 3318 if (codeinExpansion(c, system, code)) 3319 return true; 3320 } 3321 return false; 3322 } 3323 3324 private String describeReference(String reference, CanonicalResource target) { 3325 if (reference == null && target == null) 3326 return "null"; 3327 if (reference == null) { 3328 return target.getUrl(); 3329 } 3330 if (target == null) { 3331 return reference; 3332 } 3333 if (reference.equals(target.getUrl())) { 3334 return reference; 3335 } 3336 return reference + "(which actually refers to " + target.getUrl() + ")"; 3337 } 3338 3339 private String describeTypes(List<TypeRefComponent> types) { 3340 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 3341 for (TypeRefComponent t : types) { 3342 b.append(t.getWorkingCode()); 3343 } 3344 return b.toString(); 3345 } 3346 3347 protected ElementDefinition findElement(StructureDefinition profile, String name) { 3348 for (ElementDefinition c : profile.getSnapshot().getElement()) { 3349 if (c.getPath().equals(name)) { 3350 return c; 3351 } 3352 } 3353 return null; 3354 } 3355 3356 public BestPracticeWarningLevel getBestPracticeWarningLevel() { 3357 return bpWarnings; 3358 } 3359 3360 @Override 3361 public CheckDisplayOption getCheckDisplay() { 3362 return checkDisplay; 3363 } 3364 3365 private ConceptDefinitionComponent getCodeDefinition(ConceptDefinitionComponent c, String code) { 3366 if (code.equals(c.getCode())) 3367 return c; 3368 for (ConceptDefinitionComponent g : c.getConcept()) { 3369 ConceptDefinitionComponent r = getCodeDefinition(g, code); 3370 if (r != null) 3371 return r; 3372 } 3373 return null; 3374 } 3375 3376 private ConceptDefinitionComponent getCodeDefinition(CodeSystem cs, String code) { 3377 for (ConceptDefinitionComponent c : cs.getConcept()) { 3378 ConceptDefinitionComponent r = getCodeDefinition(c, code); 3379 if (r != null) 3380 return r; 3381 } 3382 return null; 3383 } 3384 3385 private IndexedElement getContainedById(Element container, String id) { 3386 List<Element> contained = new ArrayList<Element>(); 3387 container.getNamedChildren("contained", contained); 3388 for (int i = 0; i < contained.size(); i++) { 3389 Element we = contained.get(i); 3390 if (id.equals(we.getNamedChildValue(ID))) { 3391 return new IndexedElement(i, we, null); 3392 } 3393 } 3394 return null; 3395 } 3396 3397 public IWorkerContext getContext() { 3398 return context; 3399 } 3400 3401 private List<ElementDefinition> getCriteriaForDiscriminator(String path, ElementDefinition element, String discriminator, StructureDefinition profile, boolean removeResolve, StructureDefinition srcProfile) throws FHIRException { 3402 List<ElementDefinition> elements = new ArrayList<ElementDefinition>(); 3403 if ("value".equals(discriminator) && element.hasFixed()) { 3404 elements.add(element); 3405 return elements; 3406 } 3407 3408 boolean dontFollowReference = false; 3409 3410 if (removeResolve) { // if we're doing profile slicing, we don't want to walk into the last resolve.. we need the profile on the source not the target 3411 if (discriminator.equals("resolve()")) { 3412 elements.add(element); 3413 return elements; 3414 } 3415 if (discriminator.endsWith(".resolve()")) { 3416 discriminator = discriminator.substring(0, discriminator.length() - 10); 3417 dontFollowReference = true; 3418 } 3419 } 3420 3421 TypedElementDefinition ted = null; 3422 String fp = fixExpr(discriminator, null); 3423 ExpressionNode expr = null; 3424 try { 3425 expr = fpe.parse(fp); 3426 } catch (Exception e) { 3427 if (STACK_TRACE) e.printStackTrace(); 3428 throw new FHIRException(context.formatMessage(I18nConstants.DISCRIMINATOR_BAD_PATH, e.getMessage(), fp), e); 3429 } 3430 long t2 = System.nanoTime(); 3431 ted = fpe.evaluateDefinition(expr, profile, new TypedElementDefinition(element), srcProfile, dontFollowReference); 3432 timeTracker.sd(t2); 3433 if (ted != null) 3434 elements.add(ted.getElement()); 3435 3436 for (TypeRefComponent type : element.getType()) { 3437 for (CanonicalType p : type.getProfile()) { 3438 String id = p.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT) ? p.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT) : null; 3439 StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getValue()); 3440 if (sd == null) 3441 throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE_, p)); 3442 profile = sd; 3443 if (id == null) 3444 element = sd.getSnapshot().getElementFirstRep(); 3445 else { 3446 element = null; 3447 for (ElementDefinition t : sd.getSnapshot().getElement()) { 3448 if (id.equals(t.getId())) 3449 element = t; 3450 } 3451 if (element == null) 3452 throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_ELEMENT__IN_PROFILE_, id, p)); 3453 } 3454 expr = fpe.parse(fp); 3455 t2 = System.nanoTime(); 3456 ted = fpe.evaluateDefinition(expr, profile, new TypedElementDefinition(element), srcProfile, dontFollowReference); 3457 timeTracker.sd(t2); 3458 if (ted != null) 3459 elements.add(ted.getElement()); 3460 } 3461 } 3462 return elements; 3463 } 3464 3465 3466 private Element getExtensionByUrl(List<Element> extensions, String urlSimple) { 3467 for (Element e : extensions) { 3468 if (urlSimple.equals(e.getNamedChildValue("url"))) 3469 return e; 3470 } 3471 return null; 3472 } 3473 3474 public List<String> getExtensionDomains() { 3475 return extensionDomains; 3476 } 3477 3478 public List<ImplementationGuide> getImplementationGuides() { 3479 return igs; 3480 } 3481 3482 private StructureDefinition getProfileForType(String type, List<TypeRefComponent> list) { 3483 for (TypeRefComponent tr : list) { 3484 String url = tr.getWorkingCode(); 3485 if (!Utilities.isAbsoluteUrl(url)) 3486 url = "http://hl7.org/fhir/StructureDefinition/" + url; 3487 long t = System.nanoTime(); 3488 StructureDefinition sd = context.fetchResource(StructureDefinition.class, url); 3489 timeTracker.sd(t); 3490 if (sd != null && (sd.getType().equals(type) || sd.getUrl().equals(type)) && sd.hasSnapshot()) 3491 return sd; 3492 } 3493 return null; 3494 } 3495 3496 private Element getValueForDiscriminator(Object appContext, List<ValidationMessage> errors, Element element, String discriminator, ElementDefinition criteria, NodeStack stack) throws FHIRException, IOException { 3497 String p = stack.getLiteralPath() + "." + element.getName(); 3498 Element focus = element; 3499 String[] dlist = discriminator.split("\\."); 3500 for (String d : dlist) { 3501 if (focus.fhirType().equals("Reference") && d.equals("reference")) { 3502 String url = focus.getChildValue("reference"); 3503 if (Utilities.noString(url)) 3504 throw new FHIRException(context.formatMessage(I18nConstants.NO_REFERENCE_RESOLVING_DISCRIMINATOR__FROM_, discriminator, element.getProperty().getName())); 3505 // Note that we use the passed in stack here. This might be a problem if the discriminator is deep enough? 3506 Element target = resolve(appContext, url, stack, errors, p); 3507 if (target == null) 3508 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_RESOURCE__AT__RESOLVING_DISCRIMINATOR__FROM_, url, d, discriminator, element.getProperty().getName())); 3509 focus = target; 3510 } else if (d.equals("value") && focus.isPrimitive()) { 3511 return focus; 3512 } else { 3513 List<Element> children = focus.getChildren(d); 3514 if (children.isEmpty()) 3515 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_FIND__RESOLVING_DISCRIMINATOR__FROM_, d, discriminator, element.getProperty().getName())); 3516 if (children.size() > 1) 3517 throw new FHIRException(context.formatMessage(I18nConstants.FOUND__ITEMS_FOR__RESOLVING_DISCRIMINATOR__FROM_, Integer.toString(children.size()), d, discriminator, element.getProperty().getName())); 3518 focus = children.get(0); 3519 p = p + "." + d; 3520 } 3521 } 3522 return focus; 3523 } 3524 3525 private CodeSystem getCodeSystem(String system) { 3526 long t = System.nanoTime(); 3527 try { 3528 return context.fetchCodeSystem(system); 3529 } finally { 3530 timeTracker.tx(t, "cs "+system); 3531 } 3532 } 3533 3534 private boolean hasTime(String fmt) { 3535 return fmt.contains("T"); 3536 } 3537 3538 private boolean hasTimeZone(String fmt) { 3539 return fmt.length() > 10 && (fmt.substring(10).contains("-") || fmt.substring(10).contains("+") || fmt.substring(10).contains("Z")); 3540 } 3541 3542 private boolean isAbsolute(String uri) { 3543 String protocol = null; 3544 String tail = null; 3545 if (uri.contains(":")) { 3546 protocol = uri.substring(0, uri.indexOf(":")); 3547 tail = uri.substring(uri.indexOf(":")+1); 3548 } 3549 if (Utilities.isToken(protocol)) { 3550 if ("file".equals(protocol)) { 3551 return tail.startsWith("/") || tail.contains(":"); 3552 } else { 3553 return true; 3554 } 3555 } else { 3556 return false; 3557 } 3558 } 3559 3560 private boolean isCodeSystemReferenceValid(String uri) { 3561 return isSystemReferenceValid(uri); 3562 } 3563 3564 private boolean isIdentifierSystemReferenceValid(String uri) { 3565 return isSystemReferenceValid(uri) || uri.startsWith("ldap:"); 3566 } 3567 3568 private boolean isSystemReferenceValid(String uri) { 3569 return uri.startsWith("http:") || uri.startsWith("https:") || uri.startsWith("urn:"); 3570 } 3571 3572 public boolean isAnyExtensionsAllowed() { 3573 return anyExtensionsAllowed; 3574 } 3575 3576 public boolean isErrorForUnknownProfiles() { 3577 return errorForUnknownProfiles; 3578 } 3579 3580 public void setErrorForUnknownProfiles(boolean errorForUnknownProfiles) { 3581 this.errorForUnknownProfiles = errorForUnknownProfiles; 3582 } 3583 3584 private boolean isParametersEntry(String path) { 3585 String[] parts = path.split("\\."); 3586 return parts.length > 2 && parts[parts.length - 1].equals(RESOURCE) && (pathEntryHasName(parts[parts.length - 2], "parameter") || pathEntryHasName(parts[parts.length - 2], "part")); 3587 } 3588 3589 private boolean isBundleEntry(String path) { 3590 String[] parts = path.split("\\."); 3591 return parts.length > 2 && parts[parts.length - 1].equals(RESOURCE) && pathEntryHasName(parts[parts.length - 2], ENTRY); 3592 } 3593 3594 private boolean isBundleOutcome(String path) { 3595 String[] parts = path.split("\\."); 3596 return parts.length > 2 && parts[parts.length - 1].equals("outcome") && pathEntryHasName(parts[parts.length - 2], "response"); 3597 } 3598 3599 3600 private static boolean pathEntryHasName(String thePathEntry, String theName) { 3601 if (thePathEntry.equals(theName)) { 3602 return true; 3603 } 3604 if (thePathEntry.length() >= theName.length() + 3) { 3605 if (thePathEntry.startsWith(theName)) { 3606 if (thePathEntry.charAt(theName.length()) == '[') { 3607 return true; 3608 } 3609 } 3610 } 3611 return false; 3612 } 3613 3614 public boolean isPrimitiveType(String code) { 3615 StructureDefinition sd = context.fetchTypeDefinition(code); 3616 return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 3617 } 3618 3619 private String getErrorMessage(String message) { 3620 return message != null ? " (error message = " + message + ")" : ""; 3621 } 3622 3623 public boolean isSuppressLoincSnomedMessages() { 3624 return suppressLoincSnomedMessages; 3625 } 3626 3627 private boolean nameMatches(String name, String tail) { 3628 if (tail.endsWith("[x]")) 3629 return name.startsWith(tail.substring(0, tail.length() - 3)); 3630 else 3631 return (name.equals(tail)); 3632 } 3633 3634 private boolean passesCodeWhitespaceRules(String v) { 3635 if (!v.trim().equals(v)) 3636 return false; 3637 boolean lastWasSpace = true; 3638 for (char c : v.toCharArray()) { 3639 if (c == ' ') { 3640 if (lastWasSpace) 3641 return false; 3642 else 3643 lastWasSpace = true; 3644 } else if (Character.isWhitespace(c)) 3645 return false; 3646 else 3647 lastWasSpace = false; 3648 } 3649 return true; 3650 } 3651 3652 private ResolvedReference localResolve(String ref, NodeStack stack, List<ValidationMessage> errors, String path, Element rootResource, Element groupingResource, Element source) { 3653 if (ref.startsWith("#")) { 3654 // work back through the parent list. 3655 // really, there should only be one level for this (contained resources cannot contain 3656 // contained resources), but we'll leave that to some other code to worry about 3657 boolean wasContained = false; 3658 NodeStack nstack = stack; 3659 while (nstack != null && nstack.getElement() != null) { 3660 if (nstack.getElement().getProperty().isResource()) { 3661 // ok, we'll try to find the contained reference 3662 if (ref.equals("#") && nstack.getElement().getSpecial() != SpecialElement.CONTAINED && wasContained) { 3663 ResolvedReference rr = new ResolvedReference(); 3664 rr.setResource(nstack.getElement()); 3665 rr.setFocus(nstack.getElement()); 3666 rr.setExternal(false); 3667 rr.setStack(nstack); 3668// rr.getStack().qualifyPath(".ofType("+nstack.getElement().fhirType()+")"); 3669// System.out.println("-->"+nstack.getLiteralPath()); 3670 return rr; 3671 } 3672 if (nstack.getElement().getSpecial() == SpecialElement.CONTAINED) { 3673 wasContained = true; 3674 } 3675 IndexedElement res = getContainedById(nstack.getElement(), ref.substring(1)); 3676 if (res != null) { 3677 ResolvedReference rr = new ResolvedReference(); 3678 rr.setResource(nstack.getElement()); 3679 rr.setFocus(res.getMatch()); 3680 rr.setExternal(false); 3681 rr.setStack(nstack.push(res.getMatch(), res.getIndex(), res.getMatch().getProperty().getDefinition(), res.getMatch().getProperty().getDefinition())); 3682 rr.getStack().qualifyPath(".ofType("+nstack.getElement().fhirType()+")"); 3683 return rr; 3684 } 3685 } 3686 if (nstack.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY || nstack.getElement().getSpecial() == SpecialElement.PARAMETER) { 3687 return null; // we don't try to resolve contained references across this boundary 3688 } 3689 nstack = nstack.getParent(); 3690 } 3691 // try again, and work up the element parent list 3692 if (ref.equals("#")) { 3693 Element e = stack.getElement(); 3694 while (e != null) { 3695 if (e.getProperty().isResource() && (e.getSpecial() != SpecialElement.CONTAINED)) { 3696 ResolvedReference rr = new ResolvedReference(); 3697 rr.setResource(e); 3698 rr.setFocus(e); 3699 rr.setExternal(false); 3700 rr.setStack(stack.push(e, -1, e.getProperty().getDefinition(), e.getProperty().getDefinition())); 3701 rr.getStack().qualifyPath(".ofType("+e.fhirType()+")"); 3702 return rr; 3703 } 3704 e = e.getParentForValidator(); 3705 } 3706 } 3707 return null; 3708 } else { 3709 // work back through the parent list - if any of them are bundles, try to resolve 3710 // the resource in the bundle 3711 String fullUrl = null; // we're going to try to work this out as we go up 3712 while (stack != null && stack.getElement() != null) { 3713 if (stack.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY && fullUrl == null && stack.getParent() != null && stack.getParent().getElement().getName().equals(ENTRY)) { 3714 String type = stack.getParent().getParent().getElement().getChildValue(TYPE); 3715 fullUrl = stack.getParent().getElement().getChildValue(FULL_URL); // we don't try to resolve contained references across this boundary 3716 if (fullUrl == null) 3717 rule(errors, IssueType.REQUIRED, stack.getParent().getElement().line(), stack.getParent().getElement().col(), stack.getParent().getLiteralPath(), 3718 Utilities.existsInList(type, "batch-response", "transaction-response") || fullUrl != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOFULLURL); 3719 } 3720 if (BUNDLE.equals(stack.getElement().getType())) { 3721 String type = stack.getElement().getChildValue(TYPE); 3722 IndexedElement res = getFromBundle(stack.getElement(), ref, fullUrl, errors, path, type, "transaction".equals(type)); 3723 if (res == null) { 3724 return null; 3725 } else { 3726 ResolvedReference rr = new ResolvedReference(); 3727 rr.setResource(res.getMatch()); 3728 rr.setFocus(res.getMatch()); 3729 rr.setExternal(false); 3730 rr.setStack(stack.push(res.getEntry(), res.getIndex(), res.getEntry().getProperty().getDefinition(), 3731 res.getEntry().getProperty().getDefinition()).push(res.getMatch(), -1, 3732 res.getMatch().getProperty().getDefinition(), res.getMatch().getProperty().getDefinition())); 3733 rr.getStack().qualifyPath(".ofType("+rr.getResource().fhirType()+")"); 3734 return rr; 3735 } 3736 } 3737 if (stack.getElement().getSpecial() == SpecialElement.PARAMETER && stack.getParent() != null) { 3738 NodeStack tgt = findInParams(stack.getParent().getParent(), ref); 3739 if (tgt != null) { 3740 ResolvedReference rr = new ResolvedReference(); 3741 rr.setResource(tgt.getElement()); 3742 rr.setFocus(tgt.getElement()); 3743 rr.setExternal(false); 3744 rr.setStack(tgt); 3745 rr.getStack().qualifyPath(".ofType("+tgt.getElement().fhirType()+")"); 3746 return rr; 3747 } 3748 } 3749 stack = stack.getParent(); 3750 } 3751 // we can get here if we got called via FHIRPath conformsTo which breaks the stack continuity. 3752 if (groupingResource != null && BUNDLE.equals(groupingResource.fhirType())) { // it could also be a Parameters resource - that case isn't handled yet 3753 String type = groupingResource.getChildValue(TYPE); 3754 Element entry = getEntryForSource(groupingResource, source); 3755 fullUrl = entry.getChildValue(FULL_URL); 3756 IndexedElement res = getFromBundle(groupingResource, ref, fullUrl, errors, path, type, "transaction".equals(type)); 3757 if (res == null) { 3758 return null; 3759 } else { 3760 ResolvedReference rr = new ResolvedReference(); 3761 rr.setResource(res.getMatch()); 3762 rr.setFocus(res.getMatch()); 3763 rr.setExternal(false); 3764 rr.setStack(new NodeStack(context, null, rootResource, validationLanguage).push(res.getEntry(), res.getIndex(), res.getEntry().getProperty().getDefinition(), 3765 res.getEntry().getProperty().getDefinition()).push(res.getMatch(), -1, 3766 res.getMatch().getProperty().getDefinition(), res.getMatch().getProperty().getDefinition())); 3767 rr.getStack().qualifyPath(".ofType("+rr.getResource().fhirType()+")"); 3768 return rr; 3769 } 3770 } 3771 } 3772 return null; 3773 } 3774 3775 private NodeStack findInParams(NodeStack params, String ref) { 3776 int i = 0; 3777 for (Element child : params.getElement().getChildren("parameter")) { 3778 NodeStack p = params.push(child, i, child.getProperty().getDefinition(), child.getProperty().getDefinition()); 3779 if (child.hasChild("resource")) { 3780 Element res = child.getNamedChild("resource"); 3781 if ((res.fhirType()+"/"+res.getIdBase()).equals(ref)) { 3782 return p.push(res, -1, res.getProperty().getDefinition(), res.getProperty().getDefinition()); 3783 } 3784 } 3785 NodeStack pc = findInParamParts(p, child, ref); 3786 if (pc != null) { 3787 return pc; 3788 } 3789 } 3790 return null; 3791 } 3792 3793 private NodeStack findInParamParts(NodeStack pp, Element param, String ref) { 3794 int i = 0; 3795 for (Element child : param.getChildren("part")) { 3796 NodeStack p = pp.push(child, i, child.getProperty().getDefinition(), child.getProperty().getDefinition()); 3797 if (child.hasChild("resource")) { 3798 Element res = child.getNamedChild("resource"); 3799 if ((res.fhirType()+"/"+res.getIdBase()).equals(ref)) { 3800 return p.push(res, -1, res.getProperty().getDefinition(), res.getProperty().getDefinition()); 3801 } 3802 } 3803 NodeStack pc = findInParamParts(p, child, ref); 3804 if (pc != null) { 3805 return pc; 3806 } 3807 } 3808 return null; 3809 } 3810 3811 private Element getEntryForSource(Element bundle, Element element) { 3812 List<Element> entries = new ArrayList<Element>(); 3813 bundle.getNamedChildren(ENTRY, entries); 3814 for (Element entry : entries) { 3815 if (entry.hasDescendant(element)) { 3816 return entry; 3817 } 3818 } 3819 return null; 3820 } 3821 3822 private ResolvedReference makeExternalRef(Element external, String path) { 3823 ResolvedReference res = new ResolvedReference(); 3824 res.setResource(external); 3825 res.setFocus(external); 3826 res.setExternal(true); 3827 res.setStack(new NodeStack(context, external, path, validationLanguage)); 3828 return res; 3829 } 3830 3831 3832 private Element resolve(Object appContext, String ref, NodeStack stack, List<ValidationMessage> errors, String path) throws IOException, FHIRException { 3833 Element local = localResolve(ref, stack, errors, path, null, null, null).getFocus(); 3834 if (local != null) 3835 return local; 3836 if (fetcher == null) 3837 return null; 3838 if (fetchCache.containsKey(ref)) { 3839 return fetchCache.get(ref); 3840 } else { 3841 Element res = fetcher.fetch(this, appContext, ref); 3842 setParents(res); 3843 fetchCache.put(ref, res); 3844 return res; 3845 } 3846 } 3847 3848 3849 private ElementDefinition resolveNameReference(StructureDefinitionSnapshotComponent snapshot, String contentReference) { 3850 for (ElementDefinition ed : snapshot.getElement()) 3851 if (contentReference.equals("#" + ed.getId())) 3852 return ed; 3853 return null; 3854 } 3855 3856 private StructureDefinition resolveProfile(StructureDefinition profile, String pr) { 3857 if (pr.startsWith("#")) { 3858 for (Resource r : profile.getContained()) { 3859 if (r.getId().equals(pr.substring(1)) && r instanceof StructureDefinition) 3860 return (StructureDefinition) r; 3861 } 3862 return null; 3863 } else { 3864 long t = System.nanoTime(); 3865 StructureDefinition fr = context.fetchResource(StructureDefinition.class, pr); 3866 timeTracker.sd(t); 3867 return fr; 3868 } 3869 } 3870 3871 private ElementDefinition resolveType(String type, List<TypeRefComponent> list) { 3872 for (TypeRefComponent tr : list) { 3873 String url = tr.getWorkingCode(); 3874 if (!Utilities.isAbsoluteUrl(url)) 3875 url = "http://hl7.org/fhir/StructureDefinition/" + url; 3876 long t = System.nanoTime(); 3877 StructureDefinition sd = context.fetchResource(StructureDefinition.class, url); 3878 timeTracker.sd(t); 3879 if (sd != null && (sd.getType().equals(type) || sd.getUrl().equals(type)) && sd.hasSnapshot()) 3880 return sd.getSnapshot().getElement().get(0); 3881 } 3882 return null; 3883 } 3884 3885 public void setAnyExtensionsAllowed(boolean anyExtensionsAllowed) { 3886 this.anyExtensionsAllowed = anyExtensionsAllowed; 3887 } 3888 3889 public IResourceValidator setBestPracticeWarningLevel(BestPracticeWarningLevel value) { 3890 bpWarnings = value; 3891 return this; 3892 } 3893 3894 @Override 3895 public void setCheckDisplay(CheckDisplayOption checkDisplay) { 3896 this.checkDisplay = checkDisplay; 3897 } 3898 3899 public void setSuppressLoincSnomedMessages(boolean suppressLoincSnomedMessages) { 3900 this.suppressLoincSnomedMessages = suppressLoincSnomedMessages; 3901 } 3902 3903 public IdStatus getResourceIdRule() { 3904 return resourceIdRule; 3905 } 3906 3907 public void setResourceIdRule(IdStatus resourceIdRule) { 3908 this.resourceIdRule = resourceIdRule; 3909 } 3910 3911 3912 public boolean isAllowXsiLocation() { 3913 return allowXsiLocation; 3914 } 3915 3916 public void setAllowXsiLocation(boolean allowXsiLocation) { 3917 this.allowXsiLocation = allowXsiLocation; 3918 } 3919 3920 /** 3921 * @param element - the candidate that might be in the slice 3922 * @param path - for reporting any errors. the XPath for the element 3923 * @param slicer - the definition of how slicing is determined 3924 * @param ed - the slice for which to test membership 3925 * @param errors 3926 * @param stack 3927 * @param srcProfile 3928 * @return 3929 * @throws DefinitionException 3930 * @throws DefinitionException 3931 * @throws IOException 3932 * @throws FHIRException 3933 */ 3934 private boolean sliceMatches(ValidatorHostContext hostContext, Element element, String path, ElementDefinition slicer, ElementDefinition ed, StructureDefinition profile, List<ValidationMessage> errors, List<ValidationMessage> sliceInfo, NodeStack stack, StructureDefinition srcProfile) throws DefinitionException, FHIRException { 3935 if (!slicer.getSlicing().hasDiscriminator()) 3936 return false; // cannot validate in this case 3937 3938 ExpressionNode n = (ExpressionNode) ed.getUserData("slice.expression.cache"); 3939 if (n == null) { 3940 long t = System.nanoTime(); 3941 // GG: this approach is flawed because it treats discriminators individually rather than collectively 3942 StringBuilder expression = new StringBuilder("true"); 3943 boolean anyFound = false; 3944 Set<String> discriminators = new HashSet<>(); 3945 for (ElementDefinitionSlicingDiscriminatorComponent s : slicer.getSlicing().getDiscriminator()) { 3946 String discriminator = s.getPath(); 3947 discriminators.add(discriminator); 3948 3949 List<ElementDefinition> criteriaElements = getCriteriaForDiscriminator(path, ed, discriminator, profile, s.getType() == DiscriminatorType.PROFILE, srcProfile); 3950 boolean found = false; 3951 for (ElementDefinition criteriaElement : criteriaElements) { 3952 found = true; 3953 if (s.getType() == DiscriminatorType.TYPE) { 3954 String type = null; 3955 if (!criteriaElement.getPath().contains("[") && discriminator.contains("[")) { 3956 discriminator = discriminator.substring(0, discriminator.indexOf('[')); 3957 String lastNode = tail(discriminator); 3958 type = tail(criteriaElement.getPath()).substring(lastNode.length()); 3959 type = type.substring(0, 1).toLowerCase() + type.substring(1); 3960 } else if (!criteriaElement.hasType() || criteriaElement.getType().size() == 1) { 3961 if (discriminator.contains("[")) 3962 discriminator = discriminator.substring(0, discriminator.indexOf('[')); 3963 if (criteriaElement.hasType()) { 3964 type = criteriaElement.getType().get(0).getWorkingCode(); 3965 } else if (!criteriaElement.getPath().contains(".")) { 3966 type = criteriaElement.getPath(); 3967 } else { 3968 throw new DefinitionException(context.formatMessage(I18nConstants.DISCRIMINATOR__IS_BASED_ON_TYPE_BUT_SLICE__IN__HAS_NO_TYPES, discriminator, ed.getId(), profile.getUrl())); 3969 } 3970 } else if (criteriaElement.getType().size() > 1) { 3971 throw new DefinitionException(context.formatMessage(I18nConstants.DISCRIMINATOR__IS_BASED_ON_TYPE_BUT_SLICE__IN__HAS_MULTIPLE_TYPES_, discriminator, ed.getId(), profile.getUrl(), criteriaElement.typeSummary())); 3972 } else 3973 throw new DefinitionException(context.formatMessage(I18nConstants.DISCRIMINATOR__IS_BASED_ON_TYPE_BUT_SLICE__IN__HAS_NO_TYPES, discriminator, ed.getId(), profile.getUrl())); 3974 if (discriminator.isEmpty()) 3975 expression.append(" and $this is " + type); 3976 else 3977 expression.append(" and " + discriminator + " is " + type); 3978 } else if (s.getType() == DiscriminatorType.PROFILE) { 3979 if (criteriaElement.getType().size() == 0) { 3980 throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_A_TYPE__IN_PROFILE_, criteriaElement.getId(), profile.getUrl())); 3981 } 3982 if (criteriaElement.getType().size() != 1) { 3983 throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_ONLY_ONE_TYPE__IN_PROFILE_, criteriaElement.getId(), profile.getUrl())); 3984 } 3985 List<CanonicalType> list = discriminator.endsWith(".resolve()") || discriminator.equals("resolve()") ? criteriaElement.getType().get(0).getTargetProfile() : criteriaElement.getType().get(0).getProfile(); 3986 if (list.size() == 0) { 3987 throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_A_TYPE_WITH_A_PROFILE__IN_PROFILE_, criteriaElement.getId(), profile.getUrl())); 3988 } else if (list.size() > 1) { 3989 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(" or "); 3990 for (CanonicalType c : list) { 3991 b.append(discriminator + ".conformsTo('" + c.getValue() + "')"); 3992 } 3993 expression.append(" and (" + b + ")"); 3994 } else { 3995 expression.append(" and " + discriminator + ".conformsTo('" + list.get(0).getValue() + "')"); 3996 } 3997 } else if (s.getType() == DiscriminatorType.EXISTS) { 3998 if (criteriaElement.hasMin() && criteriaElement.getMin() >= 1) 3999 expression.append(" and (" + discriminator + ".exists())"); 4000 else if (criteriaElement.hasMax() && criteriaElement.getMax().equals("0")) 4001 expression.append(" and (" + discriminator + ".exists().not())"); 4002 else 4003 throw new FHIRException(context.formatMessage(I18nConstants.DISCRIMINATOR__IS_BASED_ON_ELEMENT_EXISTENCE_BUT_SLICE__NEITHER_SETS_MIN1_OR_MAX0, discriminator, ed.getId())); 4004 } else if (criteriaElement.hasFixed()) { 4005 buildFixedExpression(ed, expression, discriminator, criteriaElement); 4006 } else if (criteriaElement.hasPattern()) { 4007 buildPattternExpression(ed, expression, discriminator, criteriaElement); 4008 } else if (criteriaElement.hasBinding() && criteriaElement.getBinding().hasStrength() && criteriaElement.getBinding().getStrength().equals(BindingStrength.REQUIRED) && criteriaElement.getBinding().hasValueSet()) { 4009 expression.append(" and (" + discriminator + " memberOf '" + criteriaElement.getBinding().getValueSet() + "')"); 4010 } else { 4011 found = false; 4012 } 4013 if (found) 4014 break; 4015 } 4016 if (found) 4017 anyFound = true; 4018 } 4019 if (!anyFound) { 4020 if (slicer.getSlicing().getDiscriminator().size() > 1) 4021 throw new DefinitionException(context.formatMessage(I18nConstants.COULD_NOT_MATCH_ANY_DISCRIMINATORS__FOR_SLICE__IN_PROFILE___NONE_OF_THE_DISCRIMINATOR__HAVE_FIXED_VALUE_BINDING_OR_EXISTENCE_ASSERTIONS, discriminators, ed.getId(), profile.getUrl(), discriminators)); 4022 else 4023 throw new DefinitionException(context.formatMessage(I18nConstants.COULD_NOT_MATCH_DISCRIMINATOR__FOR_SLICE__IN_PROFILE___THE_DISCRIMINATOR__DOES_NOT_HAVE_FIXED_VALUE_BINDING_OR_EXISTENCE_ASSERTIONS, discriminators, ed.getId(), profile.getUrl(), discriminators)); 4024 } 4025 4026 try { 4027 n = fpe.parse(fixExpr(expression.toString(), null)); 4028 } catch (FHIRLexerException e) { 4029 if (STACK_TRACE) e.printStackTrace(); 4030 throw new FHIRException(context.formatMessage(I18nConstants.PROBLEM_PROCESSING_EXPRESSION__IN_PROFILE__PATH__, expression, profile.getUrl(), path, e.getMessage())); 4031 } 4032 timeTracker.fpe(t); 4033 ed.setUserData("slice.expression.cache", n); 4034 } 4035 4036 ValidatorHostContext shc = hostContext.forSlicing(); 4037 boolean pass = evaluateSlicingExpression(shc, element, path, profile, n); 4038 if (!pass) { 4039 slicingHint(sliceInfo, IssueType.STRUCTURE, element.line(), element.col(), path, false, isProfile(slicer), (context.formatMessage(I18nConstants.DOES_NOT_MATCH_SLICE_, ed.getSliceName())), "discriminator = " + Utilities.escapeXml(n.toString()), null); 4040 for (String url : shc.getSliceRecords().keySet()) { 4041 slicingHint(sliceInfo, IssueType.STRUCTURE, element.line(), element.col(), path, false, isProfile(slicer), 4042 context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, stack.getLiteralPath(), url), 4043 context.formatMessage(I18nConstants.PROFILE__DOES_NOT_MATCH_FOR__BECAUSE_OF_THE_FOLLOWING_PROFILE_ISSUES__, 4044 url, 4045 stack.getLiteralPath(), errorSummaryForSlicingAsHtml(shc.getSliceRecords().get(url))), errorSummaryForSlicingAsText(shc.getSliceRecords().get(url))); 4046 } 4047 } 4048 return pass; 4049 } 4050 4051 private boolean isProfile(ElementDefinition slicer) { 4052 if (slicer == null || !slicer.hasSlicing()) { 4053 return false; 4054 } 4055 for (ElementDefinitionSlicingDiscriminatorComponent t : slicer.getSlicing().getDiscriminator()) { 4056 if (t.getType() == DiscriminatorType.PROFILE) { 4057 return true; 4058 } 4059 } 4060 return false; 4061 } 4062 4063 public boolean evaluateSlicingExpression(ValidatorHostContext hostContext, Element element, String path, StructureDefinition profile, ExpressionNode n) throws FHIRException { 4064 String msg; 4065 boolean ok; 4066 try { 4067 long t = System.nanoTime(); 4068 ok = fpe.evaluateToBoolean(hostContext.forProfile(profile), hostContext.getResource(), hostContext.getRootResource(), element, n); 4069 timeTracker.fpe(t); 4070 msg = fpe.forLog(); 4071 } catch (Exception ex) { 4072 if (STACK_TRACE) ex.printStackTrace(); 4073 throw new FHIRException(context.formatMessage(I18nConstants.PROBLEM_EVALUATING_SLICING_EXPRESSION_FOR_ELEMENT_IN_PROFILE__PATH__FHIRPATH___, profile.getUrl(), path, n, ex.getMessage())); 4074 } 4075 return ok; 4076 } 4077 4078 private void buildPattternExpression(ElementDefinition ed, StringBuilder expression, String discriminator, ElementDefinition criteriaElement) throws DefinitionException { 4079 DataType pattern = criteriaElement.getPattern(); 4080 if (pattern instanceof CodeableConcept) { 4081 CodeableConcept cc = (CodeableConcept) pattern; 4082 expression.append(" and "); 4083 buildCodeableConceptExpression(ed, expression, discriminator, cc); 4084 } else if (pattern instanceof Coding) { 4085 Coding c = (Coding) pattern; 4086 expression.append(" and "); 4087 buildCodingExpression(ed, expression, discriminator, c); 4088 } else if (pattern instanceof BooleanType || pattern instanceof IntegerType || pattern instanceof DecimalType) { 4089 expression.append(" and "); 4090 buildPrimitiveExpression(ed, expression, discriminator, pattern, false); 4091 } else if (pattern instanceof PrimitiveType) { 4092 expression.append(" and "); 4093 buildPrimitiveExpression(ed, expression, discriminator, pattern, true); 4094 } else if (pattern instanceof Identifier) { 4095 Identifier ii = (Identifier) pattern; 4096 expression.append(" and "); 4097 buildIdentifierExpression(ed, expression, discriminator, ii); 4098 } else if (pattern instanceof HumanName) { 4099 HumanName name = (HumanName) pattern; 4100 expression.append(" and "); 4101 buildHumanNameExpression(ed, expression, discriminator, name); 4102 } else if (pattern instanceof Address) { 4103 Address add = (Address) pattern; 4104 expression.append(" and "); 4105 buildAddressExpression(ed, expression, discriminator, add); 4106 } else { 4107 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_FIXED_PATTERN_TYPE_FOR_DISCRIMINATOR_FOR_SLICE__, discriminator, ed.getId(), pattern.fhirType())); 4108 } 4109 } 4110 4111 private void buildIdentifierExpression(ElementDefinition ed, StringBuilder expression, String discriminator, Identifier ii) 4112 throws DefinitionException { 4113 if (ii.hasExtension()) 4114 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId())); 4115 boolean first = true; 4116 expression.append(discriminator + ".where("); 4117 if (ii.hasSystem()) { 4118 first = false; 4119 expression.append("system = '" + ii.getSystem() + "'"); 4120 } 4121 if (ii.hasValue()) { 4122 if (first) 4123 first = false; 4124 else 4125 expression.append(" and "); 4126 expression.append("value = '" + ii.getValue() + "'"); 4127 } 4128 if (ii.hasUse()) { 4129 if (first) 4130 first = false; 4131 else 4132 expression.append(" and "); 4133 expression.append("use = '" + ii.getUse() + "'"); 4134 } 4135 if (ii.hasType()) { 4136 if (first) 4137 first = false; 4138 else 4139 expression.append(" and "); 4140 buildCodeableConceptExpression(ed, expression, TYPE, ii.getType()); 4141 } 4142 if (first) { 4143 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), ii.fhirType())); 4144 } 4145 expression.append(").exists()"); 4146 } 4147 4148 private void buildHumanNameExpression(ElementDefinition ed, StringBuilder expression, String discriminator, HumanName name) throws DefinitionException { 4149 if (name.hasExtension()) 4150 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId())); 4151 boolean first = true; 4152 expression.append(discriminator + ".where("); 4153 if (name.hasUse()) { 4154 first = false; 4155 expression.append("use = '" + name.getUse().toCode() + "'"); 4156 } 4157 if (name.hasText()) { 4158 if (first) 4159 first = false; 4160 else 4161 expression.append(" and "); 4162 expression.append("text = '" + name.getText() + "'"); 4163 } 4164 if (name.hasFamily()) { 4165 if (first) 4166 first = false; 4167 else 4168 expression.append(" and "); 4169 expression.append("family = '" + name.getFamily() + "'"); 4170 } 4171 if (name.hasGiven()) { 4172 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), name.fhirType(), "given")); 4173 } 4174 if (name.hasPrefix()) { 4175 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), name.fhirType(), "prefix")); 4176 } 4177 if (name.hasSuffix()) { 4178 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), name.fhirType(), "suffix")); 4179 } 4180 if (name.hasPeriod()) { 4181 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), name.fhirType(), "period")); 4182 } 4183 if (first) { 4184 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), name.fhirType())); 4185 } 4186 4187 expression.append(").exists()"); 4188 } 4189 4190 private void buildAddressExpression(ElementDefinition ed, StringBuilder expression, String discriminator, Address add) throws DefinitionException { 4191 if (add.hasExtension()) { 4192 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId())); 4193 } 4194 boolean first = true; 4195 expression.append(discriminator + ".where("); 4196 if (add.hasUse()) { 4197 first = false; 4198 expression.append("use = '" + add.getUse().toCode() + "'"); 4199 } 4200 if (add.hasType()) { 4201 if (first) first = false; else expression.append(" and "); 4202 expression.append("type = '" + add.getType().toCode() + "'"); 4203 } 4204 if (add.hasText()) { 4205 if (first) first = false; else expression.append(" and "); 4206 expression.append("text = '" + add.getText() + "'"); 4207 } 4208 if (add.hasCity()) { 4209 if (first) first = false; else expression.append(" and "); 4210 expression.append("city = '" + add.getCity() + "'"); 4211 } 4212 if (add.hasDistrict()) { 4213 if (first) first = false; else expression.append(" and "); 4214 expression.append("district = '" + add.getDistrict() + "'"); 4215 } 4216 if (add.hasState()) { 4217 if (first) first = false; else expression.append(" and "); 4218 expression.append("state = '" + add.getState() + "'"); 4219 } 4220 if (add.hasPostalCode()) { 4221 if (first) first = false; else expression.append(" and "); 4222 expression.append("postalCode = '" + add.getPostalCode() + "'"); 4223 } 4224 if (add.hasCountry()) { 4225 if (first) first = false; else expression.append(" and "); 4226 expression.append("country = '" + add.getCountry() + "'"); 4227 } 4228 if (add.hasLine()) { 4229 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), add.fhirType(), "line")); 4230 } 4231 if (add.hasPeriod()) { 4232 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), add.fhirType(), "period")); 4233 } 4234 if (first) { 4235 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), add.fhirType())); 4236 } 4237 expression.append(").exists()"); 4238 } 4239 4240 private void buildCodeableConceptExpression(ElementDefinition ed, StringBuilder expression, String discriminator, CodeableConcept cc) 4241 throws DefinitionException { 4242 if (cc.hasText()) 4243 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__USING_TEXT__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId())); 4244 if (!cc.hasCoding()) 4245 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__MUST_HAVE_AT_LEAST_ONE_CODING__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId())); 4246 if (cc.hasExtension()) 4247 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId())); 4248 boolean firstCoding = true; 4249 for (Coding c : cc.getCoding()) { 4250 if (c.hasExtension()) 4251 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId())); 4252 if (firstCoding) firstCoding = false; 4253 else expression.append(" and "); 4254 expression.append(discriminator + ".coding.where("); 4255 boolean first = true; 4256 if (c.hasSystem()) { 4257 first = false; 4258 expression.append("system = '" + c.getSystem() + "'"); 4259 } 4260 if (c.hasVersion()) { 4261 if (first) first = false; 4262 else expression.append(" and "); 4263 expression.append("version = '" + c.getVersion() + "'"); 4264 } 4265 if (c.hasCode()) { 4266 if (first) first = false; 4267 else expression.append(" and "); 4268 expression.append("code = '" + c.getCode() + "'"); 4269 } 4270 if (c.hasDisplay()) { 4271 if (first) first = false; 4272 else expression.append(" and "); 4273 expression.append("display = '" + c.getDisplay() + "'"); 4274 } 4275 if (first) { 4276 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), cc.fhirType())); 4277 } 4278 expression.append(").exists()"); 4279 } 4280 } 4281 4282 private void buildCodingExpression(ElementDefinition ed, StringBuilder expression, String discriminator, Coding c) 4283 throws DefinitionException { 4284 if (c.hasExtension()) 4285 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId())); 4286 expression.append(discriminator + ".where("); 4287 boolean first = true; 4288 if (c.hasSystem()) { 4289 first = false; 4290 expression.append("system = '" + c.getSystem() + "'"); 4291 } 4292 if (c.hasVersion()) { 4293 if (first) first = false; 4294 else expression.append(" and "); 4295 expression.append("version = '" + c.getVersion() + "'"); 4296 } 4297 if (c.hasCode()) { 4298 if (first) first = false; 4299 else expression.append(" and "); 4300 expression.append("code = '" + c.getCode() + "'"); 4301 } 4302 if (c.hasDisplay()) { 4303 if (first) first = false; 4304 else expression.append(" and "); 4305 expression.append("display = '" + c.getDisplay() + "'"); 4306 } 4307 if (first) { 4308 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), c.fhirType())); 4309 } 4310 expression.append(").exists()"); 4311 } 4312 4313 private void buildPrimitiveExpression(ElementDefinition ed, StringBuilder expression, String discriminator, DataType p, boolean quotes) throws DefinitionException { 4314 if (p.hasExtension()) 4315 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId())); 4316 if (quotes) { 4317 expression.append(discriminator + ".where(value = '" + p.primitiveValue() + "'"); 4318 } else { 4319 expression.append(discriminator + ".where(value = " + p.primitiveValue() + ""); 4320 } 4321 expression.append(").exists()"); 4322 } 4323 4324 private void buildFixedExpression(ElementDefinition ed, StringBuilder expression, String discriminator, ElementDefinition criteriaElement) throws DefinitionException { 4325 DataType fixed = criteriaElement.getFixed(); 4326 if (fixed instanceof CodeableConcept) { 4327 CodeableConcept cc = (CodeableConcept) fixed; 4328 expression.append(" and "); 4329 buildCodeableConceptExpression(ed, expression, discriminator, cc); 4330 } else if (fixed instanceof Identifier) { 4331 Identifier ii = (Identifier) fixed; 4332 expression.append(" and "); 4333 buildIdentifierExpression(ed, expression, discriminator, ii); 4334 } else if (fixed instanceof Coding) { 4335 Coding c = (Coding) fixed; 4336 expression.append(" and "); 4337 buildCodingExpression(ed, expression, discriminator, c); 4338 } else { 4339 expression.append(" and ("); 4340 if (fixed instanceof StringType) { 4341 Gson gson = new Gson(); 4342 String json = gson.toJson((StringType) fixed); 4343 String escapedString = json.substring(json.indexOf(":") + 2); 4344 escapedString = escapedString.substring(0, escapedString.indexOf(",\"myStringValue") - 1); 4345 expression.append("'" + escapedString + "'"); 4346 } else if (fixed instanceof UriType) { 4347 expression.append("'" + ((UriType) fixed).asStringValue() + "'"); 4348 } else if (fixed instanceof IntegerType) { 4349 expression.append(((IntegerType) fixed).asStringValue()); 4350 } else if (fixed instanceof DecimalType) { 4351 expression.append(((IntegerType) fixed).asStringValue()); 4352 } else if (fixed instanceof BooleanType) { 4353 expression.append(((BooleanType) fixed).asStringValue()); 4354 } else 4355 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_FIXED_VALUE_TYPE_FOR_DISCRIMINATOR_FOR_SLICE__, discriminator, ed.getId(), fixed.getClass().getName())); 4356 expression.append(" in " + discriminator + ")"); 4357 } 4358 } 4359 4360 // checkSpecials = we're only going to run these tests if we are actually validating this content (as opposed to we looked it up) 4361 private void start(ValidatorHostContext hostContext, List<ValidationMessage> errors, Element resource, Element element, StructureDefinition defn, NodeStack stack) throws FHIRException { 4362 checkLang(resource, stack); 4363 if (crumbTrails) { 4364 element.addMessage(signpost(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST, defn.getUrl())); 4365 } 4366 4367 if (BUNDLE.equals(element.fhirType())) { 4368 resolveBundleReferences(element, new ArrayList<Element>()); 4369 } 4370 startInner(hostContext, errors, resource, element, defn, stack, hostContext.isCheckSpecials()); 4371 4372 Element meta = element.getNamedChild(META); 4373 if (meta != null) { 4374 List<Element> profiles = new ArrayList<Element>(); 4375 meta.getNamedChildren("profile", profiles); 4376 int i = 0; 4377 for (Element profile : profiles) { 4378 StructureDefinition sd = context.fetchResource(StructureDefinition.class, profile.primitiveValue()); 4379 if (!defn.getUrl().equals(profile.primitiveValue())) { 4380 // is this a version specific reference? 4381 VersionURLInfo vu = VersionUtilities.parseVersionUrl(profile.primitiveValue()); 4382 if (vu != null) { 4383 if (!VersionUtilities.versionsCompatible(vu.getVersion(), context.getVersion())) { 4384 hint(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_OTHER_VERSION, vu.getVersion()); 4385 } else if (vu.getUrl().equals(defn.getUrl())) { 4386 hint(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_THIS_VERSION_OK); 4387 } else { 4388 StructureDefinition sdt = context.fetchResource(StructureDefinition.class, vu.getUrl()); 4389 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_THIS_VERSION_OTHER, sdt == null ? "null" : sdt.getType()); 4390 } 4391 } else { 4392 if (sd == null) { 4393 // we'll try fetching it directly from it's source, but this is likely to fail later even if the resolution succeeds 4394 if (fetcher == null) { 4395 warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN, profile.primitiveValue()); 4396 } else if (!fetcher.fetchesCanonicalResource(this, profile.primitiveValue())) { 4397 warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN_NOT_POLICY, profile.primitiveValue()); 4398 } else { 4399 try { 4400 sd = (StructureDefinition) fetcher.fetchCanonicalResource(this, profile.primitiveValue()); 4401 } catch (Exception e) { 4402 if (STACK_TRACE) e.printStackTrace(); 4403 warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN_ERROR, profile.primitiveValue(), e.getMessage()); 4404 } 4405 if (sd != null) { 4406 context.cacheResource(sd); 4407 } 4408 } 4409 } 4410 if (sd != null) { 4411 if (crumbTrails) { 4412 element.addMessage(signpost(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST_META, sd.getUrl())); 4413 } 4414 stack.resetIds(); 4415 startInner(hostContext, errors, resource, element, sd, stack, false); 4416 } 4417 } 4418 } 4419 i++; 4420 } 4421 } 4422 String rt = element.fhirType(); 4423 for (ImplementationGuide ig : igs) { 4424 for (ImplementationGuideGlobalComponent gl : ig.getGlobal()) { 4425 if (rt.equals(gl.getType())) { 4426 StructureDefinition sd = context.fetchResource(StructureDefinition.class, gl.getProfile()); 4427 if (warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), sd != null, I18nConstants.VALIDATION_VAL_GLOBAL_PROFILE_UNKNOWN, gl.getProfile())) { 4428 if (crumbTrails) { 4429 element.addMessage(signpost(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST_GLOBAL, sd.getUrl(), ig.getUrl())); 4430 } 4431 stack.resetIds(); 4432 startInner(hostContext, errors, resource, element, sd, stack, false); 4433 } 4434 } 4435 } 4436 } 4437 } 4438 4439 private void resolveBundleReferences(Element element, List<Element> bundles) { 4440 if (!element.hasUserData("validator.bundle.resolved")) { 4441 element.setUserData("validator.bundle.resolved", true); 4442 List<Element> list = new ArrayList<Element>(); 4443 list.addAll(bundles); 4444 list.add(0, element); 4445 List<Element> entries = element.getChildrenByName(ENTRY); 4446 for (Element entry : entries) { 4447 String fu = entry.getChildValue(FULL_URL); 4448 Element r = entry.getNamedChild(RESOURCE); 4449 if (r != null) { 4450 resolveBundleReferencesInResource(list, r, fu); 4451 } 4452 } 4453 } 4454 } 4455 4456 private void resolveBundleReferencesInResource(List<Element> bundles, Element r, String fu) { 4457 r.setUserData("validator.bundle.resolution-resource", null); 4458 if (BUNDLE.equals(r.fhirType())) { 4459 resolveBundleReferences(r, bundles); 4460 } else { 4461 for (Element child : r.getChildren()) { 4462 resolveBundleReferencesForElement(bundles, r, fu, child); 4463 } 4464 } 4465 } 4466 4467 private void resolveBundleReferencesForElement(List<Element> bundles, Element resource, String fu, Element element) { 4468 if ("Reference".equals(element.fhirType())) { 4469 String ref = element.getChildValue("reference"); 4470 if (!Utilities.noString(ref)) { 4471 for (Element bundle : bundles) { 4472 List<Element> entries = bundle.getChildren(ENTRY); 4473 Element tgt = resolveInBundle(entries, ref, fu, resource.fhirType(), resource.getIdBase()); 4474 if (tgt != null) { 4475 element.setUserData("validator.bundle.resolution", tgt.getNamedChild(RESOURCE)); 4476 return; 4477 } 4478 } 4479 element.setUserData("validator.bundle.resolution-failed", ref); 4480 } 4481 } else { 4482 element.setUserData("validator.bundle.resolution-noref", null); 4483 for (Element child : element.getChildren()) { 4484 resolveBundleReferencesForElement(bundles, resource, fu, child); 4485 } 4486 } 4487 4488 } 4489 4490 public void startInner(ValidatorHostContext hostContext, List<ValidationMessage> errors, Element resource, Element element, StructureDefinition defn, NodeStack stack, boolean checkSpecials) { 4491 // the first piece of business is to see if we've validated this resource against this profile before. 4492 // if we have (*or if we still are*), then we'll just return our existing errors 4493 ResourceValidationTracker resTracker = getResourceTracker(element); 4494 List<ValidationMessage> cachedErrors = resTracker.getOutcomes(defn); 4495 if (cachedErrors != null) { 4496 for (ValidationMessage vm : cachedErrors) { 4497 if (!errors.contains(vm)) { 4498 errors.add(vm); 4499 } 4500 } 4501 return; 4502 } 4503 if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), defn.hasSnapshot(), I18nConstants.VALIDATION_VAL_PROFILE_NOSNAPSHOT, defn.getUrl())) { 4504 List<ValidationMessage> localErrors = new ArrayList<ValidationMessage>(); 4505 resTracker.startValidating(defn); 4506 trackUsage(defn, hostContext, element); 4507 validateElement(hostContext, localErrors, defn, defn.getSnapshot().getElement().get(0), null, null, resource, element, element.getName(), stack, false, true, null); 4508 resTracker.storeOutcomes(defn, localErrors); 4509 for (ValidationMessage vm : localErrors) { 4510 if (!errors.contains(vm)) { 4511 errors.add(vm); 4512 } 4513 } 4514 } 4515 if (checkSpecials) { 4516 checkSpecials(hostContext, errors, element, stack, checkSpecials); 4517 validateResourceRules(errors, element, stack); 4518 } 4519 } 4520 4521 public void checkSpecials(ValidatorHostContext hostContext, List<ValidationMessage> errors, Element element, NodeStack stack, boolean checkSpecials) { 4522 // specific known special validations 4523 if (element.getType().equals(BUNDLE)) { 4524 new BundleValidator(context, serverBase, this, xverManager).validateBundle(errors, element, stack, checkSpecials, hostContext); 4525 } else if (element.getType().equals("Observation")) { 4526 validateObservation(errors, element, stack); 4527 } else if (element.getType().equals("Questionnaire")) { 4528 new QuestionnaireValidator(context, myEnableWhenEvaluator, fpe, timeTracker, questionnaireMode, xverManager).validateQuestionannaire(errors, element, element, stack); 4529 } else if (element.getType().equals("QuestionnaireResponse")) { 4530 new QuestionnaireValidator(context, myEnableWhenEvaluator, fpe, timeTracker, questionnaireMode, xverManager).validateQuestionannaireResponse(hostContext, errors, element, stack); 4531 } else if (element.getType().equals("Measure")) { 4532 new MeasureValidator(context, timeTracker, xverManager).validateMeasure(hostContext, errors, element, stack); 4533 } else if (element.getType().equals("MeasureReport")) { 4534 new MeasureValidator(context, timeTracker, xverManager).validateMeasureReport(hostContext, errors, element, stack); 4535 } else if (element.getType().equals("CapabilityStatement")) { 4536 validateCapabilityStatement(errors, element, stack); 4537 } else if (element.getType().equals("CodeSystem")) { 4538 new CodeSystemValidator(context, timeTracker, xverManager).validateCodeSystem(errors, element, stack, baseOptions.setLanguage(stack.getWorkingLang())); 4539 } else if (element.getType().equals("SearchParameter")) { 4540 new SearchParameterValidator(context, timeTracker, fpe, xverManager).validateSearchParameter(errors, element, stack); 4541 } else if (element.getType().equals("StructureDefinition")) { 4542 new StructureDefinitionValidator(context, timeTracker, fpe, wantCheckSnapshotUnchanged, xverManager).validateStructureDefinition(errors, element, stack); 4543 } else if (element.getType().equals("ValueSet")) { 4544 new ValueSetValidator(context, timeTracker, this, xverManager).validateValueSet(errors, element, stack); 4545 } 4546 } 4547 4548 private ResourceValidationTracker getResourceTracker(Element element) { 4549 ResourceValidationTracker res = resourceTracker.get(element); 4550 if (res == null) { 4551 res = new ResourceValidationTracker(); 4552 resourceTracker.put(element, res); 4553 } 4554 return res; 4555 } 4556 4557 private void checkLang(Element resource, NodeStack stack) { 4558 String lang = resource.getNamedChildValue("language"); 4559 if (!Utilities.noString(lang)) 4560 stack.setWorkingLang(lang); 4561 } 4562 4563 private void validateResourceRules(List<ValidationMessage> errors, Element element, NodeStack stack) { 4564 String lang = element.getNamedChildValue("language"); 4565 Element text = element.getNamedChild("text"); 4566 if (text != null) { 4567 Element div = text.getNamedChild("div"); 4568 if (lang != null && div != null) { 4569 XhtmlNode xhtml = div.getXhtml(); 4570 String l = xhtml.getAttribute("lang"); 4571 String xl = xhtml.getAttribute("xml:lang"); 4572 if (l == null && xl == null) { 4573 warning(errors, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, I18nConstants.LANGUAGE_XHTML_LANG_MISSING1); 4574 } else { 4575 if (l == null) { 4576 warning(errors, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, I18nConstants.LANGUAGE_XHTML_LANG_MISSING2); 4577 } else if (!l.equals(lang)) { 4578 warning(errors, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, I18nConstants.LANGUAGE_XHTML_LANG_DIFFERENT1, lang, l); 4579 } 4580 if (xl == null) { 4581 warning(errors, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, I18nConstants.LANGUAGE_XHTML_LANG_MISSING3); 4582 } else if (!xl.equals(lang)) { 4583 warning(errors, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, I18nConstants.LANGUAGE_XHTML_LANG_DIFFERENT2, lang, xl); 4584 } 4585 } 4586 } 4587 } 4588 // security tags are a set (system|code) 4589 Element meta = element.getNamedChild(META); 4590 if (meta != null) { 4591 Set<String> tags = new HashSet<>(); 4592 List<Element> list = new ArrayList<>(); 4593 meta.getNamedChildren("security", list); 4594 int i = 0; 4595 for (Element e : list) { 4596 String s = e.getNamedChildValue("system") + "#" + e.getNamedChildValue("code"); 4597 rule(errors, IssueType.BUSINESSRULE, e.line(), e.col(), stack.getLiteralPath() + ".meta.profile[" + Integer.toString(i) + "]", !tags.contains(s), I18nConstants.META_RES_SECURITY_DUPLICATE, s); 4598 tags.add(s); 4599 i++; 4600 } 4601 } 4602 } 4603 4604 private void validateCapabilityStatement(List<ValidationMessage> errors, Element cs, NodeStack stack) { 4605 int iRest = 0; 4606 for (Element rest : cs.getChildrenByName("rest")) { 4607 int iResource = 0; 4608 for (Element resource : rest.getChildrenByName(RESOURCE)) { 4609 int iSP = 0; 4610 for (Element searchParam : resource.getChildrenByName("searchParam")) { 4611 String ref = searchParam.getChildValue("definition"); 4612 String type = searchParam.getChildValue(TYPE); 4613 if (!Utilities.noString(ref)) { 4614 SearchParameter sp = context.fetchResource(SearchParameter.class, ref); 4615 if (sp != null) { 4616 rule(errors, IssueType.INVALID, searchParam.line(), searchParam.col(), stack.getLiteralPath() + ".rest[" + iRest + "].resource[" + iResource + "].searchParam[" + iSP + "]", 4617 sp.getType().toCode().equals(type), I18nConstants.CAPABALITYSTATEMENT_CS_SP_WRONGTYPE, sp.getUrl(), sp.getType().toCode(), type); 4618 } 4619 } 4620 iSP++; 4621 } 4622 iResource++; 4623 } 4624 iRest++; 4625 } 4626 } 4627 4628 private void validateContains(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, 4629 ElementDefinition child, ElementDefinition context, Element resource, 4630 Element element, NodeStack stack, IdStatus idstatus, StructureDefinition parentProfile) throws FHIRException { 4631 4632 SpecialElement special = element.getSpecial(); 4633 4634 ContainedReferenceValidationPolicy containedValidationPolicy = getPolicyAdvisor() == null ? 4635 ContainedReferenceValidationPolicy.CHECK_VALID : getPolicyAdvisor().policyForContained(this, 4636 hostContext, context.fhirType(), context.getId(), special, path, parentProfile.getUrl()); 4637 4638 if (containedValidationPolicy.ignore()) { 4639 return; 4640 } 4641 4642 String resourceName = element.getType(); 4643 TypeRefComponent typeForResource = null; 4644 CommaSeparatedStringBuilder bt = new CommaSeparatedStringBuilder(); 4645 4646 // Iterate through all possible types 4647 for (TypeRefComponent type : child.getType()) { 4648 bt.append(type.getCode()); 4649 if (type.getCode().equals("Resource") || type.getCode().equals(resourceName) ) { 4650 typeForResource = type; 4651 break; 4652 } 4653 } 4654 4655 stack.qualifyPath(".ofType("+resourceName+")"); 4656 4657 if (typeForResource == null) { 4658 rule(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), 4659 false, I18nConstants.BUNDLE_BUNDLE_ENTRY_TYPE, resourceName, bt.toString()); 4660 } else if (isValidResourceType(resourceName, typeForResource)) { 4661 if (containedValidationPolicy.checkValid()) { 4662 // special case: resource wrapper is reset if we're crossing a bundle boundary, but not otherwise 4663 ValidatorHostContext hc = null; 4664 if (special == SpecialElement.BUNDLE_ENTRY || special == SpecialElement.BUNDLE_OUTCOME || special == SpecialElement.PARAMETER) { 4665 resource = element; 4666 assert Utilities.existsInList(hostContext.getRootResource().fhirType(), "Bundle", "Parameters") : "Resource is "+hostContext.getRootResource().fhirType()+", expected Bundle or Parameters"; 4667 hc = hostContext.forEntry(element, hostContext.getRootResource()); // root becomes the grouping resource (should be either bundle or parameters) 4668 } else { 4669 hc = hostContext.forContained(element); 4670 } 4671 4672 stack.resetIds(); 4673 if (special != null) { 4674 switch (special) { 4675 case BUNDLE_ENTRY: 4676 case BUNDLE_OUTCOME: 4677 case PARAMETER: 4678 idstatus = IdStatus.OPTIONAL; 4679 break; 4680 case CONTAINED: 4681 stack.setContained(true); 4682 idstatus = IdStatus.REQUIRED; 4683 break; 4684 default: 4685 break; 4686 } 4687 } 4688 4689 if (typeForResource.getProfile().size() == 1) { 4690 long t = System.nanoTime(); 4691 StructureDefinition profile = this.context.fetchResource(StructureDefinition.class, typeForResource.getProfile().get(0).asStringValue()); 4692 timeTracker.sd(t); 4693 trackUsage(profile, hostContext, element); 4694 if (rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), 4695 profile != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOPROFILE_EXPL, special.toHuman(), resourceName, typeForResource.getProfile().get(0).asStringValue())) { 4696 validateResource(hc, errors, resource, element, profile, idstatus, stack); 4697 } 4698 } else if (typeForResource.getProfile().isEmpty()) { 4699 long t = System.nanoTime(); 4700 StructureDefinition profile = this.context.fetchResource(StructureDefinition.class, 4701 "http://hl7.org/fhir/StructureDefinition/" + resourceName); 4702 timeTracker.sd(t); 4703 trackUsage(profile, hostContext, element); 4704 if (rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), 4705 profile != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOPROFILE_TYPE, special == null ? "??" : special.toHuman(), resourceName)) { 4706 validateResource(hc, errors, resource, element, profile, idstatus, stack); 4707 } 4708 } else { 4709 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 4710 for (CanonicalType u : typeForResource.getProfile()) { 4711 b.append(u.asStringValue()); 4712 } 4713 rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), 4714 false, I18nConstants.BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES, special.toHuman(), typeForResource.getCode(), b.toString()); 4715 } 4716 } 4717 } else { 4718 List<String> types = new ArrayList<>(); 4719 for (UriType u : typeForResource.getProfile()) { 4720 StructureDefinition sd = this.context.fetchResource(StructureDefinition.class, u.getValue()); 4721 if (sd != null && !types.contains(sd.getType())) { 4722 types.add(sd.getType()); 4723 } 4724 } 4725 if (types.size() == 1) { 4726 rule(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), 4727 false, I18nConstants.BUNDLE_BUNDLE_ENTRY_TYPE2, resourceName, types.get(0)); 4728 } else { 4729 rule(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), 4730 false, I18nConstants.BUNDLE_BUNDLE_ENTRY_TYPE3, resourceName, types); 4731 } 4732 } 4733 } 4734 4735 private boolean isValidResourceType(String type, TypeRefComponent def) { 4736 if (!def.hasProfile() && def.getCode().equals("Resource")) { 4737 return true; 4738 } 4739 if (def.getCode().equals(type)) { 4740 return true; 4741 } 4742 List<StructureDefinition> list = new ArrayList<>(); 4743 for (UriType u : def.getProfile()) { 4744 StructureDefinition sdt = context.fetchResource(StructureDefinition.class, u.getValue()); 4745 if (sdt != null) { 4746 list.add(sdt); 4747 } 4748 } 4749 4750 StructureDefinition sdt = context.fetchTypeDefinition(type); 4751 while (sdt != null) { 4752 if (def.getWorkingCode().equals("Resource")) { 4753 for (StructureDefinition sd : list) { 4754 if (sd.getUrl().equals(sdt.getUrl())) { 4755 return true; 4756 } 4757 if (sd.getType().equals(sdt.getType())) { 4758 return true; 4759 } 4760 } 4761 } 4762 sdt = context.fetchResource(StructureDefinition.class, sdt.getBaseDefinition()); 4763 } 4764 return false; 4765 } 4766 4767 4768 private void validateElement(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition, StructureDefinition cprofile, ElementDefinition context, 4769 Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept, boolean checkDisplayInContext, String extensionUrl) throws FHIRException { 4770 4771 String id = element.getChildValue("id"); 4772 if (!Utilities.noString(id)) { 4773 if (stack.getIds().containsKey(id) && stack.getIds().get(id) != element) { 4774 rule(errors, IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.DUPLICATE_ID, id); 4775 } 4776 if (!stack.isResetPoint()) { 4777 stack.getIds().put(id, element); 4778 } 4779 } 4780 if (definition.getPath().equals("StructureDefinition.snapshot")) { 4781 // work around a known issue in the spec, that ids are duplicated in snapshot and differential 4782 stack.resetIds(); 4783 } 4784 4785 // check type invariants 4786 checkInvariants(hostContext, errors, profile, definition, resource, element, stack, false); 4787 if (definition.getFixed() != null) { 4788 checkFixedValue(errors, stack.getLiteralPath(), element, definition.getFixed(), profile.getUrl(), definition.getSliceName(), null, false); 4789 } 4790 if (definition.getPattern() != null) { 4791 checkFixedValue(errors, stack.getLiteralPath(), element, definition.getPattern(), profile.getUrl(), definition.getSliceName(), null, true); 4792 } 4793 4794 // get the list of direct defined children, including slices 4795 List<ElementDefinition> childDefinitions = profileUtilities.getChildMap(profile, definition); 4796 if (childDefinitions.isEmpty()) { 4797 if (actualType == null) 4798 return; // there'll be an error elsewhere in this case, and we're going to stop. 4799 childDefinitions = getActualTypeChildren(hostContext, element, actualType); 4800 } else if (definition.getType().size() > 1) { 4801 // this only happens when the profile constrains the abstract children but leaves th choice open. 4802 if (actualType == null) 4803 return; // there'll be an error elsewhere in this case, and we're going to stop. 4804 List<ElementDefinition> typeChildDefinitions = getActualTypeChildren(hostContext, element, actualType); 4805 // what were going to do is merge them - the type is not allowed to constrain things that the child definitions already do (well, if it does, it'll be ignored) 4806 mergeChildLists(childDefinitions, typeChildDefinitions, definition.getPath(), actualType); 4807 } 4808 4809 List<ElementInfo> children = listChildren(element, stack); 4810 List<String> problematicPaths = assignChildren(hostContext, errors, profile, resource, stack, childDefinitions, children); 4811 4812 checkCardinalities(errors, profile, element, stack, childDefinitions, children, problematicPaths); 4813 // 4. check order if any slices are ordered. (todo) 4814 4815 // 5. inspect each child for validity 4816 for (ElementInfo ei : children) { 4817 checkChild(hostContext, errors, profile, definition, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, ei, extensionUrl); 4818 } 4819 } 4820 4821 private void mergeChildLists(List<ElementDefinition> master, List<ElementDefinition> additional, String masterPath, String typePath) { 4822 for (ElementDefinition ed : additional) { 4823 boolean inMaster = false; 4824 for (ElementDefinition t : master) { 4825 String tp = masterPath + ed.getPath().substring(typePath.length()); 4826 if (t.getPath().equals(tp)) { 4827 inMaster = true; 4828 } 4829 } 4830 if (!inMaster) { 4831 master.add(ed); 4832 } 4833 } 4834 4835 4836 } 4837 4838 // todo: the element definition in context might assign a constrained profile for the type? 4839 public List<ElementDefinition> getActualTypeChildren(ValidatorHostContext hostContext, Element element, String actualType) { 4840 List<ElementDefinition> childDefinitions; 4841 StructureDefinition dt = null; 4842 if (isAbsolute(actualType)) 4843 dt = this.context.fetchResource(StructureDefinition.class, actualType); 4844 else 4845 dt = this.context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + actualType); 4846 if (dt == null) 4847 throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_ACTUAL_TYPE_, actualType)); 4848 trackUsage(dt, hostContext, element); 4849 4850 childDefinitions = profileUtilities.getChildMap(dt, dt.getSnapshot().getElement().get(0)); 4851 return childDefinitions; 4852 } 4853 4854 public void checkChild(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition, 4855 Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept, boolean checkDisplayInContext, ElementInfo ei, String extensionUrl) 4856 throws FHIRException, DefinitionException { 4857 4858 if (debug && ei.definition != null && ei.slice != null) { 4859 System.out.println(Utilities.padLeft("", ' ', stack.depth())+ "Check "+ei.getPath()+" against both "+ei.definition.getId()+" and "+ei.slice.getId()); 4860 } 4861 if (ei.definition != null) { 4862 if (debug) { 4863 System.out.println(Utilities.padLeft("", ' ', stack.depth())+ "Check "+ei.getPath()+" against defn "+ei.definition.getId()+" from "+profile.getUrl()); 4864 } 4865 checkChildByDefinition(hostContext, errors, profile, definition, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, ei, extensionUrl, ei.definition, false); 4866 } 4867 if (ei.slice != null) { 4868 if (debug) { 4869 System.out.println(Utilities.padLeft("", ' ', stack.depth())+ "Check "+ei.getPath()+" against slice "+ei.slice.getId()); 4870 } 4871 checkChildByDefinition(hostContext, errors, profile, definition, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, ei, extensionUrl, ei.slice, true); 4872 } 4873 } 4874 4875 public void checkChildByDefinition(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, 4876 ElementDefinition definition, Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept, 4877 boolean checkDisplayInContext, ElementInfo ei, String extensionUrl, ElementDefinition checkDefn, boolean isSlice) { 4878 List<String> profiles = new ArrayList<String>(); 4879 String type = null; 4880 ElementDefinition typeDefn = null; 4881 checkMustSupport(profile, ei); 4882 4883 if (checkDefn.getType().size() == 1 && !"*".equals(checkDefn.getType().get(0).getWorkingCode()) && !"Element".equals(checkDefn.getType().get(0).getWorkingCode()) 4884 && !"BackboneElement".equals(checkDefn.getType().get(0).getWorkingCode())) { 4885 type = checkDefn.getType().get(0).getWorkingCode(); 4886 String stype = ei.getElement().fhirType(); 4887 if (checkDefn.isChoice() && !stype.equals(type)) { 4888 if ("Extension".equals(profile.getType())) { 4889 // error will be raised elsewhere 4890 } else { 4891 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), ei.getPath(), false, I18nConstants.EXTENSION_PROF_TYPE, profile.getUrl(), type, stype); 4892 } 4893 } 4894 4895 // Excluding reference is a kludge to get around versioning issues 4896 if (checkDefn.getType().get(0).hasProfile()) { 4897 for (CanonicalType p : checkDefn.getType().get(0).getProfile()) { 4898 profiles.add(p.getValue()); 4899 } 4900 } 4901 } else if (checkDefn.getType().size() == 1 && "*".equals(checkDefn.getType().get(0).getWorkingCode())) { 4902 String prefix = tail(checkDefn.getPath()); 4903 assert prefix.endsWith("[x]"); 4904 type = ei.getName().substring(prefix.length() - 3); 4905 if (isPrimitiveType(type)) 4906 type = Utilities.uncapitalize(type); 4907 if (checkDefn.getType().get(0).hasProfile()) { 4908 for (CanonicalType p : checkDefn.getType().get(0).getProfile()) { 4909 profiles.add(p.getValue()); 4910 } 4911 } 4912 } else if (checkDefn.getType().size() > 1) { 4913 4914 String prefix = tail(checkDefn.getPath()); 4915 assert typesAreAllReference(checkDefn.getType()) || checkDefn.hasRepresentation(PropertyRepresentation.TYPEATTR) || prefix.endsWith("[x]") || isResourceAndTypes(checkDefn) : "Multiple Types allowed, but name is wrong @ "+checkDefn.getPath()+": "+checkDefn.typeSummaryVB(); 4916 4917 if (checkDefn.hasRepresentation(PropertyRepresentation.TYPEATTR)) { 4918 type = ei.getElement().getType(); 4919 } else if (ei.getElement().isResource()) { 4920 type = ei.getElement().fhirType(); 4921 } else { 4922 prefix = prefix.substring(0, prefix.length() - 3); 4923 for (TypeRefComponent t : checkDefn.getType()) 4924 if ((prefix + Utilities.capitalize(t.getWorkingCode())).equals(ei.getName())) { 4925 type = t.getWorkingCode(); 4926 // Excluding reference is a kludge to get around versioning issues 4927 if (t.hasProfile() && !type.equals("Reference")) 4928 profiles.add(t.getProfile().get(0).getValue()); 4929 } 4930 } 4931 if (type == null) { 4932 TypeRefComponent trc = checkDefn.getType().get(0); 4933 if (trc.getWorkingCode().equals("Reference")) 4934 type = "Reference"; 4935 else 4936 rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), stack.getLiteralPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_NOTYPE, ei.getName(), describeTypes(checkDefn.getType())); 4937 } 4938 } else if (checkDefn.getContentReference() != null) { 4939 typeDefn = resolveNameReference(profile.getSnapshot(), checkDefn.getContentReference()); 4940 4941 } else if (checkDefn.getType().size() == 1 && ("Element".equals(checkDefn.getType().get(0).getWorkingCode()) || "BackboneElement".equals(checkDefn.getType().get(0).getWorkingCode()))) { 4942 if (checkDefn.getType().get(0).hasProfile()) { 4943 CanonicalType pu = checkDefn.getType().get(0).getProfile().get(0); 4944 if (pu.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT)) 4945 profiles.add(pu.getValue() + "#" + pu.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT)); 4946 else 4947 profiles.add(pu.getValue()); 4948 } 4949 } 4950 4951 if (type != null) { 4952 if (type.startsWith("@")) { 4953 checkDefn = findElement(profile, type.substring(1)); 4954 if (isSlice) { 4955 ei.slice = ei.definition; 4956 } else { 4957 ei.definition = ei.definition; 4958 } 4959 type = null; 4960 } 4961 } 4962 NodeStack localStack = stack.push(ei.getElement(), "*".equals(ei.getDefinition().getBase().getMax()) && ei.count == -1 ? 0 : ei.count, checkDefn, type == null ? typeDefn : resolveType(type, checkDefn.getType())); 4963// if (debug) { 4964// System.out.println(" check " + localStack.getLiteralPath()+" against "+ei.getDefinition().getId()+" in profile "+profile.getUrl()); 4965// } 4966 String localStackLiteralPath = localStack.getLiteralPath(); 4967 String eiPath = ei.getPath(); 4968 if (!eiPath.equals(localStackLiteralPath)) { 4969 assert (eiPath.equals(localStackLiteralPath)) : "ei.path: " + ei.getPath() + " - localStack.getLiteralPath: " + localStackLiteralPath; 4970 } 4971 boolean thisIsCodeableConcept = false; 4972 String thisExtension = null; 4973 boolean checkDisplay = true; 4974 4975 SpecialElement special = ei.getElement().getSpecial(); 4976 if (special == SpecialElement.BUNDLE_ENTRY || special == SpecialElement.BUNDLE_OUTCOME || special == SpecialElement.PARAMETER) { 4977 checkInvariants(hostContext, errors, profile, typeDefn != null ? typeDefn : checkDefn, ei.getElement(), ei.getElement(), localStack, false); 4978 } else { 4979 checkInvariants(hostContext, errors, profile, typeDefn != null ? typeDefn : checkDefn, resource, ei.getElement(), localStack, false); 4980 } 4981 4982 ei.getElement().markValidation(profile, checkDefn); 4983 boolean elementValidated = false; 4984 if (type != null) { 4985 if (isPrimitiveType(type)) { 4986 checkPrimitive(hostContext, errors, ei.getPath(), type, checkDefn, ei.getElement(), profile, stack); 4987 } else { 4988 if (checkDefn.hasFixed()) { 4989 checkFixedValue(errors, ei.getPath(), ei.getElement(), checkDefn.getFixed(), profile.getUrl(), checkDefn.getSliceName(), null, false); 4990 } 4991 if (checkDefn.hasPattern()) { 4992 checkFixedValue(errors, ei.getPath(), ei.getElement(), checkDefn.getPattern(), profile.getUrl(), checkDefn.getSliceName(), null, true); 4993 } 4994 } 4995 if (type.equals("Identifier")) { 4996 checkIdentifier(errors, ei.getPath(), ei.getElement(), checkDefn); 4997 } else if (type.equals("Coding")) { 4998 checkCoding(errors, ei.getPath(), ei.getElement(), profile, checkDefn, inCodeableConcept, checkDisplayInContext, stack); 4999 } else if (type.equals("Quantity")) { 5000 checkQuantity(errors, ei.getPath(), ei.getElement(), profile, checkDefn, stack); 5001 } else if (type.equals("Attachment")) { 5002 checkAttachment(errors, ei.getPath(), ei.getElement(), profile, checkDefn, inCodeableConcept, checkDisplayInContext, stack); 5003 } else if (type.equals("CodeableConcept")) { 5004 checkDisplay = checkCodeableConcept(errors, ei.getPath(), ei.getElement(), profile, checkDefn, stack); 5005 thisIsCodeableConcept = true; 5006 } else if (type.equals("Reference")) { 5007 checkReference(hostContext, errors, ei.getPath(), ei.getElement(), profile, checkDefn, actualType, localStack); 5008 // We only check extensions if we're not in a complex extension or if the element we're dealing with is not defined as part of that complex extension 5009 } else if (type.equals("Extension")) { 5010 Element eurl = ei.getElement().getNamedChild("url"); 5011 if (rule(errors, IssueType.INVALID, ei.getPath(), eurl != null, I18nConstants.EXTENSION_EXT_URL_NOTFOUND)) { 5012 String url = eurl.primitiveValue(); 5013 thisExtension = url; 5014 if (rule(errors, IssueType.INVALID, ei.getPath(), !Utilities.noString(url), I18nConstants.EXTENSION_EXT_URL_NOTFOUND)) { 5015 if (rule(errors, IssueType.INVALID, ei.getPath(), (extensionUrl != null) || Utilities.isAbsoluteUrl(url), I18nConstants.EXTENSION_EXT_URL_ABSOLUTE)) { 5016 checkExtension(hostContext, errors, ei.getPath(), resource, element, ei.getElement(), checkDefn, profile, localStack, stack, extensionUrl); 5017 } 5018 } 5019 } 5020 } else if (type.equals("Resource") || isResource(type)) { 5021 validateContains(hostContext, errors, ei.getPath(), checkDefn, definition, resource, ei.getElement(), 5022 localStack, idStatusForEntry(element, ei), profile); // if 5023 elementValidated = true; 5024 // (str.matches(".*([.,/])work\\1$")) 5025 } else if (Utilities.isAbsoluteUrl(type)) { 5026 StructureDefinition defn = context.fetchTypeDefinition(type); 5027 if (defn != null && hasMapping("http://hl7.org/fhir/terminology-pattern", defn, defn.getSnapshot().getElementFirstRep())) { 5028 List<String> txtype = getMapping("http://hl7.org/fhir/terminology-pattern", defn, defn.getSnapshot().getElementFirstRep()); 5029 if (txtype.contains("CodeableConcept")) { 5030 checkTerminologyCodeableConcept(errors, ei.getPath(), ei.getElement(), profile, checkDefn, stack, defn); 5031 thisIsCodeableConcept = true; 5032 } else if (txtype.contains("Coding")) { 5033 checkTerminologyCoding(errors, ei.getPath(), ei.getElement(), profile, checkDefn, inCodeableConcept, checkDisplayInContext, stack, defn); 5034 } 5035 } 5036 } 5037 } else { 5038 if (rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), stack.getLiteralPath(), checkDefn != null, I18nConstants.VALIDATION_VAL_CONTENT_UNKNOWN, ei.getName())) 5039 validateElement(hostContext, errors, profile, checkDefn, null, null, resource, ei.getElement(), type, localStack, false, true, null); 5040 } 5041 StructureDefinition p = null; 5042 String tail = null; 5043 if (profiles.isEmpty()) { 5044 if (type != null) { 5045 p = getProfileForType(type, checkDefn.getType()); 5046 5047 // If dealing with a primitive type, then we need to check the current child against 5048 // the invariants (constraints) on the current element, because otherwise it only gets 5049 // checked against the primary type's invariants: LLoyd 5050 //if (p.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { 5051 // checkInvariants(hostContext, errors, ei.path, profile, ei.definition, null, null, resource, ei.element); 5052 //} 5053 5054 rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.getPath(), p != null, I18nConstants.VALIDATION_VAL_NOTYPE, type); 5055 } 5056 } else if (profiles.size() == 1) { 5057 String url = profiles.get(0); 5058 if (url.contains("#")) { 5059 tail = url.substring(url.indexOf("#") + 1); 5060 url = url.substring(0, url.indexOf("#")); 5061 } 5062 p = this.context.fetchResource(StructureDefinition.class, url); 5063 rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.getPath(), p != null, I18nConstants.VALIDATION_VAL_UNKNOWN_PROFILE, profiles.get(0)); 5064 } else { 5065 elementValidated = true; 5066 HashMap<String, List<ValidationMessage>> goodProfiles = new HashMap<String, List<ValidationMessage>>(); 5067 HashMap<String, List<ValidationMessage>> badProfiles = new HashMap<String, List<ValidationMessage>>(); 5068 for (String typeProfile : profiles) { 5069 String url = typeProfile; 5070 tail = null; 5071 if (url.contains("#")) { 5072 tail = url.substring(url.indexOf("#") + 1); 5073 url = url.substring(0, url.indexOf("#")); 5074 } 5075 p = this.context.fetchResource(StructureDefinition.class, typeProfile); 5076 if (rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.getPath(), p != null, I18nConstants.VALIDATION_VAL_UNKNOWN_PROFILE, typeProfile)) { 5077 List<ValidationMessage> profileErrors = new ArrayList<ValidationMessage>(); 5078 validateElement(hostContext, profileErrors, p, getElementByTail(p, tail), profile, checkDefn, resource, ei.getElement(), type, localStack, thisIsCodeableConcept, checkDisplay, thisExtension); 5079 if (hasErrors(profileErrors)) 5080 badProfiles.put(typeProfile, profileErrors); 5081 else 5082 goodProfiles.put(typeProfile, profileErrors); 5083 } 5084 } 5085 if (goodProfiles.size() == 1) { 5086 errors.addAll(goodProfiles.values().iterator().next()); 5087 } else if (goodProfiles.size() == 0) { 5088 rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.getPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_NOMATCH, StringUtils.join("; ", profiles)); 5089 for (String m : badProfiles.keySet()) { 5090 p = this.context.fetchResource(StructureDefinition.class, m); 5091 for (ValidationMessage message : badProfiles.get(m)) { 5092 message.setMessage(message.getMessage() + " (validating against " + p.getUrl() + (p.hasVersion() ? "|" + p.getVersion() : "") + " [" + p.getName() + "])"); 5093 errors.add(message); 5094 } 5095 } 5096 } else { 5097 warning(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.getPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_MULTIPLEMATCHES, StringUtils.join("; ", goodProfiles.keySet())); 5098 for (String m : goodProfiles.keySet()) { 5099 p = this.context.fetchResource(StructureDefinition.class, m); 5100 for (ValidationMessage message : goodProfiles.get(m)) { 5101 message.setMessage(message.getMessage() + " (validating against " + p.getUrl() + (p.hasVersion() ? "|" + p.getVersion() : "") + " [" + p.getName() + "])"); 5102 errors.add(message); 5103 } 5104 } 5105 } 5106 } 5107 if (p != null) { 5108 trackUsage(p, hostContext, element); 5109 5110 if (!elementValidated) { 5111 if (ei.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY || ei.getElement().getSpecial() == SpecialElement.BUNDLE_OUTCOME || ei.getElement().getSpecial() == SpecialElement.PARAMETER) 5112 validateElement(hostContext, errors, p, getElementByTail(p, tail), profile, checkDefn, ei.getElement(), ei.getElement(), type, localStack.resetIds(), thisIsCodeableConcept, checkDisplay, thisExtension); 5113 else 5114 validateElement(hostContext, errors, p, getElementByTail(p, tail), profile, checkDefn, resource, ei.getElement(), type, localStack, thisIsCodeableConcept, checkDisplay, thisExtension); 5115 } 5116 int index = profile.getSnapshot().getElement().indexOf(checkDefn); 5117 if (index < profile.getSnapshot().getElement().size() - 1) { 5118 String nextPath = profile.getSnapshot().getElement().get(index + 1).getPath(); 5119 if (!nextPath.equals(checkDefn.getPath()) && nextPath.startsWith(checkDefn.getPath())) 5120 validateElement(hostContext, errors, profile, checkDefn, null, null, resource, ei.getElement(), type, localStack, thisIsCodeableConcept, checkDisplay, thisExtension); 5121 } 5122 } 5123 } 5124 5125 private boolean isResourceAndTypes(ElementDefinition ed) { 5126 if (!Utilities.existsInList(ed.getBase().getPath(), "Bundle.entry.resource", "Bundle.entry.response.outcome", "DomainResource.contained", "Parameters.parameter.resource", "Parameters.parameter.part.resource")) { 5127 return false; 5128 } 5129 for (TypeRefComponent tr : ed.getType()) { 5130 if (!isResource(tr.getCode())) { 5131 return false; 5132 } 5133 } 5134 return true; 5135 } 5136 5137 private boolean isResource(String type) { 5138 StructureDefinition sd = context.fetchTypeDefinition(type); 5139 return sd != null && sd.getKind().equals(StructureDefinitionKind.RESOURCE); 5140 } 5141 5142 private void trackUsage(StructureDefinition profile, ValidatorHostContext hostContext, Element element) { 5143 if (tracker != null) { 5144 tracker.recordProfileUsage(profile, hostContext.getAppContext(), element); 5145 } 5146 } 5147 5148 private boolean hasMapping(String url, StructureDefinition defn, ElementDefinition elem) { 5149 String id = null; 5150 for (StructureDefinitionMappingComponent m : defn.getMapping()) { 5151 if (url.equals(m.getUri())) { 5152 id = m.getIdentity(); 5153 break; 5154 } 5155 } 5156 if (id != null) { 5157 for (ElementDefinitionMappingComponent m : elem.getMapping()) { 5158 if (id.equals(m.getIdentity())) { 5159 return true; 5160 } 5161 } 5162 5163 } 5164 return false; 5165 } 5166 5167 private List<String> getMapping(String url, StructureDefinition defn, ElementDefinition elem) { 5168 List<String> res = new ArrayList<>(); 5169 String id = null; 5170 for (StructureDefinitionMappingComponent m : defn.getMapping()) { 5171 if (url.equals(m.getUri())) { 5172 id = m.getIdentity(); 5173 break; 5174 } 5175 } 5176 if (id != null) { 5177 for (ElementDefinitionMappingComponent m : elem.getMapping()) { 5178 if (id.equals(m.getIdentity())) { 5179 res.add(m.getMap()); 5180 } 5181 } 5182 } 5183 return res; 5184 } 5185 5186 public void checkMustSupport(StructureDefinition profile, ElementInfo ei) { 5187 String usesMustSupport = profile.getUserString("usesMustSupport"); 5188 if (usesMustSupport == null) { 5189 usesMustSupport = "N"; 5190 for (ElementDefinition pe : profile.getSnapshot().getElement()) { 5191 if (pe.getMustSupport()) { 5192 usesMustSupport = "Y"; 5193 break; 5194 } 5195 } 5196 profile.setUserData("usesMustSupport", usesMustSupport); 5197 } 5198 if (usesMustSupport.equals("Y")) { 5199 String elementSupported = ei.getElement().getUserString("elementSupported"); 5200 if (elementSupported == null || ei.definition.getMustSupport()) 5201 if (ei.definition.getMustSupport()) { 5202 ei.getElement().setUserData("elementSupported", "Y"); 5203 } 5204 } 5205 } 5206 5207 public void checkCardinalities(List<ValidationMessage> errors, StructureDefinition profile, Element element, NodeStack stack, 5208 List<ElementDefinition> childDefinitions, List<ElementInfo> children, List<String> problematicPaths) throws DefinitionException { 5209 // 3. report any definitions that have a cardinality problem 5210 for (ElementDefinition ed : childDefinitions) { 5211 if (ed.getRepresentation().isEmpty()) { // ignore xml attributes 5212 int count = 0; 5213 List<ElementDefinition> slices = null; 5214 if (ed.hasSlicing()) 5215 slices = profileUtilities.getSliceList(profile, ed); 5216 for (ElementInfo ei : children) 5217 if (ei.definition == ed) 5218 count++; 5219 else if (slices != null) { 5220 for (ElementDefinition sed : slices) { 5221 if (ei.definition == sed) { 5222 count++; 5223 break; 5224 } 5225 } 5226 } 5227 if (ed.getMin() > 0) { 5228 if (problematicPaths.contains(ed.getPath())) 5229 hint(errors, IssueType.NOTSUPPORTED, element.line(), element.col(), stack.getLiteralPath(), count >= ed.getMin(), I18nConstants.VALIDATION_VAL_PROFILE_NOCHECKMIN, profile.getUrl(), ed.getPath(), ed.getId(), ed.getSliceName(),ed.getLabel(), stack.getLiteralPath(), Integer.toString(ed.getMin())); 5230 else { 5231 if (count < ed.getMin()) { 5232 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_MINIMUM, profile.getUrl(), ed.getPath(), ed.getId(), ed.getSliceName(),ed.getLabel(), stack.getLiteralPath(), Integer.toString(ed.getMin()), Integer.toString(count)); 5233 } 5234 } 5235 } 5236 if (ed.hasMax() && !ed.getMax().equals("*")) { 5237 if (problematicPaths.contains(ed.getPath())) 5238 hint(errors, IssueType.NOTSUPPORTED, element.line(), element.col(), stack.getLiteralPath(), count <= Integer.parseInt(ed.getMax()), I18nConstants.VALIDATION_VAL_PROFILE_NOCHECKMAX, profile.getUrl(), ed.getPath(), ed.getId(), ed.getSliceName(),ed.getLabel(), stack.getLiteralPath(), ed.getMax()); 5239 else if (count > Integer.parseInt(ed.getMax())) { 5240 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_MAXIMUM, profile.getUrl(), ed.getPath(), ed.getId(), ed.getSliceName(),ed.getLabel(), stack.getLiteralPath(), ed.getMax(), Integer.toString(count)); 5241 } 5242 } 5243 } 5244 } 5245 } 5246 5247 public List<String> assignChildren(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, Element resource, 5248 NodeStack stack, List<ElementDefinition> childDefinitions, List<ElementInfo> children) throws DefinitionException { 5249 // 2. assign children to a definition 5250 // for each definition, for each child, check whether it belongs in the slice 5251 ElementDefinition slicer = null; 5252 boolean unsupportedSlicing = false; 5253 List<String> problematicPaths = new ArrayList<String>(); 5254 String slicingPath = null; 5255 int sliceOffset = 0; 5256 for (int i = 0; i < childDefinitions.size(); i++) { 5257 ElementDefinition ed = childDefinitions.get(i); 5258 boolean childUnsupportedSlicing = false; 5259 boolean process = true; 5260 if (ed.hasSlicing() && !ed.getSlicing().getOrdered()) { 5261 slicingPath = ed.getPath(); 5262 } else if (slicingPath != null && ed.getPath().equals(slicingPath)) { 5263 ; // nothing 5264 } else if (slicingPath != null && !ed.getPath().startsWith(slicingPath)) { 5265 slicingPath = null; 5266 } 5267 // where are we with slicing 5268 if (ed.hasSlicing()) { 5269 if (slicer != null && slicer.getPath().equals(ed.getPath())) { 5270 String errorContext = "profile " + profile.getUrl(); 5271 if (!resource.getChildValue(ID).isEmpty()) { 5272 errorContext += "; instance " + resource.getChildValue("id"); 5273 } 5274 throw new DefinitionException(context.formatMessage(I18nConstants.SLICE_ENCOUNTERED_MIDWAY_THROUGH_SET_PATH___ID___, slicer.getPath(), slicer.getId(), errorContext)); 5275 } 5276 slicer = ed; 5277 process = false; 5278 sliceOffset = i; 5279 } else if (slicer != null && !slicer.getPath().equals(ed.getPath())) 5280 slicer = null; 5281 5282 for (ElementInfo ei : children) { 5283 if (ei.sliceInfo == null) { 5284 ei.sliceInfo = new ArrayList<>(); 5285 } 5286 unsupportedSlicing = matchSlice(hostContext, errors, ei.sliceInfo, profile, stack, slicer, unsupportedSlicing, problematicPaths, sliceOffset, i, ed, childUnsupportedSlicing, ei); 5287 } 5288 } 5289 int last = -1; 5290 ElementInfo lastei = null; 5291 int lastSlice = -1; 5292 for (ElementInfo ei : children) { 5293 String sliceInfo = ""; 5294 if (slicer != null) { 5295 sliceInfo = " (slice: " + slicer.getPath() + ")"; 5296 } 5297 if (!unsupportedSlicing) { 5298 if (ei.additionalSlice && ei.definition != null) { 5299 if (ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPEN) || 5300 ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPENATEND) && true /* TODO: replace "true" with condition to check that this element is at "end" */) { 5301 slicingHint(errors, IssueType.INFORMATIONAL, ei.line(), ei.col(), ei.getPath(), false, isProfile(slicer) || isCritical(ei.sliceInfo), 5302 context.formatMessage(I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_, 5303 profile == null ? "" : " defined in the profile " + profile.getUrl()), 5304 context.formatMessage(I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_, profile == null ? "" : I18nConstants.DEFINED_IN_THE_PROFILE + profile.getUrl()) + errorSummaryForSlicingAsHtml(ei.sliceInfo), 5305 errorSummaryForSlicingAsText(ei.sliceInfo)); 5306 } else if (ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.CLOSED)) { 5307 rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_NOTSLICE, (profile == null ? "" : " defined in the profile " + profile.getUrl()), errorSummaryForSlicing(ei.sliceInfo)); 5308 } 5309 } else { 5310 // Don't raise this if we're in an abstract profile, like Resource 5311 if (!profile.getAbstract()) { 5312 rule(errors, IssueType.NOTSUPPORTED, ei.line(), ei.col(), ei.getPath(), (ei.definition != null), I18nConstants.VALIDATION_VAL_PROFILE_NOTALLOWED, profile.getUrl()); 5313 } 5314 } 5315 } 5316 // TODO: Should get the order of elements correct when parsing elements that are XML attributes vs. elements 5317 boolean isXmlAttr = false; 5318 if (ei.definition != null) { 5319 for (Enumeration<PropertyRepresentation> r : ei.definition.getRepresentation()) { 5320 if (r.getValue() == PropertyRepresentation.XMLATTR) { 5321 isXmlAttr = true; 5322 break; 5323 } 5324 } 5325 } 5326 5327 if (!ToolingExtensions.readBoolExtension(profile, "http://hl7.org/fhir/StructureDefinition/structuredefinition-xml-no-order")) { 5328 boolean ok = (ei.definition == null) || (ei.index >= last) || isXmlAttr; 5329 rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), ok, I18nConstants.VALIDATION_VAL_PROFILE_OUTOFORDER, profile.getUrl(), ei.getName(), lastei == null ? "(null)" : lastei.getName()); 5330 } 5331 if (ei.slice != null && ei.index == last && ei.slice.getSlicing().getOrdered()) { 5332 rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), (ei.definition == null) || (ei.sliceindex >= lastSlice) || isXmlAttr, I18nConstants.VALIDATION_VAL_PROFILE_SLICEORDER, profile.getUrl(), ei.getName()); 5333 } 5334 if (ei.definition == null || !isXmlAttr) { 5335 last = ei.index; 5336 lastei = ei; 5337 } 5338 if (ei.slice != null) { 5339 lastSlice = ei.sliceindex; 5340 } else { 5341 lastSlice = -1; 5342 } 5343 } 5344 return problematicPaths; 5345 } 5346 5347 5348 public List<ElementInfo> listChildren(Element element, NodeStack stack) { 5349 // 1. List the children, and remember their exact path (convenience) 5350 List<ElementInfo> children = new ArrayList<ElementInfo>(); 5351 ChildIterator iter = new ChildIterator(this, stack.getLiteralPath(), element); 5352 while (iter.next()) 5353 children.add(new ElementInfo(iter.name(), iter.element(), iter.path(), iter.count())); 5354 return children; 5355 } 5356 5357 public void checkInvariants(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition, Element resource, Element element, NodeStack stack, boolean onlyNonInherited) throws FHIRException { 5358 checkInvariants(hostContext, errors, stack.getLiteralPath(), profile, definition, null, null, resource, element, onlyNonInherited); 5359 } 5360 5361 public boolean matchSlice(ValidatorHostContext hostContext, List<ValidationMessage> errors, List<ValidationMessage> sliceInfo, StructureDefinition profile, NodeStack stack, 5362 ElementDefinition slicer, boolean unsupportedSlicing, List<String> problematicPaths, int sliceOffset, int i, ElementDefinition ed, 5363 boolean childUnsupportedSlicing, ElementInfo ei) { 5364 boolean match = false; 5365 if (slicer == null || slicer == ed) { 5366 match = nameMatches(ei.getName(), tail(ed.getPath())); 5367 } else { 5368 if (nameMatches(ei.getName(), tail(ed.getPath()))) 5369 try { 5370// System.out.println("match slices for "+stack.getLiteralPath()+": "+slicer.getId()+" = "+slicingSummary(slicer.getSlicing())); 5371 match = sliceMatches(hostContext, ei.getElement(), ei.getPath(), slicer, ed, profile, errors, sliceInfo, stack, profile); 5372 if (match) { 5373 ei.slice = slicer; 5374 5375 // Since a defined slice was found, this is not an additional (undefined) slice. 5376 ei.additionalSlice = false; 5377 } else if (ei.slice == null) { 5378 // if the specified slice is undefined, keep track of the fact this is an additional (undefined) slice, but only if a slice wasn't found previously 5379 ei.additionalSlice = true; 5380 } 5381 } catch (FHIRException e) { 5382 rule(errors, IssueType.PROCESSING, ei.line(), ei.col(), ei.getPath(), false, I18nConstants.SLICING_CANNOT_BE_EVALUATED, e.getMessage()); 5383 unsupportedSlicing = true; 5384 childUnsupportedSlicing = true; 5385 } 5386 } 5387 if (match) { 5388 boolean isOk = ei.definition == null || ei.definition == slicer || (ei.definition.getPath().endsWith("[x]") && ed.getPath().startsWith(ei.definition.getPath().replace("[x]", ""))); 5389 if (rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), isOk, I18nConstants.VALIDATION_VAL_PROFILE_MATCHMULTIPLE, profile.getUrl(), (ei.definition == null || !ei.definition.hasSliceName() ? "" : ei.definition.getSliceName()), (ed.hasSliceName() ? ed.getSliceName() : ""))) { 5390 ei.definition = ed; 5391 if (ei.slice == null) { 5392 ei.index = i; 5393 } else { 5394 ei.index = sliceOffset; 5395 ei.sliceindex = i - (sliceOffset + 1); 5396 } 5397 } 5398 } else if (childUnsupportedSlicing) { 5399 problematicPaths.add(ed.getPath()); 5400 } 5401 return unsupportedSlicing; 5402 } 5403 5404 private String slicingSummary(ElementDefinitionSlicingComponent slicing) { 5405 StringBuilder b = new StringBuilder(); 5406 b.append('['); 5407 boolean first = true; 5408 for (ElementDefinitionSlicingDiscriminatorComponent t : slicing.getDiscriminator()) { 5409 if (first) first = false; else b.append(","); 5410 b.append(t.getType().toCode()); 5411 b.append(":"); 5412 b.append(t.getPath()); 5413 } 5414 b.append(']'); 5415 b.append(slicing.getOrdered() ? ";ordered" : ""); 5416 b.append(slicing.getRules().toString()); 5417 return b.toString(); 5418 } 5419 5420 private ElementDefinition getElementByTail(StructureDefinition p, String tail) throws DefinitionException { 5421 if (tail == null) 5422 return p.getSnapshot().getElement().get(0); 5423 for (ElementDefinition t : p.getSnapshot().getElement()) { 5424 if (tail.equals(t.getId())) 5425 return t; 5426 } 5427 throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_ELEMENT_WITH_ID_, tail)); 5428 } 5429 5430 private IdStatus idStatusForEntry(Element ep, ElementInfo ei) { 5431 if (isBundleEntry(ei.getPath())) { 5432 Element req = ep.getNamedChild("request"); 5433 Element resp = ep.getNamedChild("response"); 5434 Element fullUrl = ep.getNamedChild(FULL_URL); 5435 Element method = null; 5436 Element url = null; 5437 if (req != null) { 5438 method = req.getNamedChild("method"); 5439 url = req.getNamedChild("url"); 5440 } 5441 if (resp != null) { 5442 return IdStatus.OPTIONAL; 5443 } 5444 if (method == null) { 5445 if (fullUrl == null) 5446 return IdStatus.REQUIRED; 5447 else if (fullUrl.primitiveValue().startsWith("urn:uuid:") || fullUrl.primitiveValue().startsWith("urn:oid:")) 5448 return IdStatus.OPTIONAL; 5449 else 5450 return IdStatus.REQUIRED; 5451 } else { 5452 String s = method.primitiveValue(); 5453 if (s.equals("PUT")) { 5454 if (url == null) 5455 return IdStatus.REQUIRED; 5456 else 5457 return IdStatus.OPTIONAL; // or maybe prohibited? not clear 5458 } else if (s.equals("POST")) 5459 return IdStatus.OPTIONAL; // this should be prohibited, but see task 9102 5460 else // actually, we should never get to here; a bundle entry with method get/delete should not have a resource 5461 return IdStatus.OPTIONAL; 5462 } 5463 } else if (isParametersEntry(ei.getPath()) || isBundleOutcome(ei.getPath())) 5464 return IdStatus.OPTIONAL; 5465 else 5466 return IdStatus.REQUIRED; 5467 } 5468 5469 private void checkInvariants(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, StructureDefinition profile, ElementDefinition ed, String typename, String typeProfile, Element resource, Element element, boolean onlyNonInherited) throws FHIRException, FHIRException { 5470 if (noInvariantChecks) 5471 return; 5472 5473 for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) { 5474 if (inv.hasExpression() && (!onlyNonInherited || !inv.hasSource() || (!isInheritedProfile(profile, inv.getSource()) && !isInheritedProfile(ed.getType(), inv.getSource())) )) { 5475 @SuppressWarnings("unchecked") 5476 Map<String, List<ValidationMessage>> invMap = executionId.equals(element.getUserString(EXECUTION_ID)) ? (Map<String, List<ValidationMessage>>) element.getUserData(EXECUTED_CONSTRAINT_LIST) : null; 5477 if (invMap == null) { 5478 invMap = new HashMap<>(); 5479 element.setUserData(EXECUTED_CONSTRAINT_LIST, invMap); 5480 element.setUserData(EXECUTION_ID, executionId); 5481 } 5482 List<ValidationMessage> invErrors = null; 5483 // We key based on inv.expression rather than inv.key because expressions can change in derived profiles and aren't guaranteed to be consistent across profiles. 5484 String key = fixExpr(inv.getExpression(), inv.getKey()); 5485 if (!invMap.keySet().contains(key)) { 5486 invErrors = new ArrayList<ValidationMessage>(); 5487 invMap.put(key, invErrors); 5488 checkInvariant(hostContext, invErrors, path, profile, resource, element, inv); 5489 } else { 5490 invErrors = (ArrayList<ValidationMessage>)invMap.get(key); 5491 } 5492 errors.addAll(invErrors); 5493 } 5494 } 5495 } 5496 5497 private boolean isInheritedProfile(List<TypeRefComponent> types, String source) { 5498 for (TypeRefComponent type : types) { 5499 for (CanonicalType c : type.getProfile()) { 5500 StructureDefinition sd = context.fetchResource(StructureDefinition.class, c.asStringValue()); 5501 if (sd != null) { 5502 if (sd.getUrl().equals(source)) { 5503 return true; 5504 } 5505 if (isInheritedProfile(sd, source)) { 5506 return true; 5507 } 5508 } 5509 } 5510 } 5511 return false; 5512 } 5513 5514 private boolean isInheritedProfile(StructureDefinition profile, String source) { 5515 if (source.equals(profile.getUrl())) { 5516 return false; 5517 } 5518 while (profile != null) { 5519 profile = context.fetchResource(StructureDefinition.class, profile.getBaseDefinition()); 5520 if (profile != null) { 5521 if (source.equals(profile.getUrl())) { 5522 return true; 5523 } 5524 } 5525 } 5526 return false; 5527 } 5528 5529 public void checkInvariant(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, StructureDefinition profile, Element resource, Element element, ElementDefinitionConstraintComponent inv) throws FHIRException { 5530// if (debug) { 5531// System.out.println("inv "+inv.getKey()+" on "+path+" in "+resource.fhirType()+" {{ "+inv.getExpression()+" }}"); 5532// } 5533 ExpressionNode n = (ExpressionNode) inv.getUserData("validator.expression.cache"); 5534 if (n == null) { 5535 long t = System.nanoTime(); 5536 try { 5537 n = fpe.parse(fixExpr(inv.getExpression(), inv.getKey())); 5538 } catch (FHIRLexerException e) { 5539 rule(errors, IssueType.INVARIANT, element.line(), element.col(), path, false, I18nConstants.PROBLEM_PROCESSING_EXPRESSION__IN_PROFILE__PATH__, inv.getExpression(), profile.getUrl(), path, e.getMessage()); 5540 return; 5541 } 5542 timeTracker.fpe(t); 5543 inv.setUserData("validator.expression.cache", n); 5544 } 5545 5546 String msg; 5547 boolean ok; 5548 try { 5549 long t = System.nanoTime(); 5550 ok = fpe.evaluateToBoolean(hostContext, resource, hostContext.getRootResource(), element, n); 5551 timeTracker.fpe(t); 5552 msg = fpe.forLog(); 5553 } catch (Exception ex) { 5554 ok = false; 5555 msg = ex.getMessage(); 5556 } 5557 if (!ok) { 5558 if (!Utilities.noString(msg)) { 5559 msg = "'" + inv.getHuman()+"' (" + msg + ")"; 5560 } else if (wantInvariantInMessage) { 5561 msg = "'" + inv.getHuman()+"' [" + n.toString() + "]"; 5562 } else { 5563 msg = context.formatMessage(I18nConstants.INV_FAILED, "'" + inv.getHuman()+"'"); 5564 } 5565 if (inv.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice") && 5566 ToolingExtensions.readBooleanExtension(inv, "http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice")) { 5567 if (bpWarnings == BestPracticeWarningLevel.Hint) 5568 hint(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": " + msg); 5569 else if (bpWarnings == BestPracticeWarningLevel.Warning) 5570 warning(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": '" + inv.getHuman()+"' " + msg); 5571 else if (bpWarnings == BestPracticeWarningLevel.Error) 5572 rule(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": '" + inv.getHuman()+"' " + msg); 5573 } else if (inv.getSeverity() == ConstraintSeverity.ERROR) { 5574 rule(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": '" + inv.getHuman()+"' " + msg); 5575 } else if (inv.getSeverity() == ConstraintSeverity.WARNING) { 5576 warning(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": '" + inv.getHuman()+"' " + msg); 5577 } 5578 } 5579 } 5580 5581 private void validateObservation(List<ValidationMessage> errors, Element element, NodeStack stack) { 5582 // all observations should have a subject, a performer, and a time 5583 5584 bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), element.getNamedChild("subject") != null, I18nConstants.ALL_OBSERVATIONS_SHOULD_HAVE_A_SUBJECT); 5585 List<Element> performers = new ArrayList<>(); 5586 element.getNamedChildren("performer", performers); 5587 bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), performers.size() > 0, I18nConstants.ALL_OBSERVATIONS_SHOULD_HAVE_A_PERFORMER); 5588 bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), element.getNamedChild("effectiveDateTime") != null || element.getNamedChild("effectivePeriod") != null, I18nConstants.ALL_OBSERVATIONS_SHOULD_HAVE_AN_EFFECTIVEDATETIME_OR_AN_EFFECTIVEPERIOD); 5589 } 5590 5591 /* 5592 * The actual base entry point for internal use (re-entrant) 5593 */ 5594 private void validateResource(ValidatorHostContext hostContext, List<ValidationMessage> errors, Element resource, 5595 Element element, StructureDefinition defn, IdStatus idstatus, NodeStack stack) throws FHIRException { 5596 5597 // check here if we call validation policy here, and then change it to the new interface 5598 assert stack != null; 5599 assert resource != null; 5600 boolean ok = true; 5601 String resourceName = element.getType(); // todo: consider namespace...? 5602 5603 if (defn == null) { 5604 long t = System.nanoTime(); 5605 defn = element.getProperty().getStructure(); 5606 if (defn == null) 5607 defn = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + resourceName); 5608 timeTracker.sd(t); 5609 //check exists 5610 ok = rule(errors, IssueType.INVALID, element.line(), element.col(), stack.addToLiteralPath(resourceName), 5611 defn != null, I18nConstants.VALIDATION_VAL_PROFILE_NODEFINITION, resourceName); 5612 } 5613 5614 // special case: we have a bundle, and the profile is not for a bundle. We'll try the first entry instead 5615 if (!typeMatchesDefn(resourceName, defn) && resourceName.equals(BUNDLE)) { 5616 NodeStack first = getFirstEntry(stack); 5617 if (first != null && typeMatchesDefn(first.getElement().getType(), defn)) { 5618 element = first.getElement(); 5619 stack = first; 5620 resourceName = element.getType(); // todo: consider namespace...? 5621 idstatus = IdStatus.OPTIONAL; // why? 5622 } 5623 // todo: validate everything in this bundle. 5624 } 5625 if (ok) { 5626 if (idstatus == IdStatus.REQUIRED && (element.getNamedChild(ID) == null)) { 5627 rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.RESOURCE_RES_ID_MISSING); 5628 } else if (idstatus == IdStatus.PROHIBITED && (element.getNamedChild(ID) != null)) { 5629 rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.RESOURCE_RES_ID_PROHIBITED); 5630 } 5631 if (element.getNamedChild(ID) != null) { 5632 Element eid = element.getNamedChild(ID); 5633 if (eid.getProperty() != null && eid.getProperty().getDefinition() != null && eid.getProperty().getDefinition().getBase().getPath().equals("Resource.id")) { 5634 NodeStack ns = stack.push(eid, -1, eid.getProperty().getDefinition(), null); 5635 rule(errors, IssueType.INVALID, eid.line(), eid.col(), ns.getLiteralPath(), FormatUtilities.isValidId(eid.primitiveValue()), I18nConstants.RESOURCE_RES_ID_MALFORMED); 5636 } 5637 } 5638 // validate 5639 if (rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), resourceName.equals(defn.getType()), I18nConstants.VALIDATION_VAL_PROFILE_WRONGTYPE, 5640 defn.getType(), resourceName, defn.getUrl())) { 5641 start(hostContext, errors, element, element, defn, stack); // root is both definition and type 5642 } 5643 } 5644 } 5645 5646 private boolean typeMatchesDefn(String name, StructureDefinition defn) { 5647 if (defn.getKind() == StructureDefinitionKind.LOGICAL) { 5648 return name.equals(defn.getType()) || name.equals(defn.getName()) || name.equals(defn.getId()); 5649 } else { 5650 return name.matches(defn.getType()); 5651 } 5652 } 5653 5654 private NodeStack getFirstEntry(NodeStack bundle) { 5655 List<Element> list = new ArrayList<Element>(); 5656 bundle.getElement().getNamedChildren(ENTRY, list); 5657 if (list.isEmpty()) 5658 return null; 5659 Element resource = list.get(0).getNamedChild(RESOURCE); 5660 if (resource == null) 5661 return null; 5662 else { 5663 NodeStack entry = bundle.push(list.get(0), 0, list.get(0).getProperty().getDefinition(), list.get(0).getProperty().getDefinition()); 5664 return entry.push(resource, -1, resource.getProperty().getDefinition(), context.fetchTypeDefinition(resource.fhirType()).getSnapshot().getElementFirstRep()); 5665 } 5666 } 5667 5668 private boolean valueMatchesCriteria(Element value, ElementDefinition criteria, StructureDefinition profile) throws FHIRException { 5669 if (criteria.hasFixed()) { 5670 List<ValidationMessage> msgs = new ArrayList<ValidationMessage>(); 5671 checkFixedValue(msgs, "{virtual}", value, criteria.getFixed(), profile.getUrl(), "value", null, false); 5672 return msgs.size() == 0; 5673 } else if (criteria.hasBinding() && criteria.getBinding().getStrength() == BindingStrength.REQUIRED && criteria.getBinding().hasValueSet()) { 5674 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SLICE_MATCHING__SLICE_MATCHING_BY_VALUE_SET_NOT_DONE)); 5675 } else { 5676 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SLICE_MATCHING__NO_FIXED_VALUE_OR_REQUIRED_VALUE_SET)); 5677 } 5678 } 5679 5680 private boolean yearIsValid(String v) { 5681 if (v == null) { 5682 return false; 5683 } 5684 try { 5685 int i = Integer.parseInt(v.substring(0, Math.min(4, v.length()))); 5686 return i >= 1800 && i <= thisYear() + 80; 5687 } catch (NumberFormatException e) { 5688 return false; 5689 } 5690 } 5691 5692 private int thisYear() { 5693 return Calendar.getInstance().get(Calendar.YEAR); 5694 } 5695 5696 5697 public String reportTimes() { 5698 String s = String.format("Times (ms): overall = %d, tx = %d, sd = %d, load = %d, fpe = %d", timeTracker.getOverall() / 1000000, timeTracker.getTxTime() / 1000000, timeTracker.getSdTime() / 1000000, timeTracker.getLoadTime() / 1000000, timeTracker.getFpeTime() / 1000000); 5699 timeTracker.reset(); 5700 return s; 5701 } 5702 5703 public boolean isNoBindingMsgSuppressed() { 5704 return noBindingMsgSuppressed; 5705 } 5706 5707 public IResourceValidator setNoBindingMsgSuppressed(boolean noBindingMsgSuppressed) { 5708 this.noBindingMsgSuppressed = noBindingMsgSuppressed; 5709 return this; 5710 } 5711 5712 5713 public boolean isNoTerminologyChecks() { 5714 return noTerminologyChecks; 5715 } 5716 5717 public IResourceValidator setNoTerminologyChecks(boolean noTerminologyChecks) { 5718 this.noTerminologyChecks = noTerminologyChecks; 5719 return this; 5720 } 5721 5722 public void checkAllInvariants() { 5723 for (StructureDefinition sd : context.allStructures()) { 5724 if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 5725 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 5726 for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) { 5727 if (inv.hasExpression()) { 5728 try { 5729 ExpressionNode n = (ExpressionNode) inv.getUserData("validator.expression.cache"); 5730 if (n == null) { 5731 n = fpe.parse(fixExpr(inv.getExpression(), inv.getKey())); 5732 inv.setUserData("validator.expression.cache", n); 5733 } 5734 fpe.check(null, sd.getKind() == StructureDefinitionKind.RESOURCE ? sd.getType() : "DomainResource", ed.getPath(), n); 5735 } catch (Exception e) { 5736 System.out.println("Error processing structure [" + sd.getId() + "] path " + ed.getPath() + ":" + inv.getKey() + " ('" + inv.getExpression() + "'): " + e.getMessage()); 5737 } 5738 } 5739 } 5740 } 5741 } 5742 } 5743 } 5744 5745 private String fixExpr(String expr, String key) { 5746 // this is a hack work around for past publication of wrong FHIRPath expressions 5747 // R4 5748 // waiting for 4.0.2 5749 //TODO is this expression below correct? @grahamegrieve 5750 if ("probability is decimal implies (probability as decimal) <= 100".equals(expr)) { 5751 return "probability.empty() or ((probability is decimal) implies ((probability as decimal) <= 100))"; 5752 } 5753 if ("enableWhen.count() > 2 implies enableBehavior.exists()".equals(expr)) { 5754 return "enableWhen.count() >= 2 implies enableBehavior.exists()"; 5755 } 5756 if ("txt-2".equals(key)) { 5757 return "htmlChecks2()"; 5758 } 5759 5760 // handled in 4.0.1 5761 if ("(component.empty() and hasMember.empty()) implies (dataAbsentReason or value)".equals(expr)) { 5762 return "(component.empty() and hasMember.empty()) implies (dataAbsentReason.exists() or value.exists())"; 5763 } 5764 if ("isModifier implies isModifierReason.exists()".equals(expr)) { 5765 return "(isModifier.exists() and isModifier) implies isModifierReason.exists()"; 5766 } 5767 if ("(%resource.kind = 'logical' or element.first().path.startsWith(%resource.type)) and (element.tail().not() or element.tail().all(path.startsWith(%resource.differential.element.first().path.replaceMatches('\\\\..*','')&'.')))".equals(expr)) { 5768 return "(%resource.kind = 'logical' or element.first().path.startsWith(%resource.type)) and (element.tail().empty() or element.tail().all(path.startsWith(%resource.differential.element.first().path.replaceMatches('\\\\..*','')&'.')))"; 5769 } 5770 if ("differential.element.all(id) and differential.element.id.trace('ids').isDistinct()".equals(expr)) { 5771 return "differential.element.all(id.exists()) and differential.element.id.trace('ids').isDistinct()"; 5772 } 5773 if ("snapshot.element.all(id) and snapshot.element.id.trace('ids').isDistinct()".equals(expr)) { 5774 return "snapshot.element.all(id.exists()) and snapshot.element.id.trace('ids').isDistinct()"; 5775 } 5776 5777 // R3 5778 if ("(code or value.empty()) and (system.empty() or system = 'urn:iso:std:iso:4217')".equals(expr)) { 5779 return "(code.exists() or value.empty()) and (system.empty() or system = 'urn:iso:std:iso:4217')"; 5780 } 5781 if ("value.empty() or code!=component.code".equals(expr)) { 5782 return "value.empty() or (code in component.code).not()"; 5783 } 5784 if ("(code or value.empty()) and (system.empty() or system = %ucum) and (value.empty() or value > 0)".equals(expr)) { 5785 return "(code.exists() or value.empty()) and (system.empty() or system = %ucum) and (value.empty() or value > 0)"; 5786 } 5787 if ("element.all(definition and min and max)".equals(expr)) { 5788 return "element.all(definition.exists() and min.exists() and max.exists())"; 5789 } 5790 if ("telecom or endpoint".equals(expr)) { 5791 return "telecom.exists() or endpoint.exists()"; 5792 } 5793 if ("(code or value.empty()) and (system.empty() or system = %ucum) and (value.empty() or value > 0)".equals(expr)) { 5794 return "(code.exists() or value.empty()) and (system.empty() or system = %ucum) and (value.empty() or value > 0)"; 5795 } 5796 if ("searchType implies type = 'string'".equals(expr)) { 5797 return "searchType.exists() implies type = 'string'"; 5798 } 5799 if ("abatement.empty() or (abatement as boolean).not() or clinicalStatus='resolved' or clinicalStatus='remission' or clinicalStatus='inactive'".equals(expr)) { 5800 return "abatement.empty() or (abatement is boolean).not() or (abatement as boolean).not() or (clinicalStatus = 'resolved') or (clinicalStatus = 'remission') or (clinicalStatus = 'inactive')"; 5801 } 5802 if ("(component.empty() and related.empty()) implies (dataAbsentReason or value)".equals(expr)) { 5803 return "(component.empty() and related.empty()) implies (dataAbsentReason.exists() or value.exists())"; 5804 } 5805 if ("reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids'))".equals(expr)) { 5806 return "(reference = '#') or reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids'))"; 5807 } 5808 if ("reference.startsWith('#').not() or (reference.substring(1).trace('url') in %resource.contained.id.trace('ids'))".equals(expr)) { 5809 return "(reference = '#') or reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids'))"; 5810 } 5811 if ("probability is decimal implies probability.as(decimal) <= 100".equals(expr)) { 5812 if (key.equals("ras-2")) { 5813 return "probability.empty() or (probability is decimal implies probability.as(decimal) <= 100)"; 5814 } 5815 } 5816 if ("".equals(expr)) { 5817 return ""; 5818 } 5819 return expr; 5820 } 5821 5822 public IEvaluationContext getExternalHostServices() { 5823 return externalHostServices; 5824 } 5825 5826 public String getValidationLanguage() { 5827 return validationLanguage; 5828 } 5829 5830 public void setValidationLanguage(String validationLanguage) { 5831 this.validationLanguage = validationLanguage; 5832 } 5833 5834 public boolean isDebug() { 5835 return debug; 5836 } 5837 5838 public void setDebug(boolean debug) { 5839 this.debug = debug; 5840 } 5841 private String tail(String path) { 5842 return path.substring(path.lastIndexOf(".") + 1); 5843 } 5844 private String tryParse(String ref) { 5845 String[] parts = ref.split("\\/"); 5846 switch (parts.length) { 5847 case 1: 5848 return null; 5849 case 2: 5850 return checkResourceType(parts[0]); 5851 default: 5852 if (parts[parts.length - 2].equals("_history") && parts.length >= 4) 5853 return checkResourceType(parts[parts.length - 4]); 5854 else 5855 return checkResourceType(parts[parts.length - 2]); 5856 } 5857 } 5858 5859 private boolean typesAreAllReference(List<TypeRefComponent> theType) { 5860 for (TypeRefComponent typeRefComponent : theType) { 5861 if (typeRefComponent.getCode().equals("Reference") == false) { 5862 return false; 5863 } 5864 } 5865 return true; 5866 } 5867 5868 5869 public ValidationResult checkCodeOnServer(NodeStack stack, ValueSet vs, String value, ValidationOptions options) { 5870 return context.validateCode(options, value, vs); 5871 } 5872 5873 // no delay on this one? 5874 public ValidationResult checkCodeOnServer(NodeStack stack, String code, String system, String version, String display, boolean checkDisplay) { 5875 return context.validateCode(baseOptions.setLanguage(stack.getWorkingLang()), system, version, code, checkDisplay ? display : null); 5876 } 5877 5878 public ValidationResult checkCodeOnServer(NodeStack stack, ValueSet valueset, Coding c, boolean checkMembership) { 5879 if (checkMembership) { 5880 return context.validateCode(baseOptions.setLanguage(stack.getWorkingLang()).checkValueSetOnly(), c, valueset); 5881 } else { 5882 return context.validateCode(baseOptions.setLanguage(stack.getWorkingLang()).noCheckValueSetMembership(), c, valueset); 5883 } 5884 } 5885 5886 public ValidationResult checkCodeOnServer(NodeStack stack, ValueSet valueset, CodeableConcept cc, boolean vsOnly) { 5887 if (vsOnly) { 5888 return context.validateCode(baseOptions.setLanguage(stack.getWorkingLang()).checkValueSetOnly(), cc, valueset); 5889 } else { 5890 return context.validateCode(baseOptions.setLanguage(stack.getWorkingLang()), cc, valueset); 5891 } 5892 } 5893 5894 public boolean isSecurityChecks() { 5895 return securityChecks; 5896 } 5897 5898 public void setSecurityChecks(boolean securityChecks) { 5899 this.securityChecks = securityChecks; 5900 } 5901 5902 @Override 5903 public List<BundleValidationRule> getBundleValidationRules() { 5904 return bundleValidationRules ; 5905 } 5906 5907 @Override 5908 public boolean isValidateValueSetCodesOnTxServer() { 5909 return validateValueSetCodesOnTxServer; 5910 } 5911 5912 @Override 5913 public void setValidateValueSetCodesOnTxServer(boolean value) { 5914 this.validateValueSetCodesOnTxServer = value; 5915 } 5916 5917 public boolean isNoCheckAggregation() { 5918 return noCheckAggregation; 5919 } 5920 5921 public void setNoCheckAggregation(boolean noCheckAggregation) { 5922 this.noCheckAggregation = noCheckAggregation; 5923 } 5924 5925 5926 public static void setParents(Element element) { 5927 if (element != null && !element.hasParentForValidator()) { 5928 element.setParentForValidator(null); 5929 setParentsInner(element); 5930 } 5931 } 5932 5933 public static void setParentsInner(Element element) { 5934 for (Element child : element.getChildren()) { 5935 child.setParentForValidator(element); 5936 setParentsInner(child); 5937 } 5938 5939 } 5940 5941 public void setQuestionnaireMode(QuestionnaireMode questionnaireMode) { 5942 this.questionnaireMode = questionnaireMode; 5943 } 5944 5945 public QuestionnaireMode getQuestionnaireMode() { 5946 return questionnaireMode; 5947 } 5948 5949 public boolean isWantCheckSnapshotUnchanged() { 5950 return wantCheckSnapshotUnchanged; 5951 } 5952 5953 public void setWantCheckSnapshotUnchanged(boolean wantCheckSnapshotUnchanged) { 5954 this.wantCheckSnapshotUnchanged = wantCheckSnapshotUnchanged; 5955 } 5956 5957 public ValidationOptions getBaseOptions() { 5958 return baseOptions; 5959 } 5960 5961 public void setBaseOptions(ValidationOptions baseOptions) { 5962 this.baseOptions = baseOptions; 5963 } 5964 5965 public boolean isNoUnicodeBiDiControlChars() { 5966 return noUnicodeBiDiControlChars; 5967 } 5968 5969 public void setNoUnicodeBiDiControlChars(boolean noUnicodeBiDiControlChars) { 5970 this.noUnicodeBiDiControlChars = noUnicodeBiDiControlChars; 5971 } 5972 5973}