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 if ("http://hl7.org/fhir/StructureDefinition/regex".equals(extUrl)) { 1878 list.get(1).setExpression("ElementDefinition.type"); 1879 } 1880 if ("http://hl7.org/fhir/StructureDefinition/structuredefinition-normative-version".equals(extUrl)) { 1881 list.get(0).setExpression("Element"); // well, it can't be used anywhere but the list of places it can be used is quite long 1882 } 1883 if (!VersionUtilities.isThisOrLater("4.6", context.getVersion())) { 1884 if (Utilities.existsInList(extUrl, "http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation", "http://hl7.org/fhir/StructureDefinition/capabilitystatement-prohibited")) { 1885 list.get(0).setExpression("Element"); // well, they can't be used anywhere but the list of places they can be used is quite long 1886 } 1887 } 1888 return list; 1889 } 1890 1891 private String stripIndexes(String path) { 1892 boolean skip = false; 1893 StringBuilder b = new StringBuilder(); 1894 for (char c : path.toCharArray()) { 1895 if (skip) { 1896 if (c == ']') { 1897 skip = false; 1898 } 1899 } else if (c == '[') { 1900 skip = true; 1901 } else { 1902 b.append(c); 1903 } 1904 } 1905 return b.toString(); 1906 } 1907 1908 @SuppressWarnings("rawtypes") 1909 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) { 1910 if ((fixed == null || fixed.isEmpty()) && focus == null) { 1911 ; // this is all good 1912 } else if ((fixed == null || fixed.isEmpty()) && focus != null) { 1913 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, pattern, I18nConstants.PROFILE_VAL_NOTALLOWED, focus.getName(), (pattern ? "pattern" : "fixed value")); 1914 } else if (fixed != null && !fixed.isEmpty() && focus == null) { 1915 rule(errors, IssueType.VALUE, parent == null ? -1 : parent.line(), parent == null ? -1 : parent.col(), path, false, I18nConstants.PROFILE_VAL_MISSINGELEMENT, propName, fixedSource); 1916 } else { 1917 String value = focus.primitiveValue(); 1918 if (fixed instanceof org.hl7.fhir.r5.model.BooleanType) 1919 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()); 1920 else if (fixed instanceof org.hl7.fhir.r5.model.IntegerType) 1921 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()); 1922 else if (fixed instanceof org.hl7.fhir.r5.model.DecimalType) 1923 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()); 1924 else if (fixed instanceof org.hl7.fhir.r5.model.Base64BinaryType) 1925 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()); 1926 else if (fixed instanceof org.hl7.fhir.r5.model.InstantType) 1927 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()); 1928 else if (fixed instanceof org.hl7.fhir.r5.model.CodeType) 1929 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()); 1930 else if (fixed instanceof org.hl7.fhir.r5.model.Enumeration) 1931 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()); 1932 else if (fixed instanceof org.hl7.fhir.r5.model.StringType) 1933 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()); 1934 else if (fixed instanceof org.hl7.fhir.r5.model.UriType) 1935 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()); 1936 else if (fixed instanceof org.hl7.fhir.r5.model.DateType) 1937 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()); 1938 else if (fixed instanceof org.hl7.fhir.r5.model.DateTimeType) 1939 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()); 1940 else if (fixed instanceof org.hl7.fhir.r5.model.OidType) 1941 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()); 1942 else if (fixed instanceof org.hl7.fhir.r5.model.UuidType) 1943 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()); 1944 else if (fixed instanceof org.hl7.fhir.r5.model.IdType) 1945 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()); 1946 else if (fixed instanceof Quantity) 1947 checkQuantity(errors, path, focus, (Quantity) fixed, fixedSource, pattern); 1948 else if (fixed instanceof Address) 1949 checkAddress(errors, path, focus, (Address) fixed, fixedSource, pattern); 1950 else if (fixed instanceof ContactPoint) 1951 checkContactPoint(errors, path, focus, (ContactPoint) fixed, fixedSource, pattern); 1952 else if (fixed instanceof Attachment) 1953 checkAttachment(errors, path, focus, (Attachment) fixed, fixedSource, pattern); 1954 else if (fixed instanceof Identifier) 1955 checkIdentifier(errors, path, focus, (Identifier) fixed, fixedSource, pattern); 1956 else if (fixed instanceof Coding) 1957 checkCoding(errors, path, focus, (Coding) fixed, fixedSource, pattern); 1958 else if (fixed instanceof HumanName) 1959 checkHumanName(errors, path, focus, (HumanName) fixed, fixedSource, pattern); 1960 else if (fixed instanceof CodeableConcept) 1961 checkCodeableConcept(errors, path, focus, (CodeableConcept) fixed, fixedSource, pattern); 1962 else if (fixed instanceof Timing) 1963 checkTiming(errors, path, focus, (Timing) fixed, fixedSource, pattern); 1964 else if (fixed instanceof Period) 1965 checkPeriod(errors, path, focus, (Period) fixed, fixedSource, pattern); 1966 else if (fixed instanceof Range) 1967 checkRange(errors, path, focus, (Range) fixed, fixedSource, pattern); 1968 else if (fixed instanceof Ratio) 1969 checkRatio(errors, path, focus, (Ratio) fixed, fixedSource, pattern); 1970 else if (fixed instanceof SampledData) 1971 checkSampledData(errors, path, focus, (SampledData) fixed, fixedSource, pattern); 1972 else if (fixed instanceof Reference) 1973 checkReference(errors, path, focus, (Reference) fixed, fixedSource, pattern); 1974 1975 else 1976 rule(errors, IssueType.EXCEPTION, focus.line(), focus.col(), path, false, I18nConstants.INTERNAL_INT_BAD_TYPE, fixed.fhirType()); 1977 List<Element> extensions = new ArrayList<Element>(); 1978 focus.getNamedChildren("extension", extensions); 1979 if (fixed.getExtension().size() == 0) { 1980 rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, extensions.size() == 0 || pattern == true, I18nConstants.EXTENSION_EXT_FIXED_BANNED); 1981 } 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()))) { 1982 for (Extension e : fixed.getExtension()) { 1983 Element ex = getExtensionByUrl(extensions, e.getUrl()); 1984 if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, ex != null, I18nConstants.EXTENSION_EXT_COUNT_NOTFOUND, e.getUrl())) { 1985 checkFixedValue(errors, path, ex.getNamedChild("extension").getNamedChild("value"), e.getValue(), fixedSource, "extension.value", ex.getNamedChild("extension"), false); 1986 } 1987 } 1988 } 1989 } 1990 } 1991 1992 private void checkHumanName(List<ValidationMessage> errors, String path, Element focus, HumanName fixed, String fixedSource, boolean pattern) { 1993 checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), fixedSource, "use", focus, pattern); 1994 checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), fixedSource, "text", focus, pattern); 1995 checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), fixedSource, "period", focus, pattern); 1996 1997 List<Element> parts = new ArrayList<Element>(); 1998 if (!pattern || fixed.hasFamily()) { 1999 focus.getNamedChildren("family", parts); 2000 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()))) { 2001 for (int i = 0; i < parts.size(); i++) 2002 checkFixedValue(errors, path + ".family", parts.get(i), fixed.getFamilyElement(), fixedSource, "family", focus, pattern); 2003 } 2004 } 2005 if (!pattern || fixed.hasGiven()) { 2006 focus.getNamedChildren("given", parts); 2007 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()))) { 2008 for (int i = 0; i < parts.size(); i++) 2009 checkFixedValue(errors, path + ".given", parts.get(i), fixed.getGiven().get(i), fixedSource, "given", focus, pattern); 2010 } 2011 } 2012 if (!pattern || fixed.hasPrefix()) { 2013 focus.getNamedChildren("prefix", parts); 2014 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()))) { 2015 for (int i = 0; i < parts.size(); i++) 2016 checkFixedValue(errors, path + ".prefix", parts.get(i), fixed.getPrefix().get(i), fixedSource, "prefix", focus, pattern); 2017 } 2018 } 2019 if (!pattern || fixed.hasSuffix()) { 2020 focus.getNamedChildren("suffix", parts); 2021 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()))) { 2022 for (int i = 0; i < parts.size(); i++) 2023 checkFixedValue(errors, path + ".suffix", parts.get(i), fixed.getSuffix().get(i), fixedSource, "suffix", focus, pattern); 2024 } 2025 } 2026 } 2027 2028 private void checkIdentifier(List<ValidationMessage> errors, String path, Element element, ElementDefinition context) { 2029 String system = element.getNamedChildValue("system"); 2030 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, system == null || isIdentifierSystemReferenceValid(system), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_IDENTIFIER_SYSTEM); 2031 if ("urn:ietf:rfc:3986".equals(system)) { 2032 String value = element.getNamedChildValue("value"); 2033 rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, value == null || isAbsolute(value), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_IDENTIFIER_IETF_SYSTEM_VALUE); 2034 } 2035 } 2036 2037 private void checkIdentifier(List<ValidationMessage> errors, String path, Element focus, Identifier fixed, String fixedSource, boolean pattern) { 2038 checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), fixedSource, "use", focus, pattern); 2039 checkFixedValue(errors, path + ".type", focus.getNamedChild(TYPE), fixed.getType(), fixedSource, TYPE, focus, pattern); 2040 checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), fixedSource, "system", focus, pattern); 2041 checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), fixedSource, "value", focus, pattern); 2042 checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), fixedSource, "period", focus, pattern); 2043 checkFixedValue(errors, path + ".assigner", focus.getNamedChild("assigner"), fixed.getAssigner(), fixedSource, "assigner", focus, pattern); 2044 } 2045 2046 private void checkPeriod(List<ValidationMessage> errors, String path, Element focus, Period fixed, String fixedSource, boolean pattern) { 2047 checkFixedValue(errors, path + ".start", focus.getNamedChild("start"), fixed.getStartElement(), fixedSource, "start", focus, pattern); 2048 checkFixedValue(errors, path + ".end", focus.getNamedChild("end"), fixed.getEndElement(), fixedSource, "end", focus, pattern); 2049 } 2050 2051 private void checkPrimitive(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, String type, ElementDefinition context, Element e, StructureDefinition profile, NodeStack node) throws FHIRException { 2052 if (isBlank(e.primitiveValue())) { 2053 if (e.primitiveValue() == null) 2054 rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_VALUEEXT); 2055 else if (e.primitiveValue().length() == 0) 2056 rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_NOTEMPTY); 2057 else if (StringUtils.isWhitespace(e.primitiveValue())) 2058 warning(errors, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_WS); 2059 if (context.hasBinding()) { 2060 rule(errors, IssueType.CODEINVALID, e.line(), e.col(), path, context.getBinding().getStrength() != BindingStrength.REQUIRED, I18nConstants.Terminology_TX_Code_ValueSet_MISSING); 2061 } 2062 return; 2063 } else { 2064 boolean hasBiDiControls = UnicodeUtilities.hasBiDiChars(e.primitiveValue()); 2065 if (hasBiDiControls) { 2066 if (rule(errors, IssueType.CODEINVALID, e.line(), e.col(), path, !noUnicodeBiDiControlChars, I18nConstants.UNICODE_BIDI_CONTROLS_CHARS_DISALLOWED, UnicodeUtilities.replaceBiDiChars(e.primitiveValue()))) { 2067 String msg = UnicodeUtilities.checkUnicodeWellFormed(e.primitiveValue()); 2068 warning(errors, IssueType.INVALID, e.line(), e.col(), path, msg == null, I18nConstants.UNICODE_BIDI_CONTROLS_CHARS_MATCH, msg); 2069 } 2070 } 2071 } 2072 String regex = context.getExtensionString(ToolingExtensions.EXT_REGEX); 2073 if (regex != null) { 2074 rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().matches(regex), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX, e.primitiveValue(), regex); 2075 } 2076 if (!"xhtml".equals(type)) { 2077 if (securityChecks) { 2078 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !containsHtmlTags(e.primitiveValue()), I18nConstants.SECURITY_STRING_CONTENT_ERROR); 2079 } else { 2080 hint(errors, IssueType.INVALID, e.line(), e.col(), path, !containsHtmlTags(e.primitiveValue()), I18nConstants.SECURITY_STRING_CONTENT_WARNING); 2081 } 2082 } 2083 2084 2085 if (type.equals("boolean")) { 2086 rule(errors, IssueType.INVALID, e.line(), e.col(), path, "true".equals(e.primitiveValue()) || "false".equals(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BOOLEAN_VALUE); 2087 } 2088 if (type.equals("uri") || type.equals("oid") || type.equals("uuid") || type.equals("url") || type.equals("canonical")) { 2089 String url = e.primitiveValue(); 2090 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !url.startsWith("oid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URI_OID); 2091 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !url.startsWith("uuid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URI_UUID); 2092 rule(errors, IssueType.INVALID, e.line(), e.col(), path, url.equals(url.trim().replace(" ", "")) 2093 // work around an old invalid example in a core package 2094 || "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); 2095 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()); 2096 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()); 2097 2098 if (type.equals("oid")) { 2099 rule(errors, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("urn:oid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_OID_START); 2100 } 2101 if (type.equals("uuid")) { 2102 rule(errors, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("urn:uuid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_UUID_STRAT); 2103 } 2104 if (type.equals("canonical")) { 2105 rule(errors, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("#") || Utilities.isAbsoluteUrl(url), I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_ABSOLUTE, url); 2106 } 2107 2108 if (url != null && url.startsWith("urn:uuid:")) { 2109 rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isValidUUID(url.substring(9)), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_UUID_VALID); 2110 } 2111 if (url != null && url.startsWith("urn:oid:")) { 2112 rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isOid(url.substring(8)), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_OID_VALID); 2113 } 2114 2115 if (isCanonicalURLElement(e)) { 2116 // we get to here if this is a defining canonical URL (e.g. CodeSystem.url) 2117 // the URL must be an IRI if present 2118 rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isAbsoluteUrl(url), 2119 node.isContained() ? I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_CONTAINED : I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_ABSOLUTE, url); 2120 } else { 2121 validateReference(hostContext, errors, path, type, context, e, url); 2122 } 2123 } 2124 if (type.equals(ID) && !"Resource.id".equals(context.getBase().getPath())) { 2125 // work around an old issue with ElementDefinition.id 2126 if (!context.getPath().equals("ElementDefinition.id")) { 2127 rule(errors, IssueType.INVALID, e.line(), e.col(), path, FormatUtilities.isValidId(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ID_VALID, e.primitiveValue()); 2128 } 2129 } 2130 if (type.equalsIgnoreCase("string") && e.hasPrimitiveValue()) { 2131 if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || e.primitiveValue().length() > 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_NOTEMPTY)) { 2132 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); 2133 if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().length() <= 1048576, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_STRING_LENGTH)) { 2134 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()); 2135 } 2136 } 2137 } 2138 if (type.equals("dateTime")) { 2139 warning(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue()); 2140 rule(errors, IssueType.INVALID, e.line(), e.col(), path, 2141 e.primitiveValue() 2142 .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()); 2143 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !hasTime(e.primitiveValue()) || hasTimeZone(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_TZ); 2144 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()); 2145 try { 2146 DateTimeType dt = new DateTimeType(e.primitiveValue()); 2147 } catch (Exception ex) { 2148 rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_VALID, ex.getMessage()); 2149 } 2150 } 2151 if (type.equals("time")) { 2152 rule(errors, IssueType.INVALID, e.line(), e.col(), path, 2153 e.primitiveValue() 2154 .matches("([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_TIME_VALID); 2155 try { 2156 TimeType dt = new TimeType(e.primitiveValue()); 2157 } catch (Exception ex) { 2158 rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_TIME_VALID, ex.getMessage()); 2159 } 2160 } 2161 if (type.equals("date")) { 2162 warning(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue()); 2163 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); 2164 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()); 2165 try { 2166 DateType dt = new DateType(e.primitiveValue()); 2167 } catch (Exception ex) { 2168 rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATE_VALID, ex.getMessage()); 2169 } 2170 } 2171 if (type.equals("base64Binary")) { 2172 String encoded = e.primitiveValue(); 2173 if (isNotBlank(encoded)) { 2174 boolean ok = isValidBase64(encoded); 2175 if (!ok) { 2176 String value = encoded.length() < 100 ? encoded : "(snip)"; 2177 rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_VALID, value); 2178 } else { 2179 boolean wsok = !base64HasWhitespace(encoded); 2180 if (VersionUtilities.isR5VerOrLater(this.context.getVersion())) { 2181 rule(errors, IssueType.INVALID, e.line(), e.col(), path, wsok, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_ERROR); 2182 } else { 2183 warning(errors, IssueType.INVALID, e.line(), e.col(), path, wsok, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_WARNING); 2184 } 2185 } 2186 if (ok && context.hasExtension("http://hl7.org/fhir/StructureDefinition/maxSize")) { 2187 int size = countBase64DecodedBytes(encoded); 2188 long def = Long.parseLong(ToolingExtensions.readStringExtension(context, "http://hl7.org/fhir/StructureDefinition/maxSize")); 2189 rule(errors, IssueType.STRUCTURE, e.line(), e.col(), path, size <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_TOO_LONG, size, def); 2190 } 2191 2192 } 2193 } 2194 if (type.equals("integer") || type.equals("unsignedInt") || type.equals("positiveInt")) { 2195 if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isInteger(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_VALID, e.primitiveValue())) { 2196 Integer v = new Integer(e.getValue()).intValue(); 2197 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() : "")); 2198 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() : "")); 2199 if (type.equals("unsignedInt")) 2200 rule(errors, IssueType.INVALID, e.line(), e.col(), path, v >= 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT0); 2201 if (type.equals("positiveInt")) 2202 rule(errors, IssueType.INVALID, e.line(), e.col(), path, v > 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT1); 2203 } 2204 } 2205 if (type.equals("integer64")) { 2206 if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isLong(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER64_VALID, e.primitiveValue())) { 2207 Long v = new Long(e.getValue()).longValue(); 2208 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() : "")); 2209 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() : "")); 2210 if (type.equals("unsignedInt")) 2211 rule(errors, IssueType.INVALID, e.line(), e.col(), path, v >= 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT0); 2212 if (type.equals("positiveInt")) 2213 rule(errors, IssueType.INVALID, e.line(), e.col(), path, v > 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT1); 2214 } 2215 } 2216 if (type.equals("decimal")) { 2217 if (e.primitiveValue() != null) { 2218 DecimalStatus ds = Utilities.checkDecimal(e.primitiveValue(), true, false); 2219 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())) { 2220 warning(errors, IssueType.VALUE, e.line(), e.col(), path, ds != DecimalStatus.RANGE, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_RANGE, e.primitiveValue()); 2221 try { 2222 Decimal v = new Decimal(e.getValue()); 2223 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxValueIntegerType() || 2224 !context.getMaxValueIntegerType().hasValue() || checkDecimalMaxValue(v, context.getMaxValueDecimalType().getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_GT, (context.hasMaxValueIntegerType() ? context.getMaxValueIntegerType() : "")); 2225 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMinValueIntegerType() || 2226 !context.getMinValueIntegerType().hasValue() || checkDecimalMinValue(v, context.getMaxValueDecimalType().getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_LT, (context.hasMinValueIntegerType() ? context.getMinValueIntegerType() : "")); 2227 } catch (Exception ex) { 2228 // should never happen? 2229 } 2230 } 2231 } 2232 if (context.hasExtension("http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces")) { 2233 int dp = e.primitiveValue().contains(".") ? e.primitiveValue().substring(e.primitiveValue().indexOf(".")+1).length() : 0; 2234 int def = Integer.parseInt(ToolingExtensions.readStringExtension(context, "http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces")); 2235 rule(errors, IssueType.STRUCTURE, e.line(), e.col(), path, dp <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_CHARS, dp, def); 2236 } 2237 } 2238 if (type.equals("instant")) { 2239 rule(errors, IssueType.INVALID, e.line(), e.col(), path, 2240 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()); 2241 warning(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue()); 2242 try { 2243 InstantType dt = new InstantType(e.primitiveValue()); 2244 } catch (Exception ex) { 2245 rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INSTANT_VALID, ex.getMessage()); 2246 } 2247 } 2248 2249 if (type.equals("code") && e.primitiveValue() != null) { 2250 // 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 2251 // other than single spaces in the contents 2252 rule(errors, IssueType.INVALID, e.line(), e.col(), path, passesCodeWhitespaceRules(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CODE_WS, e.primitiveValue()); 2253 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()); 2254 } 2255 2256 if (context.hasBinding() && e.primitiveValue() != null) { 2257 checkPrimitiveBinding(hostContext, errors, path, type, context, e, profile, node); 2258 } 2259 2260 if (type.equals("xhtml")) { 2261 XhtmlNode xhtml = e.getXhtml(); 2262 if (xhtml != null) { // if it is null, this is an error already noted in the parsers 2263 // check that the namespace is there and correct. 2264 String ns = xhtml.getNsDecl(); 2265 rule(errors, IssueType.INVALID, e.line(), e.col(), path, FormatUtilities.XHTML_NS.equals(ns), I18nConstants.XHTML_XHTML_NS_INVALID, ns, FormatUtilities.XHTML_NS); 2266 // check that inner namespaces are all correct 2267 checkInnerNS(errors, e, path, xhtml.getChildNodes()); 2268 rule(errors, IssueType.INVALID, e.line(), e.col(), path, "div".equals(xhtml.getName()), I18nConstants.XHTML_XHTML_NAME_INVALID, ns); 2269 // check that no illegal elements and attributes have been used 2270 checkInnerNames(errors, e, path, xhtml.getChildNodes(), false); 2271 checkUrls(errors, e, path, xhtml.getChildNodes()); 2272 } 2273 } 2274 2275 if (context.hasFixed()) { 2276 checkFixedValue(errors, path, e, context.getFixed(), profile.getUrl(), context.getSliceName(), null, false); 2277 } 2278 if (context.hasPattern()) { 2279 checkFixedValue(errors, path, e, context.getPattern(), profile.getUrl(), context.getSliceName(), null, true); 2280 } 2281 2282 // for nothing to check 2283 } 2284 2285 public void validateReference(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, String type, ElementDefinition context, Element e, String url) { 2286 // now, do we check the URI target? 2287 if (fetcher != null && !type.equals("uuid")) { 2288 boolean found; 2289 try { 2290 found = isDefinitionURL(url) || (allowExamples && (url.contains("example.org") || url.contains("acme.com")) || url.contains("acme.org")) || (url.startsWith("http://hl7.org/fhir/tools")) || 2291 SpecialExtensions.isKnownExtension(url) || isXverUrl(url); 2292 if (!found) { 2293 found = fetcher.resolveURL(this, hostContext, path, url, type); 2294 } 2295 } catch (IOException e1) { 2296 found = false; 2297 } 2298 if (!found) { 2299 if (type.equals("canonical")) { 2300 ReferenceValidationPolicy rp = policyAdvisor == null ? ReferenceValidationPolicy.CHECK_VALID : policyAdvisor.policyForReference(this, hostContext, path, url); 2301 if (rp == ReferenceValidationPolicy.CHECK_EXISTS || rp == ReferenceValidationPolicy.CHECK_EXISTS_AND_TYPE) { 2302 rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE, url); 2303 } else { 2304 hint(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE, url); 2305 } 2306 } else { 2307 if (url.contains("hl7.org") || url.contains("fhir.org")) { 2308 rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URL_RESOLVE, url); 2309 } else if (url.contains("example.org") || url.contains("acme.com")) { 2310 rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URL_EXAMPLE, url); 2311 } else { 2312 warning(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URL_RESOLVE, url); 2313 } 2314 } 2315 } else { 2316 if (type.equals("canonical")) { 2317 ReferenceValidationPolicy rp = policyAdvisor == null ? ReferenceValidationPolicy.CHECK_VALID : policyAdvisor.policyForReference(this, hostContext, path, url); 2318 if (rp == ReferenceValidationPolicy.CHECK_EXISTS_AND_TYPE || rp == ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS || rp == ReferenceValidationPolicy.CHECK_VALID) { 2319 try { 2320 Resource r = null; 2321 if (url.startsWith("#")) { 2322 r = loadContainedResource(errors, path, hostContext.getRootResource(), url.substring(1), Resource.class); 2323 } 2324 if (r == null) { 2325 fetcher.fetchCanonicalResource(this, url); 2326 } 2327 if (r == null) { 2328 r = this.context.fetchResource(Resource.class, url); 2329 } 2330 if (r == null) { 2331 warning(errors, IssueType.INVALID, e.line(), e.col(), path, rp != ReferenceValidationPolicy.CHECK_VALID, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE_NC, url); 2332 } 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))) { 2333 if (rp == ReferenceValidationPolicy.CHECK_VALID) { 2334 // todo.... 2335 } 2336 } 2337 } catch (Exception ex) { 2338 // won't happen 2339 } 2340 } 2341 } 2342 } 2343 } 2344 } 2345 2346 private Set<String> listExpectedCanonicalTypes(ElementDefinition context) { 2347 Set<String> res = new HashSet<>(); 2348 TypeRefComponent tr = context.getType("canonical"); 2349 if (tr != null) { 2350 for (CanonicalType p : tr.getTargetProfile()) { 2351 String url = p.getValue(); 2352 StructureDefinition sd = this.context.fetchResource(StructureDefinition.class, url); 2353 if (sd != null) { 2354 res.add(sd.getType()); 2355 } else { 2356 if (url != null && url.startsWith("http://hl7.org/fhir/StructureDefinition/")) { 2357 res.add(url.substring("http://hl7.org/fhir/StructureDefinition/".length())); 2358 } 2359 } 2360 } 2361 } 2362 return res; 2363 } 2364 2365 private boolean isCorrectCanonicalType(Resource r, ElementDefinition context) { 2366 TypeRefComponent tr = context.getType("canonical"); 2367 if (tr != null) { 2368 for (CanonicalType p : tr.getTargetProfile()) { 2369 if (isCorrectCanonicalType(r, p)) { 2370 return true; 2371 } 2372 } 2373 if (tr.getTargetProfile().isEmpty()) { 2374 return true; 2375 } 2376 } 2377 return false; 2378 } 2379 2380 private boolean isCorrectCanonicalType(Resource r, CanonicalType p) { 2381 String url = p.getValue(); 2382 String t = null; 2383 StructureDefinition sd = context.fetchResource(StructureDefinition.class, url); 2384 if (sd != null) { 2385 t = sd.getType(); 2386 } else if (url.startsWith("http://hl7.org/fhir/StructureDefinition/")) { 2387 t = url.substring("http://hl7.org/fhir/StructureDefinition/".length()); 2388 } else { 2389 return false; 2390 } 2391 return Utilities.existsInList(t, "Resource", "CanonicalResource") || t.equals(r.fhirType()); 2392 } 2393 2394 private boolean isCanonicalURLElement(Element e) { 2395 if (e.getProperty() == null || e.getProperty().getDefinition() == null) { 2396 return false; 2397 } 2398 String path = e.getProperty().getDefinition().getBase().getPath(); 2399 if (path == null) { 2400 return false; 2401 } 2402 String[] p = path.split("\\."); 2403 if (p.length != 2) { 2404 return false; 2405 } 2406 if (!"url".equals(p[1])) { 2407 return false; 2408 } 2409 return Utilities.existsInList(p[0], VersionUtilities.getCanonicalResourceNames(context.getVersion())); 2410 } 2411 2412 private boolean containsHtmlTags(String cnt) { 2413 int i = cnt.indexOf("<"); 2414 while (i > -1) { 2415 cnt = cnt.substring(i+1); 2416 i = cnt.indexOf("<"); 2417 int e = cnt.indexOf(">"); 2418 if (e > -1 && e < i) { 2419 String s = cnt.substring(0, e); 2420 if (s.matches(HTML_FRAGMENT_REGEX)) { 2421 return true; 2422 } 2423 } 2424 } 2425 return false; 2426 } 2427 2428 /** 2429 * Technically this is not bulletproof as some invalid base64 won't be caught, 2430 * but I think it's good enough. The original code used Java8 Base64 decoder 2431 * but I've replaced it with a regex for 2 reasons: 2432 * 1. This code will run on any version of Java 2433 * 2. This code doesn't actually decode, which is much easier on memory use for big payloads 2434 */ 2435 private boolean isValidBase64(String theEncoded) { 2436 if (theEncoded == null) { 2437 return false; 2438 } 2439 int charCount = 0; 2440 boolean ok = true; 2441 for (int i = 0; i < theEncoded.length(); i++) { 2442 char nextChar = theEncoded.charAt(i); 2443 if (Character.isWhitespace(nextChar)) { 2444 continue; 2445 } 2446 if (Character.isLetterOrDigit(nextChar)) { 2447 charCount++; 2448 } 2449 if (nextChar == '/' || nextChar == '=' || nextChar == '+') { 2450 charCount++; 2451 } 2452 } 2453 2454 if (charCount > 0 && charCount % 4 != 0) { 2455 ok = false; 2456 } 2457 return ok; 2458 } 2459 2460 private boolean base64HasWhitespace(String theEncoded) { 2461 if (theEncoded == null) { 2462 return false; 2463 } 2464 for (int i = 0; i < theEncoded.length(); i++) { 2465 char nextChar = theEncoded.charAt(i); 2466 if (Character.isWhitespace(nextChar)) { 2467 return true; 2468 } 2469 } 2470 return false; 2471 2472 } 2473 2474 2475 private int countBase64DecodedBytes(String theEncoded) { 2476 Base64InputStream inputStream = new Base64InputStream(new ByteArrayInputStream(theEncoded.getBytes(StandardCharsets.UTF_8))); 2477 try { 2478 try { 2479 for (int counter = 0; ; counter++) { 2480 if (inputStream.read() == -1) { 2481 return counter; 2482 } 2483 } 2484 } finally { 2485 inputStream.close(); 2486 } 2487 } catch (IOException e) { 2488 throw new IllegalStateException(e); // should not happen 2489 } 2490 } 2491 2492 private boolean isDefinitionURL(String url) { 2493 return Utilities.existsInList(url, "http://hl7.org/fhirpath/System.Boolean", "http://hl7.org/fhirpath/System.String", "http://hl7.org/fhirpath/System.Integer", 2494 "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"); 2495 } 2496 2497 private void checkInnerNames(List<ValidationMessage> errors, Element e, String path, List<XhtmlNode> list, boolean inPara) { 2498 for (XhtmlNode node : list) { 2499 if (node.getNodeType() == NodeType.Comment) { 2500 rule(errors, IssueType.INVALID, e.line(), e.col(), path, !node.getContent().startsWith("DOCTYPE"), I18nConstants.XHTML_XHTML_DOCTYPE_ILLEGAL); 2501 } 2502 if (node.getNodeType() == NodeType.Element) { 2503 rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.existsInList(node.getName(), 2504 "p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", "b", "em", "i", "strong", 2505 "small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup", 2506 "ul", "ol", "li", "dl", "dt", "dd", "pre", "table", "caption", "colgroup", "col", "thead", "tr", "tfoot", "tbody", "th", "td", 2507 "code", "samp", "img", "map", "area"), I18nConstants.XHTML_XHTML_ELEMENT_ILLEGAL, node.getName()); 2508 2509 for (String an : node.getAttributes().keySet()) { 2510 boolean ok = an.startsWith("xmlns") || Utilities.existsInList(an, 2511 "title", "style", "class", ID, "lang", "xml:lang", "dir", "accesskey", "tabindex", 2512 // tables 2513 "span", "width", "align", "valign", "char", "charoff", "abbr", "axis", "headers", "scope", "rowspan", "colspan") || 2514 2515 Utilities.existsInList(node.getName() + "." + an, "a.href", "a.name", "img.src", "img.border", "div.xmlns", "blockquote.cite", "q.cite", 2516 "a.charset", "a.type", "a.name", "a.href", "a.hreflang", "a.rel", "a.rev", "a.shape", "a.coords", "img.src", 2517 "img.alt", "img.longdesc", "img.height", "img.width", "img.usemap", "img.ismap", "map.name", "area.shape", 2518 "area.coords", "area.href", "area.nohref", "area.alt", "table.summary", "table.width", "table.border", 2519 "table.frame", "table.rules", "table.cellspacing", "table.cellpadding", "pre.space", "td.nowrap" 2520 ); 2521 if (!ok) { 2522 rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.XHTML_XHTML_ATTRIBUTE_ILLEGAL, an, node.getName()); 2523 } 2524 } 2525 2526 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()); 2527 2528 checkInnerNames(errors, e, path, node.getChildNodes(), inPara || "p".equals(node.getName())); 2529 } 2530 } 2531 } 2532 2533 private void checkUrls(List<ValidationMessage> errors, Element e, String path, List<XhtmlNode> list) { 2534 for (XhtmlNode node : list) { 2535 if (node.getNodeType() == NodeType.Element) { 2536 if ("a".equals(node.getName())) { 2537 String msg = checkValidUrl(node.getAttribute("href")); 2538 rule(errors, IssueType.INVALID, e.line(), e.col(), path, msg == null, I18nConstants.XHTML_URL_INVALID, node.getAttribute("href"), msg); 2539 } else if ("img".equals(node.getName())) { 2540 String msg = checkValidUrl(node.getAttribute("src")); 2541 rule(errors, IssueType.INVALID, e.line(), e.col(), path, msg == null, I18nConstants.XHTML_URL_INVALID, node.getAttribute("src"), msg); 2542 } 2543 checkUrls(errors, e, path, node.getChildNodes()); 2544 } 2545 } 2546 } 2547 2548 private String checkValidUrl(String value) { 2549 if (value == null) { 2550 return null; 2551 } 2552 if (Utilities.noString(value)) { 2553 return context.formatMessage(I18nConstants.XHTML_URL_EMPTY); 2554 } 2555 2556 if (value.startsWith("data:")) { 2557 String[] p = value.substring(5).split("\\,"); 2558 if (p.length < 2) { 2559 return context.formatMessage(I18nConstants.XHTML_URL_DATA_NO_DATA, value); 2560 } else if (p.length > 2) { 2561 return context.formatMessage(I18nConstants.XHTML_URL_DATA_DATA_INVALID_COMMA, value); 2562 } else if (!p[0].endsWith(";base64") || !isValidBase64(p[1])) { 2563 return context.formatMessage(I18nConstants.XHTML_URL_DATA_DATA_INVALID, value); 2564 } else { 2565 if (p[0].startsWith(" ")) { 2566 p[0] = p[0].trim(); 2567 } 2568 String mMsg = checkValidMimeType(p[0].substring(0, p[0].lastIndexOf(";"))); 2569 if (mMsg != null) { 2570 return context.formatMessage(I18nConstants.XHTML_URL_DATA_MIMETYPE, value, mMsg); 2571 } 2572 } 2573 return null; 2574 } else { 2575 Set<Character> invalidChars = new HashSet<>(); 2576 for (char ch : value.toCharArray()) { 2577 if (!(Character.isDigit(ch) || Character.isAlphabetic(ch) || Utilities.existsInList(ch, ';', '?', ':', '@', '&', '=', '+', '$', '.', ',', '/', '%', '-', '_', '~', '#', '[', ']', '!', '\'', '(', ')', '*' ))) { 2578 invalidChars.add(ch); 2579 } 2580 } 2581 if (invalidChars.isEmpty()) { 2582 return null; 2583 } else { 2584 return context.formatMessage(I18nConstants.XHTML_URL_INVALID_CHARS, invalidChars.toString()); 2585 } 2586 } 2587 } 2588 2589 private String checkValidMimeType(String mt) { 2590 if (!mt.matches("^(\\w+|\\*)\\/(\\w+|\\*)((;\\s*(\\w+)=\\s*(\\S+))?)$")) { 2591 return "Mime type invalid"; 2592 } 2593 return null; 2594 } 2595 2596 private void checkInnerNS(List<ValidationMessage> errors, Element e, String path, List<XhtmlNode> list) { 2597 for (XhtmlNode node : list) { 2598 if (node.getNodeType() == NodeType.Element) { 2599 String ns = node.getNsDecl(); 2600 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); 2601 checkInnerNS(errors, e, path, node.getChildNodes()); 2602 } 2603 } 2604 } 2605 2606 private void checkPrimitiveBinding(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, String type, ElementDefinition elementContext, Element element, StructureDefinition profile, NodeStack stack) { 2607 // We ignore bindings that aren't on string, uri or code 2608 if (!element.hasPrimitiveValue() || !("code".equals(type) || "string".equals(type) || "uri".equals(type) || "url".equals(type) || "canonical".equals(type))) { 2609 return; 2610 } 2611 if (noTerminologyChecks) 2612 return; 2613 2614 String value = element.primitiveValue(); 2615 // System.out.println("check "+value+" in "+path); 2616 2617 // firstly, resolve the value set 2618 ElementDefinitionBindingComponent binding = elementContext.getBinding(); 2619 if (binding.hasValueSet()) { 2620 ValueSet vs = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl()); 2621 if (vs == null) { 2622 CodeSystem cs = context.fetchCodeSystem(binding.getValueSet()); 2623 if (rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(binding.getValueSet()))) { 2624 warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(binding.getValueSet())); 2625 } 2626 } else { 2627 CodedContentValidationPolicy validationPolicy = getPolicyAdvisor() == null ? 2628 CodedContentValidationPolicy.VALUESET : getPolicyAdvisor().policyForCodedContent(this, hostContext, stack.getLiteralPath(), elementContext, profile, BindingKind.PRIMARY, vs, new ArrayList<>()); 2629 2630 if (validationPolicy != CodedContentValidationPolicy.IGNORE) { 2631 long t = System.nanoTime(); 2632 ValidationResult vr = null; 2633 if (binding.getStrength() != BindingStrength.EXAMPLE) { 2634 ValidationOptions options = baseOptions.setLanguage(stack.getWorkingLang()).guessSystem(); 2635 if (validationPolicy == CodedContentValidationPolicy.CODE) { 2636 options = options.noCheckValueSetMembership(); 2637 } 2638 vr = checkCodeOnServer(stack, vs, value, options); 2639 } 2640 timeTracker.tx(t, "vc "+value+""); 2641 if (binding.getStrength() == BindingStrength.REQUIRED) { 2642 removeTrackedMessagesForLocation(errors, element, path); 2643 } 2644 if (vr != null && !vr.isOk()) { 2645 if (vr.IsNoService()) 2646 txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_15, value); 2647 else if (binding.getStrength() == BindingStrength.REQUIRED) 2648 txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_16, value, describeValueSet(binding.getValueSet()), getErrorMessage(vr.getMessage())); 2649 else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { 2650 if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) 2651 checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), value, stack); 2652 else if (!noExtensibleWarnings && !isOkExtension(value, vs)) 2653 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())); 2654 } else if (binding.getStrength() == BindingStrength.PREFERRED) { 2655 if (baseOnly) { 2656 txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_18, value, describeValueSet(binding.getValueSet()), getErrorMessage(vr.getMessage())); 2657 } 2658 } 2659 } 2660 } 2661 } 2662 } else if (!noBindingMsgSuppressed) 2663 hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, !type.equals("code"), I18nConstants.TERMINOLOGY_TX_BINDING_NOSOURCE2); 2664 } 2665 2666 private boolean isOkExtension(String value, ValueSet vs) { 2667 if ("http://hl7.org/fhir/ValueSet/defined-types".equals(vs.getUrl())) { 2668 return value.startsWith("http://hl7.org/fhirpath/System."); 2669 } 2670 return false; 2671 } 2672 2673 private void checkQuantity(List<ValidationMessage> errors, String path, Element focus, Quantity fixed, String fixedSource, boolean pattern) { 2674 checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), fixedSource, "value", focus, pattern); 2675 checkFixedValue(errors, path + ".comparator", focus.getNamedChild("comparator"), fixed.getComparatorElement(), fixedSource, "comparator", focus, pattern); 2676 checkFixedValue(errors, path + ".units", focus.getNamedChild("unit"), fixed.getUnitElement(), fixedSource, "units", focus, pattern); 2677 checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), fixedSource, "system", focus, pattern); 2678 checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), fixedSource, "code", focus, pattern); 2679 } 2680 2681 private void checkQuantity(List<ValidationMessage> errors, String path, Element element, StructureDefinition theProfile, ElementDefinition definition, NodeStack theStack) { 2682 String value = element.hasChild("value") ? element.getNamedChild("value").getValue() : null; 2683 String unit = element.hasChild("unit") ? element.getNamedChild("unit").getValue() : null; 2684 String system = element.hasChild("system") ? element.getNamedChild("system").getValue() : null; 2685 String code = element.hasChild("code") ? element.getNamedChild("code").getValue() : null; 2686 2687 // todo: allowedUnits http://hl7.org/fhir/StructureDefinition/elementdefinition-allowedUnits - codeableConcept, or canonical(ValueSet) 2688 // todo: http://hl7.org/fhir/StructureDefinition/iso21090-PQ-translation 2689 2690 if (!Utilities.noString(value) && definition.hasExtension("http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces")) { 2691 int dp = value.contains(".") ? value.substring(value.indexOf(".")+1).length() : 0; 2692 int def = Integer.parseInt(ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces")); 2693 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, dp <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_CHARS, dp, def); 2694 } 2695 2696 if (system != null || code != null ) { 2697 checkCodedElement(errors, path, element, theProfile, definition, false, false, theStack, code, system, null, unit); 2698 } 2699 2700 if (code != null && "http://unitsofmeasure.org".equals(system)) { 2701 int b = code.indexOf("{"); 2702 int e = code.indexOf("}"); 2703 if (b >= 0 && e > 0 && b < e) { 2704 bpCheck(errors, IssueType.BUSINESSRULE, element.line(), element.col(), path, !code.contains("{"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_NO_ANNOTATIONS, code.substring(b, e+1)); 2705 } 2706 } 2707 2708 if (definition.hasMinValue()) { 2709 if (warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(value), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_NO_VALUE) && 2710 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())) { 2711 Quantity min = definition.getMinValueQuantity(); 2712 if (warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(min.getSystem()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_SYSTEM) && 2713 warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(system), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_NO_SYSTEM) && 2714 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()) && 2715 warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(min.getCode()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_CODE) && 2716 warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(code), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_NO_CODE) && 2717 rule(errors, IssueType.INVALID, element.line(), element.col(), path, definition.getMinValueQuantity().hasValue(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_VALUE)) { 2718 if (code.equals(min.getCode())) { 2719 // straight value comparison 2720 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()); 2721 } else if ("http://unitsofmeasure.org".equals(system)) { 2722 if (warning(errors, IssueType.INVALID, element.line(), element.col(), path, context.getUcumService() != null, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_NO_UCUM_SVC)) { 2723 Decimal v = convertUcumValue(value, code, min.getCode()); 2724 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())) { 2725 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()); 2726 } 2727 } 2728 } else { 2729 warning(errors, IssueType.INVALID, element.line(), element.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_CODE_MISMATCH, code, min.getCode()); 2730 } 2731 } 2732 } 2733 } 2734 2735 if (definition.hasMaxValue()) { 2736 if (warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(value), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_NO_VALUE) && 2737 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())) { 2738 Quantity max = definition.getMaxValueQuantity(); 2739 if (warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(max.getSystem()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_SYSTEM) && 2740 warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(system), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_NO_SYSTEM) && 2741 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()) && 2742 warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(max.getCode()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_CODE) && 2743 warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(code), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_NO_CODE) && 2744 rule(errors, IssueType.INVALID, element.line(), element.col(), path, definition.getMaxValueQuantity().hasValue(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_VALUE)) { 2745 if (code.equals(max.getCode())) { 2746 // straight value comparison 2747 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()); 2748 } else if ("http://unitsofmeasure.org".equals(system)) { 2749 if (warning(errors, IssueType.INVALID, element.line(), element.col(), path, context.getUcumService() != null, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_NO_UCUM_SVC)) { 2750 Decimal v = convertUcumValue(value, code, max.getCode()); 2751 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())) { 2752 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()); 2753 } 2754 } 2755 } else { 2756 warning(errors, IssueType.INVALID, element.line(), element.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_CODE_MISMATCH, code, max.getCode()); 2757 } 2758 } 2759 } 2760 } 2761 } 2762 2763 private Decimal convertUcumValue(String value, String code, String minCode) { 2764 try { 2765 Decimal v = new Decimal(value); 2766 return context.getUcumService().convert(v, code, minCode); 2767 } catch (Exception e) { 2768 return null; 2769 } 2770 } 2771 2772 private boolean checkDecimalMaxValue(Decimal value, BigDecimal min) { 2773 try { 2774 Decimal m = new Decimal(min.toString()); 2775 return value.comparesTo(m) <= 0; 2776 } catch (Exception e) { 2777 return false; // this will be another error somewhere else? 2778 } 2779 } 2780 2781 private boolean checkDecimalMaxValue(String value, BigDecimal min) { 2782 try { 2783 BigDecimal v = new BigDecimal(value); 2784 return v.compareTo(min) <= 0; 2785 } catch (Exception e) { 2786 return false; // this will be another error somewhere else 2787 } 2788 } 2789 2790 private boolean checkDecimalMinValue(Decimal value, BigDecimal min) { 2791 try { 2792 Decimal m = new Decimal(min.toString()); 2793 return value.comparesTo(m) >= 0; 2794 } catch (Exception e) { 2795 return false; // this will be another error somewhere else? 2796 } 2797 } 2798 2799 private boolean checkDecimalMinValue(String value, BigDecimal min) { 2800 try { 2801 BigDecimal v = new BigDecimal(value); 2802 return v.compareTo(min) >= 0; 2803 } catch (Exception e) { 2804 return false; // this will be another error somewhere else 2805 } 2806 } 2807 2808 private void checkAttachment(List<ValidationMessage> errors, String path, Element element, StructureDefinition theProfile, ElementDefinition definition, boolean theInCodeableConcept, boolean theCheckDisplayInContext, NodeStack theStack) { 2809 long size = -1; 2810 // first check size 2811 String fetchError = null; 2812 if (element.hasChild("data")) { 2813 String b64 = element.getChildValue("data"); 2814 // Note: If the value isn't valid, we're not adding an error here, as the test to the 2815 // child Base64Binary will catch it and we don't want to log it twice 2816 boolean ok = isValidBase64(b64); 2817 if (ok && element.hasChild("size")) { 2818 size = countBase64DecodedBytes(b64); 2819 String sz = element.getChildValue("size"); 2820 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, Long.toString(size).equals(sz), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_CORRECT, sz, size); 2821 } 2822 } else if (element.hasChild("size")) { 2823 String sz = element.getChildValue("size"); 2824 if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, Utilities.isLong(sz), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_INVALID, sz)) { 2825 size = Long.parseLong(sz); 2826 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, size >= 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_INVALID, sz); 2827 } 2828 } else if (element.hasChild("url")) { 2829 String url = element.getChildValue("url"); 2830 if (definition.hasExtension("http://hl7.org/fhir/StructureDefinition/maxSize")) { 2831 try { 2832 if (url.startsWith("http://") || url.startsWith("https://")) { 2833 if (fetcher == null) { 2834 fetchError = context.formatMessage(I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_NO_FETCHER, url); 2835 } else { 2836 byte[] cnt = fetcher.fetchRaw(this, url); 2837 size = cnt.length; 2838 } 2839 } else if (url.startsWith("file:")) { 2840 size = new File(url.substring(5)).length(); 2841 } else { 2842 fetchError = context.formatMessage(I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_UNKNOWN_URL_SCHEME, url); } 2843 } catch (Exception e) { 2844 if (STACK_TRACE) e.printStackTrace(); 2845 fetchError = context.formatMessage(I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_URL_ERROR, url, e.getMessage()); 2846 } 2847 } 2848 } 2849 if (definition.hasExtension("http://hl7.org/fhir/StructureDefinition/maxSize")) { 2850 if (warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, size >= 0, fetchError)) { 2851 long def = Long.parseLong(ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/maxSize")); 2852 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, size <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_TOO_LONG, size, def); 2853 } 2854 } 2855 warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, (element.hasChild("data") || element.hasChild("url")) || (element.hasChild("contentType") || element.hasChild("language")), 2856 I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_NO_CONTENT); 2857 } 2858 2859 // implementation 2860 2861 private void checkRange(List<ValidationMessage> errors, String path, Element focus, Range fixed, String fixedSource, boolean pattern) { 2862 checkFixedValue(errors, path + ".low", focus.getNamedChild("low"), fixed.getLow(), fixedSource, "low", focus, pattern); 2863 checkFixedValue(errors, path + ".high", focus.getNamedChild("high"), fixed.getHigh(), fixedSource, "high", focus, pattern); 2864 2865 } 2866 2867 private void checkRatio(List<ValidationMessage> errors, String path, Element focus, Ratio fixed, String fixedSource, boolean pattern) { 2868 checkFixedValue(errors, path + ".numerator", focus.getNamedChild("numerator"), fixed.getNumerator(), fixedSource, "numerator", focus, pattern); 2869 checkFixedValue(errors, path + ".denominator", focus.getNamedChild("denominator"), fixed.getDenominator(), fixedSource, "denominator", focus, pattern); 2870 } 2871 2872 private void checkReference(ValidatorHostContext hostContext, 2873 List<ValidationMessage> errors, 2874 String path, 2875 Element element, 2876 StructureDefinition profile, 2877 ElementDefinition container, 2878 String parentType, 2879 NodeStack stack) throws FHIRException { 2880 Reference reference = ObjectConverter.readAsReference(element); 2881 2882 String ref = reference.getReference(); 2883 if (Utilities.noString(ref)) { 2884 if (!path.contains("element.pattern")) { // this business rule doesn't apply to patterns 2885 if (Utilities.noString(reference.getIdentifier().getSystem()) && Utilities.noString(reference.getIdentifier().getValue())) { 2886 warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, 2887 !Utilities.noString(element.getNamedChildValue("display")), I18nConstants.REFERENCE_REF_NODISPLAY); 2888 } 2889 } 2890 return; 2891 } else if (Utilities.existsInList(ref, "http://tools.ietf.org/html/bcp47")) { 2892 // special known URLs that can't be validated but are known to be valid 2893 return; 2894 } 2895 warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, !isSuspiciousReference(ref), I18nConstants.REFERENCE_REF_SUSPICIOUS, ref); 2896 2897 ResolvedReference we = localResolve(ref, stack, errors, path, hostContext.getRootResource(), hostContext.getGroupingResource(), element); 2898 String refType; 2899 if (ref.startsWith("#")) { 2900 refType = "contained"; 2901 } else { 2902 if (we == null) { 2903 refType = "remote"; 2904 } else { 2905 refType = "bundled"; 2906 } 2907 } 2908 ReferenceValidationPolicy pol; 2909 if (refType.equals("contained") || refType.equals("bundled")) { 2910 pol = ReferenceValidationPolicy.CHECK_VALID; 2911 } else { 2912 if (policyAdvisor == null) pol = ReferenceValidationPolicy.IGNORE; 2913 else pol = policyAdvisor.policyForReference(this, hostContext.getAppContext(), path, ref); 2914 } 2915 2916 if (pol.checkExists()) { 2917 if (we == null) { 2918 if (!refType.equals("contained")) { 2919 if (fetcher == null) { 2920 throw new FHIRException(context.formatMessage(I18nConstants.RESOURCE_RESOLUTION_SERVICES_NOT_PROVIDED)); 2921 } else { 2922 Element ext = null; 2923 if (fetchCache.containsKey(ref)) { 2924 ext = fetchCache.get(ref); 2925 } else { 2926 try { 2927 ext = fetcher.fetch(this, hostContext.getAppContext(), ref); 2928 } catch (IOException e) { 2929 if (STACK_TRACE) e.printStackTrace(); 2930 throw new FHIRException(e); 2931 } 2932 if (ext != null) { 2933 setParents(ext); 2934 fetchCache.put(ref, ext); 2935 } 2936 } 2937 we = ext == null ? null : makeExternalRef(ext, path); 2938 } 2939 } 2940 } 2941 boolean ok = (allowExamples && (ref.contains("example.org") || ref.contains("acme.com"))) 2942 || (we != null || pol == ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS); 2943 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ok, I18nConstants.REFERENCE_REF_CANTRESOLVE, ref); 2944 } 2945 2946 String ft; 2947 if (we != null) { 2948 ft = we.getType(); 2949 } else { 2950 ft = tryParse(ref); 2951 } 2952 2953 if (reference.hasType()) { // R4 onwards... 2954 // the type has to match the specified 2955 String tu = isAbsolute(reference.getType()) ? reference.getType() : "http://hl7.org/fhir/StructureDefinition/" + reference.getType(); 2956 TypeRefComponent containerType = container.getType("Reference"); 2957 if (!containerType.hasTargetProfile(tu) 2958 && !containerType.hasTargetProfile("http://hl7.org/fhir/StructureDefinition/Resource") 2959 && !containerType.getTargetProfile().isEmpty() 2960 ) { 2961 boolean matchingResource = false; 2962 for (CanonicalType target : containerType.getTargetProfile()) { 2963 StructureDefinition sd = resolveProfile(profile, target.asStringValue()); 2964 if (rule(errors, IssueType.NOTFOUND, element.line(), element.col(), path, sd != null, 2965 I18nConstants.REFERENCE_REF_CANTRESOLVEPROFILE, target.asStringValue())) { 2966 if (("http://hl7.org/fhir/StructureDefinition/" + sd.getType()).equals(tu)) { 2967 matchingResource = true; 2968 break; 2969 } 2970 } 2971 } 2972 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, matchingResource, 2973 I18nConstants.REFERENCE_REF_WRONGTARGET, reference.getType(), container.getType("Reference").getTargetProfile()); 2974 2975 } 2976 // the type has to match the actual 2977 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, 2978 ft == null || ft.equals(reference.getType()), I18nConstants.REFERENCE_REF_BADTARGETTYPE, reference.getType(), ft); 2979 } 2980 2981 if (we != null && pol.checkType()) { 2982 if (warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ft != null, 2983 I18nConstants.REFERENCE_REF_NOTYPE)) { 2984 // we validate as much as we can. First, can we infer a type from the profile? 2985 boolean ok = false; 2986 TypeRefComponent type = getReferenceTypeRef(container.getType()); 2987 if (type.hasTargetProfile() && !type.hasTargetProfile("http://hl7.org/fhir/StructureDefinition/Resource")) { 2988 Set<String> types = new HashSet<>(); 2989 List<StructureDefinition> profiles = new ArrayList<>(); 2990 for (UriType u : type.getTargetProfile()) { 2991 StructureDefinition sd = resolveProfile(profile, u.getValue()); 2992 if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, sd != null, 2993 I18nConstants.REFERENCE_REF_CANTRESOLVEPROFILE, u.getValue())) { 2994 types.add(sd.getType()); 2995 if (ft.equals(sd.getType())) { 2996 ok = true; 2997 profiles.add(sd); 2998 } 2999 } 3000 } 3001 if (!pol.checkValid()) { 3002 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, profiles.size() > 0, 3003 I18nConstants.REFERENCE_REF_CANTMATCHTYPE, ref, StringUtils.join("; ", type.getTargetProfile())); 3004 } else { 3005 Map<StructureDefinition, List<ValidationMessage>> badProfiles = new HashMap<>(); 3006 Map<StructureDefinition, List<ValidationMessage>> goodProfiles = new HashMap<>(); 3007 int goodCount = 0; 3008 for (StructureDefinition pr : profiles) { 3009 List<ValidationMessage> profileErrors = new ArrayList<ValidationMessage>(); 3010 validateResource(we.hostContext(hostContext, pr), profileErrors, we.getResource(), we.getFocus(), pr, 3011 IdStatus.OPTIONAL, we.getStack().resetIds()); 3012 if (!hasErrors(profileErrors)) { 3013 goodCount++; 3014 goodProfiles.put(pr, profileErrors); 3015 trackUsage(pr, hostContext, element); 3016 } else { 3017 badProfiles.put(pr, profileErrors); 3018 } 3019 } 3020 if (goodCount == 1) { 3021 if (showMessagesFromReferences) { 3022 for (ValidationMessage vm : goodProfiles.values().iterator().next()) { 3023 if (!errors.contains(vm)) { 3024 errors.add(vm); 3025 } 3026 } 3027 } 3028 3029 } else if (goodProfiles.size() == 0) { 3030 if (!isShowMessagesFromReferences()) { 3031 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, areAllBaseProfiles(profiles), 3032 I18nConstants.REFERENCE_REF_CANTMATCHCHOICE, ref, asList(type.getTargetProfile())); 3033 for (StructureDefinition sd : badProfiles.keySet()) { 3034 slicingHint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, false, 3035 context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, ref, sd.getUrl()), 3036 errorSummaryForSlicingAsHtml(badProfiles.get(sd)), errorSummaryForSlicingAsText(badProfiles.get(sd))); 3037 } 3038 } else { 3039 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, profiles.size() == 1, 3040 I18nConstants.REFERENCE_REF_CANTMATCHCHOICE, ref, asList(type.getTargetProfile())); 3041 for (List<ValidationMessage> messages : badProfiles.values()) { 3042 for (ValidationMessage vm : messages) { 3043 if (!errors.contains(vm)) { 3044 errors.add(vm); 3045 } 3046 } 3047 } 3048 } 3049 } else { 3050 if (!isShowMessagesFromReferences()) { 3051 warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, 3052 I18nConstants.REFERENCE_REF_MULTIPLEMATCHES, ref, asListByUrl(goodProfiles.keySet())); 3053 for (StructureDefinition sd : badProfiles.keySet()) { 3054 slicingHint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, 3055 false, context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, ref, sd.getUrl()), 3056 errorSummaryForSlicingAsHtml(badProfiles.get(sd)), errorSummaryForSlicingAsText(badProfiles.get(sd))); 3057 } 3058 } else { 3059 warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, 3060 I18nConstants.REFERENCE_REF_MULTIPLEMATCHES, ref, asListByUrl(goodProfiles.keySet())); 3061 for (List<ValidationMessage> messages : goodProfiles.values()) { 3062 for (ValidationMessage vm : messages) { 3063 if (!errors.contains(vm)) { 3064 errors.add(vm); 3065 } 3066 } 3067 } 3068 } 3069 } 3070 } 3071 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ok, 3072 I18nConstants.REFERENCE_REF_BADTARGETTYPE, ft, types.toString()); 3073 } 3074 if (type.hasAggregation() && !noCheckAggregation) { 3075 boolean modeOk = false; 3076 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 3077 for (Enumeration<AggregationMode> mode : type.getAggregation()) { 3078 b.append(mode.getCode()); 3079 if (mode.getValue().equals(AggregationMode.CONTAINED) && refType.equals("contained")) 3080 modeOk = true; 3081 else if (mode.getValue().equals(AggregationMode.BUNDLED) && refType.equals("bundled")) 3082 modeOk = true; 3083 else if (mode.getValue().equals(AggregationMode.REFERENCED) && (refType.equals("bundled") || refType.equals("remote"))) 3084 modeOk = true; 3085 } 3086 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, modeOk, 3087 I18nConstants.REFERENCE_REF_AGGREGATION, refType, b.toString()); 3088 } 3089 } 3090 } 3091 if (we == null) { 3092 TypeRefComponent type = getReferenceTypeRef(container.getType()); 3093 boolean okToRef = !type.hasAggregation() || type.hasAggregation(AggregationMode.REFERENCED); 3094 rule(errors, IssueType.REQUIRED, -1, -1, path, okToRef, I18nConstants.REFERENCE_REF_NOTFOUND_BUNDLE, ref); 3095 } 3096 if (we == null && ft != null && assumeValidRestReferences) { 3097 // if we == null, we inferred ft from the reference. if we are told to treat this as gospel 3098 TypeRefComponent type = getReferenceTypeRef(container.getType()); 3099 Set<String> types = new HashSet<>(); 3100 StructureDefinition sdFT = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+ft); 3101 boolean ok = false; 3102 for (CanonicalType tp : type.getTargetProfile()) { 3103 StructureDefinition sd = context.fetchResource(StructureDefinition.class, tp.getValue()); 3104 if (sd != null) { 3105 types.add(sd.getType()); 3106 } 3107 StructureDefinition sdF = sdFT; 3108 while (sdF != null) { 3109 if (sdF.getType().equals(sd.getType())) { 3110 ok = true; 3111 break; 3112 } 3113 sdF = sdF.hasBaseDefinition() ? context.fetchResource(StructureDefinition.class, sdF.getBaseDefinition()) : null; 3114 } 3115 } 3116 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, types.isEmpty() || ok, 3117 I18nConstants.REFERENCE_REF_BADTARGETTYPE2, ft, ref, types); 3118 3119 } 3120 if (pol == ReferenceValidationPolicy.CHECK_VALID) { 3121 // todo.... 3122 } 3123 } 3124 3125 private boolean isSuspiciousReference(String url) { 3126 if (!assumeValidRestReferences || url == null || Utilities.isAbsoluteUrl(url) || url.startsWith("#")) { 3127 return false; 3128 } 3129 String[] parts = url.split("\\/"); 3130 if (parts.length == 2 && context.getResourceNames().contains(parts[0]) && Utilities.isValidId(parts[1])) { 3131 return false; 3132 } 3133 if (parts.length == 4 && context.getResourceNames().contains(parts[0]) && Utilities.isValidId(parts[1]) && "_history".equals(parts[2]) && Utilities.isValidId(parts[3])) { 3134 return false; 3135 } 3136 return true; 3137 } 3138 3139 private String asListByUrl(Collection<StructureDefinition> list) { 3140 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 3141 for (StructureDefinition sd : list) { 3142 b.append(sd.getUrl()); 3143 } 3144 return b.toString(); 3145 } 3146 3147 private String asList(Collection<CanonicalType> list) { 3148 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 3149 for (CanonicalType c : list) { 3150 b.append(c.getValue()); 3151 } 3152 return b.toString(); 3153 } 3154 3155 private boolean areAllBaseProfiles(List<StructureDefinition> profiles) { 3156 for (StructureDefinition sd : profiles) { 3157 if (!sd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/")) { 3158 return false; 3159 } 3160 } 3161 return true; 3162 } 3163 3164 private String errorSummaryForSlicing(List<ValidationMessage> list) { 3165 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 3166 for (ValidationMessage vm : list) { 3167 if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL || vm.isSlicingHint()) { 3168 b.append(vm.getLocation() + ": " + vm.getMessage()); 3169 } 3170 } 3171 return b.toString(); 3172 } 3173 3174 private String errorSummaryForSlicingAsHtml(List<ValidationMessage> list) { 3175 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 3176 for (ValidationMessage vm : list) { 3177 if (vm.isSlicingHint()) { 3178 b.append("<li>" + vm.getLocation() + ": " + vm.getSliceHtml() + "</li>"); 3179 } else if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL) { 3180 b.append("<li>" + vm.getLocation() + ": " + vm.getHtml() + "</li>"); 3181 } 3182 } 3183 return "<ul>" + b.toString() + "</ul>"; 3184 } 3185 3186 private boolean isCritical(List<ValidationMessage> list) { 3187 for (ValidationMessage vm : list) { 3188 if (vm.isSlicingHint() && vm.isCriticalSignpost()) { 3189 return true; 3190 } 3191 } 3192 return false; 3193 } 3194 3195 private String[] errorSummaryForSlicingAsText(List<ValidationMessage> list) { 3196 List<String> res = new ArrayList<String>(); 3197 for (ValidationMessage vm : list) { 3198 if (vm.isSlicingHint()) { 3199 if (vm.sliceText != null) { 3200 for (String s : vm.sliceText) { 3201 res.add(vm.getLocation() + ": " + s); 3202 } 3203 } else { 3204 res.add(vm.getLocation() + ": " + vm.getMessage()); 3205 } 3206 } else if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL) { 3207 res.add(vm.getLocation() + ": " + vm.getHtml()); 3208 } 3209 } 3210 return res.toArray(new String[0]); 3211 } 3212 3213 private TypeRefComponent getReferenceTypeRef(List<TypeRefComponent> types) { 3214 for (TypeRefComponent tr : types) { 3215 if ("Reference".equals(tr.getCode())) { 3216 return tr; 3217 } 3218 } 3219 return null; 3220 } 3221 3222 private String checkResourceType(String type) { 3223 long t = System.nanoTime(); 3224 try { 3225 if (context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + type) != null) 3226 return type; 3227 else 3228 return null; 3229 } finally { 3230 timeTracker.sd(t); 3231 } 3232 } 3233 3234 private void checkSampledData(List<ValidationMessage> errors, String path, Element focus, SampledData fixed, String fixedSource, boolean pattern) { 3235 checkFixedValue(errors, path + ".origin", focus.getNamedChild("origin"), fixed.getOrigin(), fixedSource, "origin", focus, pattern); 3236 checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriodElement(), fixedSource, "period", focus, pattern); 3237 checkFixedValue(errors, path + ".factor", focus.getNamedChild("factor"), fixed.getFactorElement(), fixedSource, "factor", focus, pattern); 3238 checkFixedValue(errors, path + ".lowerLimit", focus.getNamedChild("lowerLimit"), fixed.getLowerLimitElement(), fixedSource, "lowerLimit", focus, pattern); 3239 checkFixedValue(errors, path + ".upperLimit", focus.getNamedChild("upperLimit"), fixed.getUpperLimitElement(), fixedSource, "upperLimit", focus, pattern); 3240 checkFixedValue(errors, path + ".dimensions", focus.getNamedChild("dimensions"), fixed.getDimensionsElement(), fixedSource, "dimensions", focus, pattern); 3241 checkFixedValue(errors, path + ".data", focus.getNamedChild("data"), fixed.getDataElement(), fixedSource, "data", focus, pattern); 3242 } 3243 3244 private void checkReference(List<ValidationMessage> errors, String path, Element focus, Reference fixed, String fixedSource, boolean pattern) { 3245 checkFixedValue(errors, path + ".reference", focus.getNamedChild("reference"), fixed.getReferenceElement_(), fixedSource, "reference", focus, pattern); 3246 checkFixedValue(errors, path + ".type", focus.getNamedChild("type"), fixed.getTypeElement(), fixedSource, "type", focus, pattern); 3247 checkFixedValue(errors, path + ".identifier", focus.getNamedChild("identifier"), fixed.getIdentifier(), fixedSource, "identifier", focus, pattern); 3248 checkFixedValue(errors, path + ".display", focus.getNamedChild("display"), fixed.getDisplayElement(), fixedSource, "display", focus, pattern); 3249 } 3250 3251 private void checkTiming(List<ValidationMessage> errors, String path, Element focus, Timing fixed, String fixedSource, boolean pattern) { 3252 checkFixedValue(errors, path + ".repeat", focus.getNamedChild("repeat"), fixed.getRepeat(), fixedSource, "value", focus, pattern); 3253 3254 List<Element> events = new ArrayList<Element>(); 3255 focus.getNamedChildren("event", events); 3256 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()))) { 3257 for (int i = 0; i < events.size(); i++) 3258 checkFixedValue(errors, path + ".event", events.get(i), fixed.getEvent().get(i), fixedSource, "event", focus, pattern); 3259 } 3260 } 3261 3262 private boolean codeinExpansion(ValueSetExpansionContainsComponent cnt, String system, String code) { 3263 for (ValueSetExpansionContainsComponent c : cnt.getContains()) { 3264 if (code.equals(c.getCode()) && system.equals(c.getSystem().toString())) 3265 return true; 3266 if (codeinExpansion(c, system, code)) 3267 return true; 3268 } 3269 return false; 3270 } 3271 3272 private boolean codeInExpansion(ValueSet vs, String system, String code) { 3273 for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) { 3274 if (code.equals(c.getCode()) && (system == null || system.equals(c.getSystem()))) 3275 return true; 3276 if (codeinExpansion(c, system, code)) 3277 return true; 3278 } 3279 return false; 3280 } 3281 3282 private String describeReference(String reference, CanonicalResource target) { 3283 if (reference == null && target == null) 3284 return "null"; 3285 if (reference == null) { 3286 return target.getUrl(); 3287 } 3288 if (target == null) { 3289 return reference; 3290 } 3291 if (reference.equals(target.getUrl())) { 3292 return reference; 3293 } 3294 return reference + "(which actually refers to " + target.getUrl() + ")"; 3295 } 3296 3297 private String describeTypes(List<TypeRefComponent> types) { 3298 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 3299 for (TypeRefComponent t : types) { 3300 b.append(t.getWorkingCode()); 3301 } 3302 return b.toString(); 3303 } 3304 3305 protected ElementDefinition findElement(StructureDefinition profile, String name) { 3306 for (ElementDefinition c : profile.getSnapshot().getElement()) { 3307 if (c.getPath().equals(name)) { 3308 return c; 3309 } 3310 } 3311 return null; 3312 } 3313 3314 public BestPracticeWarningLevel getBestPracticeWarningLevel() { 3315 return bpWarnings; 3316 } 3317 3318 @Override 3319 public CheckDisplayOption getCheckDisplay() { 3320 return checkDisplay; 3321 } 3322 3323 private ConceptDefinitionComponent getCodeDefinition(ConceptDefinitionComponent c, String code) { 3324 if (code.equals(c.getCode())) 3325 return c; 3326 for (ConceptDefinitionComponent g : c.getConcept()) { 3327 ConceptDefinitionComponent r = getCodeDefinition(g, code); 3328 if (r != null) 3329 return r; 3330 } 3331 return null; 3332 } 3333 3334 private ConceptDefinitionComponent getCodeDefinition(CodeSystem cs, String code) { 3335 for (ConceptDefinitionComponent c : cs.getConcept()) { 3336 ConceptDefinitionComponent r = getCodeDefinition(c, code); 3337 if (r != null) 3338 return r; 3339 } 3340 return null; 3341 } 3342 3343 private IndexedElement getContainedById(Element container, String id) { 3344 List<Element> contained = new ArrayList<Element>(); 3345 container.getNamedChildren("contained", contained); 3346 for (int i = 0; i < contained.size(); i++) { 3347 Element we = contained.get(i); 3348 if (id.equals(we.getNamedChildValue(ID))) { 3349 return new IndexedElement(i, we, null); 3350 } 3351 } 3352 return null; 3353 } 3354 3355 public IWorkerContext getContext() { 3356 return context; 3357 } 3358 3359 private List<ElementDefinition> getCriteriaForDiscriminator(String path, ElementDefinition element, String discriminator, StructureDefinition profile, boolean removeResolve, StructureDefinition srcProfile) throws FHIRException { 3360 List<ElementDefinition> elements = new ArrayList<ElementDefinition>(); 3361 if ("value".equals(discriminator) && element.hasFixed()) { 3362 elements.add(element); 3363 return elements; 3364 } 3365 3366 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 3367 if (discriminator.equals("resolve()")) { 3368 elements.add(element); 3369 return elements; 3370 } 3371 if (discriminator.endsWith(".resolve()")) 3372 discriminator = discriminator.substring(0, discriminator.length() - 10); 3373 } 3374 3375 TypedElementDefinition ted = null; 3376 String fp = fixExpr(discriminator, null); 3377 ExpressionNode expr = null; 3378 try { 3379 expr = fpe.parse(fp); 3380 } catch (Exception e) { 3381 if (STACK_TRACE) e.printStackTrace(); 3382 throw new FHIRException(context.formatMessage(I18nConstants.DISCRIMINATOR_BAD_PATH, e.getMessage(), fp), e); 3383 } 3384 long t2 = System.nanoTime(); 3385 ted = fpe.evaluateDefinition(expr, profile, new TypedElementDefinition(element), srcProfile); 3386 timeTracker.sd(t2); 3387 if (ted != null) 3388 elements.add(ted.getElement()); 3389 3390 for (TypeRefComponent type : element.getType()) { 3391 for (CanonicalType p : type.getProfile()) { 3392 String id = p.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT) ? p.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT) : null; 3393 StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getValue()); 3394 if (sd == null) 3395 throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE_, p)); 3396 profile = sd; 3397 if (id == null) 3398 element = sd.getSnapshot().getElementFirstRep(); 3399 else { 3400 element = null; 3401 for (ElementDefinition t : sd.getSnapshot().getElement()) { 3402 if (id.equals(t.getId())) 3403 element = t; 3404 } 3405 if (element == null) 3406 throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_ELEMENT__IN_PROFILE_, id, p)); 3407 } 3408 expr = fpe.parse(fp); 3409 t2 = System.nanoTime(); 3410 ted = fpe.evaluateDefinition(expr, profile, new TypedElementDefinition(element), srcProfile); 3411 timeTracker.sd(t2); 3412 if (ted != null) 3413 elements.add(ted.getElement()); 3414 } 3415 } 3416 return elements; 3417 } 3418 3419 3420 private Element getExtensionByUrl(List<Element> extensions, String urlSimple) { 3421 for (Element e : extensions) { 3422 if (urlSimple.equals(e.getNamedChildValue("url"))) 3423 return e; 3424 } 3425 return null; 3426 } 3427 3428 public List<String> getExtensionDomains() { 3429 return extensionDomains; 3430 } 3431 3432 public List<ImplementationGuide> getImplementationGuides() { 3433 return igs; 3434 } 3435 3436 private StructureDefinition getProfileForType(String type, List<TypeRefComponent> list) { 3437 for (TypeRefComponent tr : list) { 3438 String url = tr.getWorkingCode(); 3439 if (!Utilities.isAbsoluteUrl(url)) 3440 url = "http://hl7.org/fhir/StructureDefinition/" + url; 3441 long t = System.nanoTime(); 3442 StructureDefinition sd = context.fetchResource(StructureDefinition.class, url); 3443 timeTracker.sd(t); 3444 if (sd != null && (sd.getType().equals(type) || sd.getUrl().equals(type)) && sd.hasSnapshot()) 3445 return sd; 3446 } 3447 return null; 3448 } 3449 3450 private Element getValueForDiscriminator(Object appContext, List<ValidationMessage> errors, Element element, String discriminator, ElementDefinition criteria, NodeStack stack) throws FHIRException, IOException { 3451 String p = stack.getLiteralPath() + "." + element.getName(); 3452 Element focus = element; 3453 String[] dlist = discriminator.split("\\."); 3454 for (String d : dlist) { 3455 if (focus.fhirType().equals("Reference") && d.equals("reference")) { 3456 String url = focus.getChildValue("reference"); 3457 if (Utilities.noString(url)) 3458 throw new FHIRException(context.formatMessage(I18nConstants.NO_REFERENCE_RESOLVING_DISCRIMINATOR__FROM_, discriminator, element.getProperty().getName())); 3459 // Note that we use the passed in stack here. This might be a problem if the discriminator is deep enough? 3460 Element target = resolve(appContext, url, stack, errors, p); 3461 if (target == null) 3462 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_RESOURCE__AT__RESOLVING_DISCRIMINATOR__FROM_, url, d, discriminator, element.getProperty().getName())); 3463 focus = target; 3464 } else if (d.equals("value") && focus.isPrimitive()) { 3465 return focus; 3466 } else { 3467 List<Element> children = focus.getChildren(d); 3468 if (children.isEmpty()) 3469 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_FIND__RESOLVING_DISCRIMINATOR__FROM_, d, discriminator, element.getProperty().getName())); 3470 if (children.size() > 1) 3471 throw new FHIRException(context.formatMessage(I18nConstants.FOUND__ITEMS_FOR__RESOLVING_DISCRIMINATOR__FROM_, Integer.toString(children.size()), d, discriminator, element.getProperty().getName())); 3472 focus = children.get(0); 3473 p = p + "." + d; 3474 } 3475 } 3476 return focus; 3477 } 3478 3479 private CodeSystem getCodeSystem(String system) { 3480 long t = System.nanoTime(); 3481 try { 3482 return context.fetchCodeSystem(system); 3483 } finally { 3484 timeTracker.tx(t, "cs "+system); 3485 } 3486 } 3487 3488 private boolean hasTime(String fmt) { 3489 return fmt.contains("T"); 3490 } 3491 3492 private boolean hasTimeZone(String fmt) { 3493 return fmt.length() > 10 && (fmt.substring(10).contains("-") || fmt.substring(10).contains("+") || fmt.substring(10).contains("Z")); 3494 } 3495 3496 private boolean isAbsolute(String uri) { 3497 String protocol = null; 3498 String tail = null; 3499 if (uri.contains(":")) { 3500 protocol = uri.substring(0, uri.indexOf(":")); 3501 tail = uri.substring(uri.indexOf(":")+1); 3502 } 3503 if (Utilities.isToken(protocol)) { 3504 if ("file".equals(protocol)) { 3505 return tail.startsWith("/") || tail.contains(":"); 3506 } else { 3507 return true; 3508 } 3509 } else { 3510 return false; 3511 } 3512 } 3513 3514 private boolean isCodeSystemReferenceValid(String uri) { 3515 return isSystemReferenceValid(uri); 3516 } 3517 3518 private boolean isIdentifierSystemReferenceValid(String uri) { 3519 return isSystemReferenceValid(uri) || uri.startsWith("ldap:"); 3520 } 3521 3522 private boolean isSystemReferenceValid(String uri) { 3523 return uri.startsWith("http:") || uri.startsWith("https:") || uri.startsWith("urn:"); 3524 } 3525 3526 public boolean isAnyExtensionsAllowed() { 3527 return anyExtensionsAllowed; 3528 } 3529 3530 public boolean isErrorForUnknownProfiles() { 3531 return errorForUnknownProfiles; 3532 } 3533 3534 public void setErrorForUnknownProfiles(boolean errorForUnknownProfiles) { 3535 this.errorForUnknownProfiles = errorForUnknownProfiles; 3536 } 3537 3538 private boolean isParametersEntry(String path) { 3539 String[] parts = path.split("\\."); 3540 return parts.length > 2 && parts[parts.length - 1].equals(RESOURCE) && (pathEntryHasName(parts[parts.length - 2], "parameter") || pathEntryHasName(parts[parts.length - 2], "part")); 3541 } 3542 3543 private boolean isBundleEntry(String path) { 3544 String[] parts = path.split("\\."); 3545 return parts.length > 2 && parts[parts.length - 1].equals(RESOURCE) && pathEntryHasName(parts[parts.length - 2], ENTRY); 3546 } 3547 3548 private boolean isBundleOutcome(String path) { 3549 String[] parts = path.split("\\."); 3550 return parts.length > 2 && parts[parts.length - 1].equals("outcome") && pathEntryHasName(parts[parts.length - 2], "response"); 3551 } 3552 3553 3554 private static boolean pathEntryHasName(String thePathEntry, String theName) { 3555 if (thePathEntry.equals(theName)) { 3556 return true; 3557 } 3558 if (thePathEntry.length() >= theName.length() + 3) { 3559 if (thePathEntry.startsWith(theName)) { 3560 if (thePathEntry.charAt(theName.length()) == '[') { 3561 return true; 3562 } 3563 } 3564 } 3565 return false; 3566 } 3567 3568 public boolean isPrimitiveType(String code) { 3569 StructureDefinition sd = context.fetchTypeDefinition(code); 3570 return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 3571 } 3572 3573 private String getErrorMessage(String message) { 3574 return message != null ? " (error message = " + message + ")" : ""; 3575 } 3576 3577 public boolean isSuppressLoincSnomedMessages() { 3578 return suppressLoincSnomedMessages; 3579 } 3580 3581 private boolean nameMatches(String name, String tail) { 3582 if (tail.endsWith("[x]")) 3583 return name.startsWith(tail.substring(0, tail.length() - 3)); 3584 else 3585 return (name.equals(tail)); 3586 } 3587 3588 private boolean passesCodeWhitespaceRules(String v) { 3589 if (!v.trim().equals(v)) 3590 return false; 3591 boolean lastWasSpace = true; 3592 for (char c : v.toCharArray()) { 3593 if (c == ' ') { 3594 if (lastWasSpace) 3595 return false; 3596 else 3597 lastWasSpace = true; 3598 } else if (Character.isWhitespace(c)) 3599 return false; 3600 else 3601 lastWasSpace = false; 3602 } 3603 return true; 3604 } 3605 3606 private ResolvedReference localResolve(String ref, NodeStack stack, List<ValidationMessage> errors, String path, Element rootResource, Element groupingResource, Element source) { 3607 if (ref.startsWith("#")) { 3608 // work back through the parent list. 3609 // really, there should only be one level for this (contained resources cannot contain 3610 // contained resources), but we'll leave that to some other code to worry about 3611 boolean wasContained = false; 3612 NodeStack nstack = stack; 3613 while (nstack != null && nstack.getElement() != null) { 3614 if (nstack.getElement().getProperty().isResource()) { 3615 // ok, we'll try to find the contained reference 3616 if (ref.equals("#") && nstack.getElement().getSpecial() != SpecialElement.CONTAINED && wasContained) { 3617 ResolvedReference rr = new ResolvedReference(); 3618 rr.setResource(nstack.getElement()); 3619 rr.setFocus(nstack.getElement()); 3620 rr.setExternal(false); 3621 rr.setStack(nstack); 3622// rr.getStack().qualifyPath(".ofType("+nstack.getElement().fhirType()+")"); 3623// System.out.println("-->"+nstack.getLiteralPath()); 3624 return rr; 3625 } 3626 if (nstack.getElement().getSpecial() == SpecialElement.CONTAINED) { 3627 wasContained = true; 3628 } 3629 IndexedElement res = getContainedById(nstack.getElement(), ref.substring(1)); 3630 if (res != null) { 3631 ResolvedReference rr = new ResolvedReference(); 3632 rr.setResource(nstack.getElement()); 3633 rr.setFocus(res.getMatch()); 3634 rr.setExternal(false); 3635 rr.setStack(nstack.push(res.getMatch(), res.getIndex(), res.getMatch().getProperty().getDefinition(), res.getMatch().getProperty().getDefinition())); 3636 rr.getStack().qualifyPath(".ofType("+nstack.getElement().fhirType()+")"); 3637 return rr; 3638 } 3639 } 3640 if (nstack.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY || nstack.getElement().getSpecial() == SpecialElement.PARAMETER) { 3641 return null; // we don't try to resolve contained references across this boundary 3642 } 3643 nstack = nstack.getParent(); 3644 } 3645 // try again, and work up the element parent list 3646 if (ref.equals("#")) { 3647 Element e = stack.getElement(); 3648 while (e != null) { 3649 if (e.getProperty().isResource() && (e.getSpecial() != SpecialElement.CONTAINED)) { 3650 ResolvedReference rr = new ResolvedReference(); 3651 rr.setResource(e); 3652 rr.setFocus(e); 3653 rr.setExternal(false); 3654 rr.setStack(stack.push(e, -1, e.getProperty().getDefinition(), e.getProperty().getDefinition())); 3655 rr.getStack().qualifyPath(".ofType("+e.fhirType()+")"); 3656 return rr; 3657 } 3658 e = e.getParentForValidator(); 3659 } 3660 } 3661 return null; 3662 } else { 3663 // work back through the parent list - if any of them are bundles, try to resolve 3664 // the resource in the bundle 3665 String fullUrl = null; // we're going to try to work this out as we go up 3666 while (stack != null && stack.getElement() != null) { 3667 if (stack.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY && fullUrl == null && stack.getParent() != null && stack.getParent().getElement().getName().equals(ENTRY)) { 3668 String type = stack.getParent().getParent().getElement().getChildValue(TYPE); 3669 fullUrl = stack.getParent().getElement().getChildValue(FULL_URL); // we don't try to resolve contained references across this boundary 3670 if (fullUrl == null) 3671 rule(errors, IssueType.REQUIRED, stack.getParent().getElement().line(), stack.getParent().getElement().col(), stack.getParent().getLiteralPath(), 3672 Utilities.existsInList(type, "batch-response", "transaction-response") || fullUrl != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOFULLURL); 3673 } 3674 if (BUNDLE.equals(stack.getElement().getType())) { 3675 String type = stack.getElement().getChildValue(TYPE); 3676 IndexedElement res = getFromBundle(stack.getElement(), ref, fullUrl, errors, path, type, "transaction".equals(type)); 3677 if (res == null) { 3678 return null; 3679 } else { 3680 ResolvedReference rr = new ResolvedReference(); 3681 rr.setResource(res.getMatch()); 3682 rr.setFocus(res.getMatch()); 3683 rr.setExternal(false); 3684 rr.setStack(stack.push(res.getEntry(), res.getIndex(), res.getEntry().getProperty().getDefinition(), 3685 res.getEntry().getProperty().getDefinition()).push(res.getMatch(), -1, 3686 res.getMatch().getProperty().getDefinition(), res.getMatch().getProperty().getDefinition())); 3687 rr.getStack().qualifyPath(".ofType("+rr.getResource().fhirType()+")"); 3688 return rr; 3689 } 3690 } 3691 if (stack.getElement().getSpecial() == SpecialElement.PARAMETER && stack.getParent() != null) { 3692 NodeStack tgt = findInParams(stack.getParent().getParent(), ref); 3693 if (tgt != null) { 3694 ResolvedReference rr = new ResolvedReference(); 3695 rr.setResource(tgt.getElement()); 3696 rr.setFocus(tgt.getElement()); 3697 rr.setExternal(false); 3698 rr.setStack(tgt); 3699 rr.getStack().qualifyPath(".ofType("+tgt.getElement().fhirType()+")"); 3700 return rr; 3701 } 3702 } 3703 stack = stack.getParent(); 3704 } 3705 // we can get here if we got called via FHIRPath conformsTo which breaks the stack continuity. 3706 if (groupingResource != null && BUNDLE.equals(groupingResource.fhirType())) { // it could also be a Parameters resource - that case isn't handled yet 3707 String type = groupingResource.getChildValue(TYPE); 3708 Element entry = getEntryForSource(groupingResource, source); 3709 fullUrl = entry.getChildValue(FULL_URL); 3710 IndexedElement res = getFromBundle(groupingResource, ref, fullUrl, errors, path, type, "transaction".equals(type)); 3711 if (res == null) { 3712 return null; 3713 } else { 3714 ResolvedReference rr = new ResolvedReference(); 3715 rr.setResource(res.getMatch()); 3716 rr.setFocus(res.getMatch()); 3717 rr.setExternal(false); 3718 rr.setStack(new NodeStack(context, null, rootResource, validationLanguage).push(res.getEntry(), res.getIndex(), res.getEntry().getProperty().getDefinition(), 3719 res.getEntry().getProperty().getDefinition()).push(res.getMatch(), -1, 3720 res.getMatch().getProperty().getDefinition(), res.getMatch().getProperty().getDefinition())); 3721 rr.getStack().qualifyPath(".ofType("+rr.getResource().fhirType()+")"); 3722 return rr; 3723 } 3724 } 3725 } 3726 return null; 3727 } 3728 3729 private NodeStack findInParams(NodeStack params, String ref) { 3730 int i = 0; 3731 for (Element child : params.getElement().getChildren("parameter")) { 3732 NodeStack p = params.push(child, i, child.getProperty().getDefinition(), child.getProperty().getDefinition()); 3733 if (child.hasChild("resource")) { 3734 Element res = child.getNamedChild("resource"); 3735 if ((res.fhirType()+"/"+res.getIdBase()).equals(ref)) { 3736 return p.push(res, -1, res.getProperty().getDefinition(), res.getProperty().getDefinition()); 3737 } 3738 } 3739 NodeStack pc = findInParamParts(p, child, ref); 3740 if (pc != null) { 3741 return pc; 3742 } 3743 } 3744 return null; 3745 } 3746 3747 private NodeStack findInParamParts(NodeStack pp, Element param, String ref) { 3748 int i = 0; 3749 for (Element child : param.getChildren("part")) { 3750 NodeStack p = pp.push(child, i, child.getProperty().getDefinition(), child.getProperty().getDefinition()); 3751 if (child.hasChild("resource")) { 3752 Element res = child.getNamedChild("resource"); 3753 if ((res.fhirType()+"/"+res.getIdBase()).equals(ref)) { 3754 return p.push(res, -1, res.getProperty().getDefinition(), res.getProperty().getDefinition()); 3755 } 3756 } 3757 NodeStack pc = findInParamParts(p, child, ref); 3758 if (pc != null) { 3759 return pc; 3760 } 3761 } 3762 return null; 3763 } 3764 3765 private Element getEntryForSource(Element bundle, Element element) { 3766 List<Element> entries = new ArrayList<Element>(); 3767 bundle.getNamedChildren(ENTRY, entries); 3768 for (Element entry : entries) { 3769 if (entry.hasDescendant(element)) { 3770 return entry; 3771 } 3772 } 3773 return null; 3774 } 3775 3776 private ResolvedReference makeExternalRef(Element external, String path) { 3777 ResolvedReference res = new ResolvedReference(); 3778 res.setResource(external); 3779 res.setFocus(external); 3780 res.setExternal(true); 3781 res.setStack(new NodeStack(context, external, path, validationLanguage)); 3782 return res; 3783 } 3784 3785 3786 private Element resolve(Object appContext, String ref, NodeStack stack, List<ValidationMessage> errors, String path) throws IOException, FHIRException { 3787 Element local = localResolve(ref, stack, errors, path, null, null, null).getFocus(); 3788 if (local != null) 3789 return local; 3790 if (fetcher == null) 3791 return null; 3792 if (fetchCache.containsKey(ref)) { 3793 return fetchCache.get(ref); 3794 } else { 3795 Element res = fetcher.fetch(this, appContext, ref); 3796 setParents(res); 3797 fetchCache.put(ref, res); 3798 return res; 3799 } 3800 } 3801 3802 3803 private ElementDefinition resolveNameReference(StructureDefinitionSnapshotComponent snapshot, String contentReference) { 3804 for (ElementDefinition ed : snapshot.getElement()) 3805 if (contentReference.equals("#" + ed.getId())) 3806 return ed; 3807 return null; 3808 } 3809 3810 private StructureDefinition resolveProfile(StructureDefinition profile, String pr) { 3811 if (pr.startsWith("#")) { 3812 for (Resource r : profile.getContained()) { 3813 if (r.getId().equals(pr.substring(1)) && r instanceof StructureDefinition) 3814 return (StructureDefinition) r; 3815 } 3816 return null; 3817 } else { 3818 long t = System.nanoTime(); 3819 StructureDefinition fr = context.fetchResource(StructureDefinition.class, pr); 3820 timeTracker.sd(t); 3821 return fr; 3822 } 3823 } 3824 3825 private ElementDefinition resolveType(String type, List<TypeRefComponent> list) { 3826 for (TypeRefComponent tr : list) { 3827 String url = tr.getWorkingCode(); 3828 if (!Utilities.isAbsoluteUrl(url)) 3829 url = "http://hl7.org/fhir/StructureDefinition/" + url; 3830 long t = System.nanoTime(); 3831 StructureDefinition sd = context.fetchResource(StructureDefinition.class, url); 3832 timeTracker.sd(t); 3833 if (sd != null && (sd.getType().equals(type) || sd.getUrl().equals(type)) && sd.hasSnapshot()) 3834 return sd.getSnapshot().getElement().get(0); 3835 } 3836 return null; 3837 } 3838 3839 public void setAnyExtensionsAllowed(boolean anyExtensionsAllowed) { 3840 this.anyExtensionsAllowed = anyExtensionsAllowed; 3841 } 3842 3843 public IResourceValidator setBestPracticeWarningLevel(BestPracticeWarningLevel value) { 3844 bpWarnings = value; 3845 return this; 3846 } 3847 3848 @Override 3849 public void setCheckDisplay(CheckDisplayOption checkDisplay) { 3850 this.checkDisplay = checkDisplay; 3851 } 3852 3853 public void setSuppressLoincSnomedMessages(boolean suppressLoincSnomedMessages) { 3854 this.suppressLoincSnomedMessages = suppressLoincSnomedMessages; 3855 } 3856 3857 public IdStatus getResourceIdRule() { 3858 return resourceIdRule; 3859 } 3860 3861 public void setResourceIdRule(IdStatus resourceIdRule) { 3862 this.resourceIdRule = resourceIdRule; 3863 } 3864 3865 3866 public boolean isAllowXsiLocation() { 3867 return allowXsiLocation; 3868 } 3869 3870 public void setAllowXsiLocation(boolean allowXsiLocation) { 3871 this.allowXsiLocation = allowXsiLocation; 3872 } 3873 3874 /** 3875 * @param element - the candidate that might be in the slice 3876 * @param path - for reporting any errors. the XPath for the element 3877 * @param slicer - the definition of how slicing is determined 3878 * @param ed - the slice for which to test membership 3879 * @param errors 3880 * @param stack 3881 * @param srcProfile 3882 * @return 3883 * @throws DefinitionException 3884 * @throws DefinitionException 3885 * @throws IOException 3886 * @throws FHIRException 3887 */ 3888 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 { 3889 if (!slicer.getSlicing().hasDiscriminator()) 3890 return false; // cannot validate in this case 3891 3892 ExpressionNode n = (ExpressionNode) ed.getUserData("slice.expression.cache"); 3893 if (n == null) { 3894 long t = System.nanoTime(); 3895 // GG: this approach is flawed because it treats discriminators individually rather than collectively 3896 StringBuilder expression = new StringBuilder("true"); 3897 boolean anyFound = false; 3898 Set<String> discriminators = new HashSet<>(); 3899 for (ElementDefinitionSlicingDiscriminatorComponent s : slicer.getSlicing().getDiscriminator()) { 3900 String discriminator = s.getPath(); 3901 discriminators.add(discriminator); 3902 3903 List<ElementDefinition> criteriaElements = getCriteriaForDiscriminator(path, ed, discriminator, profile, s.getType() == DiscriminatorType.PROFILE, srcProfile); 3904 boolean found = false; 3905 for (ElementDefinition criteriaElement : criteriaElements) { 3906 found = true; 3907 if (s.getType() == DiscriminatorType.TYPE) { 3908 String type = null; 3909 if (!criteriaElement.getPath().contains("[") && discriminator.contains("[")) { 3910 discriminator = discriminator.substring(0, discriminator.indexOf('[')); 3911 String lastNode = tail(discriminator); 3912 type = tail(criteriaElement.getPath()).substring(lastNode.length()); 3913 type = type.substring(0, 1).toLowerCase() + type.substring(1); 3914 } else if (!criteriaElement.hasType() || criteriaElement.getType().size() == 1) { 3915 if (discriminator.contains("[")) 3916 discriminator = discriminator.substring(0, discriminator.indexOf('[')); 3917 if (criteriaElement.hasType()) { 3918 type = criteriaElement.getType().get(0).getWorkingCode(); 3919 } else if (!criteriaElement.getPath().contains(".")) { 3920 type = criteriaElement.getPath(); 3921 } else { 3922 throw new DefinitionException(context.formatMessage(I18nConstants.DISCRIMINATOR__IS_BASED_ON_TYPE_BUT_SLICE__IN__HAS_NO_TYPES, discriminator, ed.getId(), profile.getUrl())); 3923 } 3924 } else if (criteriaElement.getType().size() > 1) { 3925 throw new DefinitionException(context.formatMessage(I18nConstants.DISCRIMINATOR__IS_BASED_ON_TYPE_BUT_SLICE__IN__HAS_MULTIPLE_TYPES_, discriminator, ed.getId(), profile.getUrl(), criteriaElement.typeSummary())); 3926 } else 3927 throw new DefinitionException(context.formatMessage(I18nConstants.DISCRIMINATOR__IS_BASED_ON_TYPE_BUT_SLICE__IN__HAS_NO_TYPES, discriminator, ed.getId(), profile.getUrl())); 3928 if (discriminator.isEmpty()) 3929 expression.append(" and $this is " + type); 3930 else 3931 expression.append(" and " + discriminator + " is " + type); 3932 } else if (s.getType() == DiscriminatorType.PROFILE) { 3933 if (criteriaElement.getType().size() == 0) { 3934 throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_A_TYPE__IN_PROFILE_, criteriaElement.getId(), profile.getUrl())); 3935 } 3936 if (criteriaElement.getType().size() != 1) { 3937 throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_ONLY_ONE_TYPE__IN_PROFILE_, criteriaElement.getId(), profile.getUrl())); 3938 } 3939 List<CanonicalType> list = discriminator.endsWith(".resolve()") || discriminator.equals("resolve()") ? criteriaElement.getType().get(0).getTargetProfile() : criteriaElement.getType().get(0).getProfile(); 3940 if (list.size() == 0) { 3941 throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_A_TYPE_WITH_A_PROFILE__IN_PROFILE_, criteriaElement.getId(), profile.getUrl())); 3942 } else if (list.size() > 1) { 3943 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(" or "); 3944 for (CanonicalType c : list) { 3945 b.append(discriminator + ".conformsTo('" + c.getValue() + "')"); 3946 } 3947 expression.append(" and (" + b + ")"); 3948 } else { 3949 expression.append(" and " + discriminator + ".conformsTo('" + list.get(0).getValue() + "')"); 3950 } 3951 } else if (s.getType() == DiscriminatorType.EXISTS) { 3952 if (criteriaElement.hasMin() && criteriaElement.getMin() >= 1) 3953 expression.append(" and (" + discriminator + ".exists())"); 3954 else if (criteriaElement.hasMax() && criteriaElement.getMax().equals("0")) 3955 expression.append(" and (" + discriminator + ".exists().not())"); 3956 else 3957 throw new FHIRException(context.formatMessage(I18nConstants.DISCRIMINATOR__IS_BASED_ON_ELEMENT_EXISTENCE_BUT_SLICE__NEITHER_SETS_MIN1_OR_MAX0, discriminator, ed.getId())); 3958 } else if (criteriaElement.hasFixed()) { 3959 buildFixedExpression(ed, expression, discriminator, criteriaElement); 3960 } else if (criteriaElement.hasPattern()) { 3961 buildPattternExpression(ed, expression, discriminator, criteriaElement); 3962 } else if (criteriaElement.hasBinding() && criteriaElement.getBinding().hasStrength() && criteriaElement.getBinding().getStrength().equals(BindingStrength.REQUIRED) && criteriaElement.getBinding().hasValueSet()) { 3963 expression.append(" and (" + discriminator + " memberOf '" + criteriaElement.getBinding().getValueSet() + "')"); 3964 } else { 3965 found = false; 3966 } 3967 if (found) 3968 break; 3969 } 3970 if (found) 3971 anyFound = true; 3972 } 3973 if (!anyFound) { 3974 if (slicer.getSlicing().getDiscriminator().size() > 1) 3975 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)); 3976 else 3977 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)); 3978 } 3979 3980 try { 3981 n = fpe.parse(fixExpr(expression.toString(), null)); 3982 } catch (FHIRLexerException e) { 3983 if (STACK_TRACE) e.printStackTrace(); 3984 throw new FHIRException(context.formatMessage(I18nConstants.PROBLEM_PROCESSING_EXPRESSION__IN_PROFILE__PATH__, expression, profile.getUrl(), path, e.getMessage())); 3985 } 3986 timeTracker.fpe(t); 3987 ed.setUserData("slice.expression.cache", n); 3988 } 3989 3990 ValidatorHostContext shc = hostContext.forSlicing(); 3991 boolean pass = evaluateSlicingExpression(shc, element, path, profile, n); 3992 if (!pass) { 3993 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); 3994 for (String url : shc.getSliceRecords().keySet()) { 3995 slicingHint(sliceInfo, IssueType.STRUCTURE, element.line(), element.col(), path, false, isProfile(slicer), 3996 context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, stack.getLiteralPath(), url), 3997 context.formatMessage(I18nConstants.PROFILE__DOES_NOT_MATCH_FOR__BECAUSE_OF_THE_FOLLOWING_PROFILE_ISSUES__, 3998 url, 3999 stack.getLiteralPath(), errorSummaryForSlicingAsHtml(shc.getSliceRecords().get(url))), errorSummaryForSlicingAsText(shc.getSliceRecords().get(url))); 4000 } 4001 } 4002 return pass; 4003 } 4004 4005 private boolean isProfile(ElementDefinition slicer) { 4006 if (slicer == null || !slicer.hasSlicing()) { 4007 return false; 4008 } 4009 for (ElementDefinitionSlicingDiscriminatorComponent t : slicer.getSlicing().getDiscriminator()) { 4010 if (t.getType() == DiscriminatorType.PROFILE) { 4011 return true; 4012 } 4013 } 4014 return false; 4015 } 4016 4017 public boolean evaluateSlicingExpression(ValidatorHostContext hostContext, Element element, String path, StructureDefinition profile, ExpressionNode n) throws FHIRException { 4018 String msg; 4019 boolean ok; 4020 try { 4021 long t = System.nanoTime(); 4022 ok = fpe.evaluateToBoolean(hostContext.forProfile(profile), hostContext.getResource(), hostContext.getRootResource(), element, n); 4023 timeTracker.fpe(t); 4024 msg = fpe.forLog(); 4025 } catch (Exception ex) { 4026 if (STACK_TRACE) ex.printStackTrace(); 4027 throw new FHIRException(context.formatMessage(I18nConstants.PROBLEM_EVALUATING_SLICING_EXPRESSION_FOR_ELEMENT_IN_PROFILE__PATH__FHIRPATH___, profile.getUrl(), path, n, ex.getMessage())); 4028 } 4029 return ok; 4030 } 4031 4032 private void buildPattternExpression(ElementDefinition ed, StringBuilder expression, String discriminator, ElementDefinition criteriaElement) throws DefinitionException { 4033 DataType pattern = criteriaElement.getPattern(); 4034 if (pattern instanceof CodeableConcept) { 4035 CodeableConcept cc = (CodeableConcept) pattern; 4036 expression.append(" and "); 4037 buildCodeableConceptExpression(ed, expression, discriminator, cc); 4038 } else if (pattern instanceof Coding) { 4039 Coding c = (Coding) pattern; 4040 expression.append(" and "); 4041 buildCodingExpression(ed, expression, discriminator, c); 4042 } else if (pattern instanceof BooleanType || pattern instanceof IntegerType || pattern instanceof DecimalType) { 4043 expression.append(" and "); 4044 buildPrimitiveExpression(ed, expression, discriminator, pattern, false); 4045 } else if (pattern instanceof PrimitiveType) { 4046 expression.append(" and "); 4047 buildPrimitiveExpression(ed, expression, discriminator, pattern, true); 4048 } else if (pattern instanceof Identifier) { 4049 Identifier ii = (Identifier) pattern; 4050 expression.append(" and "); 4051 buildIdentifierExpression(ed, expression, discriminator, ii); 4052 } else if (pattern instanceof HumanName) { 4053 HumanName name = (HumanName) pattern; 4054 expression.append(" and "); 4055 buildHumanNameExpression(ed, expression, discriminator, name); 4056 } else if (pattern instanceof Address) { 4057 Address add = (Address) pattern; 4058 expression.append(" and "); 4059 buildAddressExpression(ed, expression, discriminator, add); 4060 } else { 4061 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_FIXED_PATTERN_TYPE_FOR_DISCRIMINATOR_FOR_SLICE__, discriminator, ed.getId(), pattern.fhirType())); 4062 } 4063 } 4064 4065 private void buildIdentifierExpression(ElementDefinition ed, StringBuilder expression, String discriminator, Identifier ii) 4066 throws DefinitionException { 4067 if (ii.hasExtension()) 4068 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId())); 4069 boolean first = true; 4070 expression.append(discriminator + ".where("); 4071 if (ii.hasSystem()) { 4072 first = false; 4073 expression.append("system = '" + ii.getSystem() + "'"); 4074 } 4075 if (ii.hasValue()) { 4076 if (first) 4077 first = false; 4078 else 4079 expression.append(" and "); 4080 expression.append("value = '" + ii.getValue() + "'"); 4081 } 4082 if (ii.hasUse()) { 4083 if (first) 4084 first = false; 4085 else 4086 expression.append(" and "); 4087 expression.append("use = '" + ii.getUse() + "'"); 4088 } 4089 if (ii.hasType()) { 4090 if (first) 4091 first = false; 4092 else 4093 expression.append(" and "); 4094 buildCodeableConceptExpression(ed, expression, TYPE, ii.getType()); 4095 } 4096 if (first) { 4097 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), ii.fhirType())); 4098 } 4099 expression.append(").exists()"); 4100 } 4101 4102 private void buildHumanNameExpression(ElementDefinition ed, StringBuilder expression, String discriminator, HumanName name) throws DefinitionException { 4103 if (name.hasExtension()) 4104 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId())); 4105 boolean first = true; 4106 expression.append(discriminator + ".where("); 4107 if (name.hasUse()) { 4108 first = false; 4109 expression.append("use = '" + name.getUse().toCode() + "'"); 4110 } 4111 if (name.hasText()) { 4112 if (first) 4113 first = false; 4114 else 4115 expression.append(" and "); 4116 expression.append("text = '" + name.getText() + "'"); 4117 } 4118 if (name.hasFamily()) { 4119 if (first) 4120 first = false; 4121 else 4122 expression.append(" and "); 4123 expression.append("family = '" + name.getFamily() + "'"); 4124 } 4125 if (name.hasGiven()) { 4126 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), name.fhirType(), "given")); 4127 } 4128 if (name.hasPrefix()) { 4129 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), name.fhirType(), "prefix")); 4130 } 4131 if (name.hasSuffix()) { 4132 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), name.fhirType(), "suffix")); 4133 } 4134 if (name.hasPeriod()) { 4135 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), name.fhirType(), "period")); 4136 } 4137 if (first) { 4138 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), name.fhirType())); 4139 } 4140 4141 expression.append(").exists()"); 4142 } 4143 4144 private void buildAddressExpression(ElementDefinition ed, StringBuilder expression, String discriminator, Address add) throws DefinitionException { 4145 if (add.hasExtension()) { 4146 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId())); 4147 } 4148 boolean first = true; 4149 expression.append(discriminator + ".where("); 4150 if (add.hasUse()) { 4151 first = false; 4152 expression.append("use = '" + add.getUse().toCode() + "'"); 4153 } 4154 if (add.hasType()) { 4155 if (first) first = false; else expression.append(" and "); 4156 expression.append("type = '" + add.getType().toCode() + "'"); 4157 } 4158 if (add.hasText()) { 4159 if (first) first = false; else expression.append(" and "); 4160 expression.append("text = '" + add.getText() + "'"); 4161 } 4162 if (add.hasCity()) { 4163 if (first) first = false; else expression.append(" and "); 4164 expression.append("city = '" + add.getCity() + "'"); 4165 } 4166 if (add.hasDistrict()) { 4167 if (first) first = false; else expression.append(" and "); 4168 expression.append("district = '" + add.getDistrict() + "'"); 4169 } 4170 if (add.hasState()) { 4171 if (first) first = false; else expression.append(" and "); 4172 expression.append("state = '" + add.getState() + "'"); 4173 } 4174 if (add.hasPostalCode()) { 4175 if (first) first = false; else expression.append(" and "); 4176 expression.append("postalCode = '" + add.getPostalCode() + "'"); 4177 } 4178 if (add.hasCountry()) { 4179 if (first) first = false; else expression.append(" and "); 4180 expression.append("country = '" + add.getCountry() + "'"); 4181 } 4182 if (add.hasLine()) { 4183 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), add.fhirType(), "line")); 4184 } 4185 if (add.hasPeriod()) { 4186 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), add.fhirType(), "period")); 4187 } 4188 if (first) { 4189 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), add.fhirType())); 4190 } 4191 expression.append(").exists()"); 4192 } 4193 4194 private void buildCodeableConceptExpression(ElementDefinition ed, StringBuilder expression, String discriminator, CodeableConcept cc) 4195 throws DefinitionException { 4196 if (cc.hasText()) 4197 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__USING_TEXT__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId())); 4198 if (!cc.hasCoding()) 4199 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__MUST_HAVE_AT_LEAST_ONE_CODING__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId())); 4200 if (cc.hasExtension()) 4201 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId())); 4202 boolean firstCoding = true; 4203 for (Coding c : cc.getCoding()) { 4204 if (c.hasExtension()) 4205 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId())); 4206 if (firstCoding) firstCoding = false; 4207 else expression.append(" and "); 4208 expression.append(discriminator + ".coding.where("); 4209 boolean first = true; 4210 if (c.hasSystem()) { 4211 first = false; 4212 expression.append("system = '" + c.getSystem() + "'"); 4213 } 4214 if (c.hasVersion()) { 4215 if (first) first = false; 4216 else expression.append(" and "); 4217 expression.append("version = '" + c.getVersion() + "'"); 4218 } 4219 if (c.hasCode()) { 4220 if (first) first = false; 4221 else expression.append(" and "); 4222 expression.append("code = '" + c.getCode() + "'"); 4223 } 4224 if (c.hasDisplay()) { 4225 if (first) first = false; 4226 else expression.append(" and "); 4227 expression.append("display = '" + c.getDisplay() + "'"); 4228 } 4229 if (first) { 4230 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), cc.fhirType())); 4231 } 4232 expression.append(").exists()"); 4233 } 4234 } 4235 4236 private void buildCodingExpression(ElementDefinition ed, StringBuilder expression, String discriminator, Coding c) 4237 throws DefinitionException { 4238 if (c.hasExtension()) 4239 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId())); 4240 expression.append(discriminator + ".where("); 4241 boolean first = true; 4242 if (c.hasSystem()) { 4243 first = false; 4244 expression.append("system = '" + c.getSystem() + "'"); 4245 } 4246 if (c.hasVersion()) { 4247 if (first) first = false; 4248 else expression.append(" and "); 4249 expression.append("version = '" + c.getVersion() + "'"); 4250 } 4251 if (c.hasCode()) { 4252 if (first) first = false; 4253 else expression.append(" and "); 4254 expression.append("code = '" + c.getCode() + "'"); 4255 } 4256 if (c.hasDisplay()) { 4257 if (first) first = false; 4258 else expression.append(" and "); 4259 expression.append("display = '" + c.getDisplay() + "'"); 4260 } 4261 if (first) { 4262 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), c.fhirType())); 4263 } 4264 expression.append(").exists()"); 4265 } 4266 4267 private void buildPrimitiveExpression(ElementDefinition ed, StringBuilder expression, String discriminator, DataType p, boolean quotes) throws DefinitionException { 4268 if (p.hasExtension()) 4269 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId())); 4270 if (quotes) { 4271 expression.append(discriminator + ".where(value = '" + p.primitiveValue() + "'"); 4272 } else { 4273 expression.append(discriminator + ".where(value = " + p.primitiveValue() + ""); 4274 } 4275 expression.append(").exists()"); 4276 } 4277 4278 private void buildFixedExpression(ElementDefinition ed, StringBuilder expression, String discriminator, ElementDefinition criteriaElement) throws DefinitionException { 4279 DataType fixed = criteriaElement.getFixed(); 4280 if (fixed instanceof CodeableConcept) { 4281 CodeableConcept cc = (CodeableConcept) fixed; 4282 expression.append(" and "); 4283 buildCodeableConceptExpression(ed, expression, discriminator, cc); 4284 } else if (fixed instanceof Identifier) { 4285 Identifier ii = (Identifier) fixed; 4286 expression.append(" and "); 4287 buildIdentifierExpression(ed, expression, discriminator, ii); 4288 } else if (fixed instanceof Coding) { 4289 Coding c = (Coding) fixed; 4290 expression.append(" and "); 4291 buildCodingExpression(ed, expression, discriminator, c); 4292 } else { 4293 expression.append(" and ("); 4294 if (fixed instanceof StringType) { 4295 Gson gson = new Gson(); 4296 String json = gson.toJson((StringType) fixed); 4297 String escapedString = json.substring(json.indexOf(":") + 2); 4298 escapedString = escapedString.substring(0, escapedString.indexOf(",\"myStringValue") - 1); 4299 expression.append("'" + escapedString + "'"); 4300 } else if (fixed instanceof UriType) { 4301 expression.append("'" + ((UriType) fixed).asStringValue() + "'"); 4302 } else if (fixed instanceof IntegerType) { 4303 expression.append(((IntegerType) fixed).asStringValue()); 4304 } else if (fixed instanceof DecimalType) { 4305 expression.append(((IntegerType) fixed).asStringValue()); 4306 } else if (fixed instanceof BooleanType) { 4307 expression.append(((BooleanType) fixed).asStringValue()); 4308 } else 4309 throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_FIXED_VALUE_TYPE_FOR_DISCRIMINATOR_FOR_SLICE__, discriminator, ed.getId(), fixed.getClass().getName())); 4310 expression.append(" in " + discriminator + ")"); 4311 } 4312 } 4313 4314 // checkSpecials = we're only going to run these tests if we are actually validating this content (as opposed to we looked it up) 4315 private void start(ValidatorHostContext hostContext, List<ValidationMessage> errors, Element resource, Element element, StructureDefinition defn, NodeStack stack) throws FHIRException { 4316 checkLang(resource, stack); 4317 if (crumbTrails) { 4318 element.addMessage(signpost(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST, defn.getUrl())); 4319 } 4320 4321 if (BUNDLE.equals(element.fhirType())) { 4322 resolveBundleReferences(element, new ArrayList<Element>()); 4323 } 4324 startInner(hostContext, errors, resource, element, defn, stack, hostContext.isCheckSpecials()); 4325 4326 Element meta = element.getNamedChild(META); 4327 if (meta != null) { 4328 List<Element> profiles = new ArrayList<Element>(); 4329 meta.getNamedChildren("profile", profiles); 4330 int i = 0; 4331 for (Element profile : profiles) { 4332 StructureDefinition sd = context.fetchResource(StructureDefinition.class, profile.primitiveValue()); 4333 if (!defn.getUrl().equals(profile.primitiveValue())) { 4334 // is this a version specific reference? 4335 VersionURLInfo vu = VersionUtilities.parseVersionUrl(profile.primitiveValue()); 4336 if (vu != null) { 4337 if (!VersionUtilities.versionsCompatible(vu.getVersion(), context.getVersion())) { 4338 hint(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_OTHER_VERSION, vu.getVersion()); 4339 } else if (vu.getUrl().equals(defn.getUrl())) { 4340 hint(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_THIS_VERSION_OK); 4341 } else { 4342 StructureDefinition sdt = context.fetchResource(StructureDefinition.class, vu.getUrl()); 4343 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()); 4344 } 4345 } else { 4346 if (sd == null) { 4347 // we'll try fetching it directly from it's source, but this is likely to fail later even if the resolution succeeds 4348 if (fetcher == null) { 4349 warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN, profile.primitiveValue()); 4350 } else if (!fetcher.fetchesCanonicalResource(this, profile.primitiveValue())) { 4351 warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN_NOT_POLICY, profile.primitiveValue()); 4352 } else { 4353 try { 4354 sd = (StructureDefinition) fetcher.fetchCanonicalResource(this, profile.primitiveValue()); 4355 } catch (Exception e) { 4356 if (STACK_TRACE) e.printStackTrace(); 4357 warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN_ERROR, profile.primitiveValue(), e.getMessage()); 4358 } 4359 if (sd != null) { 4360 context.cacheResource(sd); 4361 } 4362 } 4363 } 4364 if (sd != null) { 4365 if (crumbTrails) { 4366 element.addMessage(signpost(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST_META, sd.getUrl())); 4367 } 4368 stack.resetIds(); 4369 startInner(hostContext, errors, resource, element, sd, stack, false); 4370 } 4371 } 4372 } 4373 i++; 4374 } 4375 } 4376 String rt = element.fhirType(); 4377 for (ImplementationGuide ig : igs) { 4378 for (ImplementationGuideGlobalComponent gl : ig.getGlobal()) { 4379 if (rt.equals(gl.getType())) { 4380 StructureDefinition sd = context.fetchResource(StructureDefinition.class, gl.getProfile()); 4381 if (warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), sd != null, I18nConstants.VALIDATION_VAL_GLOBAL_PROFILE_UNKNOWN, gl.getProfile())) { 4382 if (crumbTrails) { 4383 element.addMessage(signpost(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST_GLOBAL, sd.getUrl(), ig.getUrl())); 4384 } 4385 stack.resetIds(); 4386 startInner(hostContext, errors, resource, element, sd, stack, false); 4387 } 4388 } 4389 } 4390 } 4391 } 4392 4393 private void resolveBundleReferences(Element element, List<Element> bundles) { 4394 if (!element.hasUserData("validator.bundle.resolved")) { 4395 element.setUserData("validator.bundle.resolved", true); 4396 List<Element> list = new ArrayList<Element>(); 4397 list.addAll(bundles); 4398 list.add(0, element); 4399 List<Element> entries = element.getChildrenByName(ENTRY); 4400 for (Element entry : entries) { 4401 String fu = entry.getChildValue(FULL_URL); 4402 Element r = entry.getNamedChild(RESOURCE); 4403 if (r != null) { 4404 resolveBundleReferencesInResource(list, r, fu); 4405 } 4406 } 4407 } 4408 } 4409 4410 private void resolveBundleReferencesInResource(List<Element> bundles, Element r, String fu) { 4411 r.setUserData("validator.bundle.resolution-resource", null); 4412 if (BUNDLE.equals(r.fhirType())) { 4413 resolveBundleReferences(r, bundles); 4414 } else { 4415 for (Element child : r.getChildren()) { 4416 resolveBundleReferencesForElement(bundles, r, fu, child); 4417 } 4418 } 4419 } 4420 4421 private void resolveBundleReferencesForElement(List<Element> bundles, Element resource, String fu, Element element) { 4422 if ("Reference".equals(element.fhirType())) { 4423 String ref = element.getChildValue("reference"); 4424 if (!Utilities.noString(ref)) { 4425 for (Element bundle : bundles) { 4426 List<Element> entries = bundle.getChildren(ENTRY); 4427 Element tgt = resolveInBundle(entries, ref, fu, resource.fhirType(), resource.getIdBase()); 4428 if (tgt != null) { 4429 element.setUserData("validator.bundle.resolution", tgt.getNamedChild(RESOURCE)); 4430 return; 4431 } 4432 } 4433 element.setUserData("validator.bundle.resolution-failed", ref); 4434 } 4435 } else { 4436 element.setUserData("validator.bundle.resolution-noref", null); 4437 for (Element child : element.getChildren()) { 4438 resolveBundleReferencesForElement(bundles, resource, fu, child); 4439 } 4440 } 4441 4442 } 4443 4444 public void startInner(ValidatorHostContext hostContext, List<ValidationMessage> errors, Element resource, Element element, StructureDefinition defn, NodeStack stack, boolean checkSpecials) { 4445 // the first piece of business is to see if we've validated this resource against this profile before. 4446 // if we have (*or if we still are*), then we'll just return our existing errors 4447 ResourceValidationTracker resTracker = getResourceTracker(element); 4448 List<ValidationMessage> cachedErrors = resTracker.getOutcomes(defn); 4449 if (cachedErrors != null) { 4450 for (ValidationMessage vm : cachedErrors) { 4451 if (!errors.contains(vm)) { 4452 errors.add(vm); 4453 } 4454 } 4455 return; 4456 } 4457 if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), defn.hasSnapshot(), I18nConstants.VALIDATION_VAL_PROFILE_NOSNAPSHOT, defn.getUrl())) { 4458 List<ValidationMessage> localErrors = new ArrayList<ValidationMessage>(); 4459 resTracker.startValidating(defn); 4460 trackUsage(defn, hostContext, element); 4461 validateElement(hostContext, localErrors, defn, defn.getSnapshot().getElement().get(0), null, null, resource, element, element.getName(), stack, false, true, null); 4462 resTracker.storeOutcomes(defn, localErrors); 4463 for (ValidationMessage vm : localErrors) { 4464 if (!errors.contains(vm)) { 4465 errors.add(vm); 4466 } 4467 } 4468 } 4469 if (checkSpecials) { 4470 checkSpecials(hostContext, errors, element, stack, checkSpecials); 4471 validateResourceRules(errors, element, stack); 4472 } 4473 } 4474 4475 public void checkSpecials(ValidatorHostContext hostContext, List<ValidationMessage> errors, Element element, NodeStack stack, boolean checkSpecials) { 4476 // specific known special validations 4477 if (element.getType().equals(BUNDLE)) { 4478 new BundleValidator(context, serverBase, this, xverManager).validateBundle(errors, element, stack, checkSpecials, hostContext); 4479 } else if (element.getType().equals("Observation")) { 4480 validateObservation(errors, element, stack); 4481 } else if (element.getType().equals("Questionnaire")) { 4482 new QuestionnaireValidator(context, myEnableWhenEvaluator, fpe, timeTracker, questionnaireMode, xverManager).validateQuestionannaire(errors, element, element, stack); 4483 } else if (element.getType().equals("QuestionnaireResponse")) { 4484 new QuestionnaireValidator(context, myEnableWhenEvaluator, fpe, timeTracker, questionnaireMode, xverManager).validateQuestionannaireResponse(hostContext, errors, element, stack); 4485 } else if (element.getType().equals("Measure")) { 4486 new MeasureValidator(context, timeTracker, xverManager).validateMeasure(hostContext, errors, element, stack); 4487 } else if (element.getType().equals("MeasureReport")) { 4488 new MeasureValidator(context, timeTracker, xverManager).validateMeasureReport(hostContext, errors, element, stack); 4489 } else if (element.getType().equals("CapabilityStatement")) { 4490 validateCapabilityStatement(errors, element, stack); 4491 } else if (element.getType().equals("CodeSystem")) { 4492 new CodeSystemValidator(context, timeTracker, xverManager).validateCodeSystem(errors, element, stack, baseOptions.setLanguage(stack.getWorkingLang())); 4493 } else if (element.getType().equals("SearchParameter")) { 4494 new SearchParameterValidator(context, timeTracker, fpe, xverManager).validateSearchParameter(errors, element, stack); 4495 } else if (element.getType().equals("StructureDefinition")) { 4496 new StructureDefinitionValidator(context, timeTracker, fpe, wantCheckSnapshotUnchanged, xverManager).validateStructureDefinition(errors, element, stack); 4497 } else if (element.getType().equals("ValueSet")) { 4498 new ValueSetValidator(context, timeTracker, this, xverManager).validateValueSet(errors, element, stack); 4499 } 4500 } 4501 4502 private ResourceValidationTracker getResourceTracker(Element element) { 4503 ResourceValidationTracker res = resourceTracker.get(element); 4504 if (res == null) { 4505 res = new ResourceValidationTracker(); 4506 resourceTracker.put(element, res); 4507 } 4508 return res; 4509 } 4510 4511 private void checkLang(Element resource, NodeStack stack) { 4512 String lang = resource.getNamedChildValue("language"); 4513 if (!Utilities.noString(lang)) 4514 stack.setWorkingLang(lang); 4515 } 4516 4517 private void validateResourceRules(List<ValidationMessage> errors, Element element, NodeStack stack) { 4518 String lang = element.getNamedChildValue("language"); 4519 Element text = element.getNamedChild("text"); 4520 if (text != null) { 4521 Element div = text.getNamedChild("div"); 4522 if (lang != null && div != null) { 4523 XhtmlNode xhtml = div.getXhtml(); 4524 String l = xhtml.getAttribute("lang"); 4525 String xl = xhtml.getAttribute("xml:lang"); 4526 if (l == null && xl == null) { 4527 warning(errors, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, I18nConstants.LANGUAGE_XHTML_LANG_MISSING1); 4528 } else { 4529 if (l == null) { 4530 warning(errors, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, I18nConstants.LANGUAGE_XHTML_LANG_MISSING2); 4531 } else if (!l.equals(lang)) { 4532 warning(errors, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, I18nConstants.LANGUAGE_XHTML_LANG_DIFFERENT1, lang, l); 4533 } 4534 if (xl == null) { 4535 warning(errors, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, I18nConstants.LANGUAGE_XHTML_LANG_MISSING3); 4536 } else if (!xl.equals(lang)) { 4537 warning(errors, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, I18nConstants.LANGUAGE_XHTML_LANG_DIFFERENT2, lang, xl); 4538 } 4539 } 4540 } 4541 } 4542 // security tags are a set (system|code) 4543 Element meta = element.getNamedChild(META); 4544 if (meta != null) { 4545 Set<String> tags = new HashSet<>(); 4546 List<Element> list = new ArrayList<>(); 4547 meta.getNamedChildren("security", list); 4548 int i = 0; 4549 for (Element e : list) { 4550 String s = e.getNamedChildValue("system") + "#" + e.getNamedChildValue("code"); 4551 rule(errors, IssueType.BUSINESSRULE, e.line(), e.col(), stack.getLiteralPath() + ".meta.profile[" + Integer.toString(i) + "]", !tags.contains(s), I18nConstants.META_RES_SECURITY_DUPLICATE, s); 4552 tags.add(s); 4553 i++; 4554 } 4555 } 4556 } 4557 4558 private void validateCapabilityStatement(List<ValidationMessage> errors, Element cs, NodeStack stack) { 4559 int iRest = 0; 4560 for (Element rest : cs.getChildrenByName("rest")) { 4561 int iResource = 0; 4562 for (Element resource : rest.getChildrenByName(RESOURCE)) { 4563 int iSP = 0; 4564 for (Element searchParam : resource.getChildrenByName("searchParam")) { 4565 String ref = searchParam.getChildValue("definition"); 4566 String type = searchParam.getChildValue(TYPE); 4567 if (!Utilities.noString(ref)) { 4568 SearchParameter sp = context.fetchResource(SearchParameter.class, ref); 4569 if (sp != null) { 4570 rule(errors, IssueType.INVALID, searchParam.line(), searchParam.col(), stack.getLiteralPath() + ".rest[" + iRest + "].resource[" + iResource + "].searchParam[" + iSP + "]", 4571 sp.getType().toCode().equals(type), I18nConstants.CAPABALITYSTATEMENT_CS_SP_WRONGTYPE, sp.getUrl(), sp.getType().toCode(), type); 4572 } 4573 } 4574 iSP++; 4575 } 4576 iResource++; 4577 } 4578 iRest++; 4579 } 4580 } 4581 4582 private void validateContains(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, 4583 ElementDefinition child, ElementDefinition context, Element resource, 4584 Element element, NodeStack stack, IdStatus idstatus, StructureDefinition parentProfile) throws FHIRException { 4585 4586 SpecialElement special = element.getSpecial(); 4587 4588 ContainedReferenceValidationPolicy containedValidationPolicy = getPolicyAdvisor() == null ? 4589 ContainedReferenceValidationPolicy.CHECK_VALID : getPolicyAdvisor().policyForContained(this, 4590 hostContext, context.fhirType(), context.getId(), special, path, parentProfile.getUrl()); 4591 4592 if (containedValidationPolicy.ignore()) { 4593 return; 4594 } 4595 4596 String resourceName = element.getType(); 4597 TypeRefComponent typeForResource = null; 4598 CommaSeparatedStringBuilder bt = new CommaSeparatedStringBuilder(); 4599 4600 // Iterate through all possible types 4601 for (TypeRefComponent type : child.getType()) { 4602 bt.append(type.getCode()); 4603 if (type.getCode().equals("Resource") || type.getCode().equals(resourceName) ) { 4604 typeForResource = type; 4605 break; 4606 } 4607 } 4608 4609 stack.qualifyPath(".ofType("+resourceName+")"); 4610 4611 if (typeForResource == null) { 4612 rule(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), 4613 false, I18nConstants.BUNDLE_BUNDLE_ENTRY_TYPE, resourceName, bt.toString()); 4614 } else if (isValidResourceType(resourceName, typeForResource)) { 4615 if (containedValidationPolicy.checkValid()) { 4616 // special case: resource wrapper is reset if we're crossing a bundle boundary, but not otherwise 4617 ValidatorHostContext hc = null; 4618 if (special == SpecialElement.BUNDLE_ENTRY || special == SpecialElement.BUNDLE_OUTCOME || special == SpecialElement.PARAMETER) { 4619 resource = element; 4620 assert Utilities.existsInList(hostContext.getRootResource().fhirType(), "Bundle", "Parameters") : "Resource is "+hostContext.getRootResource().fhirType()+", expected Bundle or Parameters"; 4621 hc = hostContext.forEntry(element, hostContext.getRootResource()); // root becomes the grouping resource (should be either bundle or parameters) 4622 } else { 4623 hc = hostContext.forContained(element); 4624 } 4625 4626 stack.resetIds(); 4627 if (special != null) { 4628 switch (special) { 4629 case BUNDLE_ENTRY: 4630 case BUNDLE_OUTCOME: 4631 case PARAMETER: 4632 idstatus = IdStatus.OPTIONAL; 4633 break; 4634 case CONTAINED: 4635 stack.setContained(true); 4636 idstatus = IdStatus.REQUIRED; 4637 break; 4638 default: 4639 break; 4640 } 4641 } 4642 4643 if (typeForResource.getProfile().size() == 1) { 4644 long t = System.nanoTime(); 4645 StructureDefinition profile = this.context.fetchResource(StructureDefinition.class, typeForResource.getProfile().get(0).asStringValue()); 4646 timeTracker.sd(t); 4647 trackUsage(profile, hostContext, element); 4648 if (rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), 4649 profile != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOPROFILE_EXPL, special.toHuman(), resourceName, typeForResource.getProfile().get(0).asStringValue())) { 4650 validateResource(hc, errors, resource, element, profile, idstatus, stack); 4651 } 4652 } else if (typeForResource.getProfile().isEmpty()) { 4653 long t = System.nanoTime(); 4654 StructureDefinition profile = this.context.fetchResource(StructureDefinition.class, 4655 "http://hl7.org/fhir/StructureDefinition/" + resourceName); 4656 timeTracker.sd(t); 4657 trackUsage(profile, hostContext, element); 4658 if (rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), 4659 profile != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOPROFILE_TYPE, special == null ? "??" : special.toHuman(), resourceName)) { 4660 validateResource(hc, errors, resource, element, profile, idstatus, stack); 4661 } 4662 } else { 4663 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 4664 for (CanonicalType u : typeForResource.getProfile()) { 4665 b.append(u.asStringValue()); 4666 } 4667 rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), 4668 false, I18nConstants.BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES, special.toHuman(), typeForResource.getCode(), b.toString()); 4669 } 4670 } 4671 } else { 4672 List<String> types = new ArrayList<>(); 4673 for (UriType u : typeForResource.getProfile()) { 4674 StructureDefinition sd = this.context.fetchResource(StructureDefinition.class, u.getValue()); 4675 if (sd != null && !types.contains(sd.getType())) { 4676 types.add(sd.getType()); 4677 } 4678 } 4679 if (types.size() == 1) { 4680 rule(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), 4681 false, I18nConstants.BUNDLE_BUNDLE_ENTRY_TYPE2, resourceName, types.get(0)); 4682 } else { 4683 rule(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), 4684 false, I18nConstants.BUNDLE_BUNDLE_ENTRY_TYPE3, resourceName, types); 4685 } 4686 } 4687 } 4688 4689 private boolean isValidResourceType(String type, TypeRefComponent def) { 4690 if (!def.hasProfile() && def.getCode().equals("Resource")) { 4691 return true; 4692 } 4693 if (def.getCode().equals(type)) { 4694 return true; 4695 } 4696 List<StructureDefinition> list = new ArrayList<>(); 4697 for (UriType u : def.getProfile()) { 4698 StructureDefinition sdt = context.fetchResource(StructureDefinition.class, u.getValue()); 4699 if (sdt != null) { 4700 list.add(sdt); 4701 } 4702 } 4703 4704 StructureDefinition sdt = context.fetchTypeDefinition(type); 4705 while (sdt != null) { 4706 if (def.getWorkingCode().equals("Resource")) { 4707 for (StructureDefinition sd : list) { 4708 if (sd.getUrl().equals(sdt.getUrl())) { 4709 return true; 4710 } 4711 if (sd.getType().equals(sdt.getType())) { 4712 return true; 4713 } 4714 } 4715 } 4716 sdt = context.fetchResource(StructureDefinition.class, sdt.getBaseDefinition()); 4717 } 4718 return false; 4719 } 4720 4721 4722 private void validateElement(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition, StructureDefinition cprofile, ElementDefinition context, 4723 Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept, boolean checkDisplayInContext, String extensionUrl) throws FHIRException { 4724 4725 String id = element.getChildValue("id"); 4726 if (!Utilities.noString(id)) { 4727 if (stack.getIds().containsKey(id) && stack.getIds().get(id) != element) { 4728 rule(errors, IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.DUPLICATE_ID, id); 4729 } 4730 if (!stack.isResetPoint()) { 4731 stack.getIds().put(id, element); 4732 } 4733 } 4734 if (definition.getPath().equals("StructureDefinition.snapshot")) { 4735 // work around a known issue in the spec, that ids are duplicated in snapshot and differential 4736 stack.resetIds(); 4737 } 4738 4739 // check type invariants 4740 checkInvariants(hostContext, errors, profile, definition, resource, element, stack, false); 4741 if (definition.getFixed() != null) { 4742 checkFixedValue(errors, stack.getLiteralPath(), element, definition.getFixed(), profile.getUrl(), definition.getSliceName(), null, false); 4743 } 4744 if (definition.getPattern() != null) { 4745 checkFixedValue(errors, stack.getLiteralPath(), element, definition.getPattern(), profile.getUrl(), definition.getSliceName(), null, true); 4746 } 4747 4748 // get the list of direct defined children, including slices 4749 List<ElementDefinition> childDefinitions = profileUtilities.getChildMap(profile, definition); 4750 if (childDefinitions.isEmpty()) { 4751 if (actualType == null) 4752 return; // there'll be an error elsewhere in this case, and we're going to stop. 4753 childDefinitions = getActualTypeChildren(hostContext, element, actualType); 4754 } else if (definition.getType().size() > 1) { 4755 // this only happens when the profile constrains the abstract children but leaves th choice open. 4756 if (actualType == null) 4757 return; // there'll be an error elsewhere in this case, and we're going to stop. 4758 List<ElementDefinition> typeChildDefinitions = getActualTypeChildren(hostContext, element, actualType); 4759 // 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) 4760 mergeChildLists(childDefinitions, typeChildDefinitions, definition.getPath(), actualType); 4761 } 4762 4763 List<ElementInfo> children = listChildren(element, stack); 4764 List<String> problematicPaths = assignChildren(hostContext, errors, profile, resource, stack, childDefinitions, children); 4765 4766 checkCardinalities(errors, profile, element, stack, childDefinitions, children, problematicPaths); 4767 // 4. check order if any slices are ordered. (todo) 4768 4769 // 5. inspect each child for validity 4770 for (ElementInfo ei : children) { 4771 checkChild(hostContext, errors, profile, definition, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, ei, extensionUrl); 4772 } 4773 } 4774 4775 private void mergeChildLists(List<ElementDefinition> master, List<ElementDefinition> additional, String masterPath, String typePath) { 4776 for (ElementDefinition ed : additional) { 4777 boolean inMaster = false; 4778 for (ElementDefinition t : master) { 4779 String tp = masterPath + ed.getPath().substring(typePath.length()); 4780 if (t.getPath().equals(tp)) { 4781 inMaster = true; 4782 } 4783 } 4784 if (!inMaster) { 4785 master.add(ed); 4786 } 4787 } 4788 4789 4790 } 4791 4792 // todo: the element definition in context might assign a constrained profile for the type? 4793 public List<ElementDefinition> getActualTypeChildren(ValidatorHostContext hostContext, Element element, String actualType) { 4794 List<ElementDefinition> childDefinitions; 4795 StructureDefinition dt = null; 4796 if (isAbsolute(actualType)) 4797 dt = this.context.fetchResource(StructureDefinition.class, actualType); 4798 else 4799 dt = this.context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + actualType); 4800 if (dt == null) 4801 throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_ACTUAL_TYPE_, actualType)); 4802 trackUsage(dt, hostContext, element); 4803 4804 childDefinitions = profileUtilities.getChildMap(dt, dt.getSnapshot().getElement().get(0)); 4805 return childDefinitions; 4806 } 4807 4808 public void checkChild(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition, 4809 Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept, boolean checkDisplayInContext, ElementInfo ei, String extensionUrl) 4810 throws FHIRException, DefinitionException { 4811 4812 if (debug && ei.definition != null && ei.slice != null) { 4813 System.out.println(Utilities.padLeft("", ' ', stack.depth())+ "Check "+ei.getPath()+" against both "+ei.definition.getId()+" and "+ei.slice.getId()); 4814 } 4815 if (ei.definition != null) { 4816 if (debug) { 4817 System.out.println(Utilities.padLeft("", ' ', stack.depth())+ "Check "+ei.getPath()+" against defn "+ei.definition.getId()+" from "+profile.getUrl()); 4818 } 4819 checkChildByDefinition(hostContext, errors, profile, definition, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, ei, extensionUrl, ei.definition, false); 4820 } 4821 if (ei.slice != null) { 4822 if (debug) { 4823 System.out.println(Utilities.padLeft("", ' ', stack.depth())+ "Check "+ei.getPath()+" against slice "+ei.slice.getId()); 4824 } 4825 checkChildByDefinition(hostContext, errors, profile, definition, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, ei, extensionUrl, ei.slice, true); 4826 } 4827 } 4828 4829 public void checkChildByDefinition(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, 4830 ElementDefinition definition, Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept, 4831 boolean checkDisplayInContext, ElementInfo ei, String extensionUrl, ElementDefinition checkDefn, boolean isSlice) { 4832 List<String> profiles = new ArrayList<String>(); 4833 String type = null; 4834 ElementDefinition typeDefn = null; 4835 checkMustSupport(profile, ei); 4836 4837 if (checkDefn.getType().size() == 1 && !"*".equals(checkDefn.getType().get(0).getWorkingCode()) && !"Element".equals(checkDefn.getType().get(0).getWorkingCode()) 4838 && !"BackboneElement".equals(checkDefn.getType().get(0).getWorkingCode())) { 4839 type = checkDefn.getType().get(0).getWorkingCode(); 4840 String stype = ei.getElement().fhirType(); 4841 if (checkDefn.isChoice() && !stype.equals(type)) { 4842 if ("Extension".equals(profile.getType())) { 4843 // error will be raised elsewhere 4844 } else { 4845 rule(errors, IssueType.STRUCTURE, element.line(), element.col(), ei.getPath(), false, I18nConstants.EXTENSION_PROF_TYPE, profile.getUrl(), type, stype); 4846 } 4847 } 4848 4849 // Excluding reference is a kludge to get around versioning issues 4850 if (checkDefn.getType().get(0).hasProfile()) { 4851 for (CanonicalType p : checkDefn.getType().get(0).getProfile()) { 4852 profiles.add(p.getValue()); 4853 } 4854 } 4855 } else if (checkDefn.getType().size() == 1 && "*".equals(checkDefn.getType().get(0).getWorkingCode())) { 4856 String prefix = tail(checkDefn.getPath()); 4857 assert prefix.endsWith("[x]"); 4858 type = ei.getName().substring(prefix.length() - 3); 4859 if (isPrimitiveType(type)) 4860 type = Utilities.uncapitalize(type); 4861 if (checkDefn.getType().get(0).hasProfile()) { 4862 for (CanonicalType p : checkDefn.getType().get(0).getProfile()) { 4863 profiles.add(p.getValue()); 4864 } 4865 } 4866 } else if (checkDefn.getType().size() > 1) { 4867 4868 String prefix = tail(checkDefn.getPath()); 4869 assert typesAreAllReference(checkDefn.getType()) || checkDefn.hasRepresentation(PropertyRepresentation.TYPEATTR) || prefix.endsWith("[x]") || isResourceAndTypes(checkDefn) : "Multiple Types allowed, but name is wrong @ "+checkDefn.getPath()+": "+checkDefn.typeSummaryVB(); 4870 4871 if (checkDefn.hasRepresentation(PropertyRepresentation.TYPEATTR)) { 4872 type = ei.getElement().getType(); 4873 } else if (ei.getElement().isResource()) { 4874 type = ei.getElement().fhirType(); 4875 } else { 4876 prefix = prefix.substring(0, prefix.length() - 3); 4877 for (TypeRefComponent t : checkDefn.getType()) 4878 if ((prefix + Utilities.capitalize(t.getWorkingCode())).equals(ei.getName())) { 4879 type = t.getWorkingCode(); 4880 // Excluding reference is a kludge to get around versioning issues 4881 if (t.hasProfile() && !type.equals("Reference")) 4882 profiles.add(t.getProfile().get(0).getValue()); 4883 } 4884 } 4885 if (type == null) { 4886 TypeRefComponent trc = checkDefn.getType().get(0); 4887 if (trc.getWorkingCode().equals("Reference")) 4888 type = "Reference"; 4889 else 4890 rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), stack.getLiteralPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_NOTYPE, ei.getName(), describeTypes(checkDefn.getType())); 4891 } 4892 } else if (checkDefn.getContentReference() != null) { 4893 typeDefn = resolveNameReference(profile.getSnapshot(), checkDefn.getContentReference()); 4894 4895 } else if (checkDefn.getType().size() == 1 && ("Element".equals(checkDefn.getType().get(0).getWorkingCode()) || "BackboneElement".equals(checkDefn.getType().get(0).getWorkingCode()))) { 4896 if (checkDefn.getType().get(0).hasProfile()) { 4897 CanonicalType pu = checkDefn.getType().get(0).getProfile().get(0); 4898 if (pu.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT)) 4899 profiles.add(pu.getValue() + "#" + pu.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT)); 4900 else 4901 profiles.add(pu.getValue()); 4902 } 4903 } 4904 4905 if (type != null) { 4906 if (type.startsWith("@")) { 4907 checkDefn = findElement(profile, type.substring(1)); 4908 if (isSlice) { 4909 ei.slice = ei.definition; 4910 } else { 4911 ei.definition = ei.definition; 4912 } 4913 type = null; 4914 } 4915 } 4916 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())); 4917// if (debug) { 4918// System.out.println(" check " + localStack.getLiteralPath()+" against "+ei.getDefinition().getId()+" in profile "+profile.getUrl()); 4919// } 4920 String localStackLiteralPath = localStack.getLiteralPath(); 4921 String eiPath = ei.getPath(); 4922 if (!eiPath.equals(localStackLiteralPath)) { 4923 assert (eiPath.equals(localStackLiteralPath)) : "ei.path: " + ei.getPath() + " - localStack.getLiteralPath: " + localStackLiteralPath; 4924 } 4925 boolean thisIsCodeableConcept = false; 4926 String thisExtension = null; 4927 boolean checkDisplay = true; 4928 4929 SpecialElement special = ei.getElement().getSpecial(); 4930 if (special == SpecialElement.BUNDLE_ENTRY || special == SpecialElement.BUNDLE_OUTCOME || special == SpecialElement.PARAMETER) { 4931 checkInvariants(hostContext, errors, profile, typeDefn != null ? typeDefn : checkDefn, ei.getElement(), ei.getElement(), localStack, false); 4932 } else { 4933 checkInvariants(hostContext, errors, profile, typeDefn != null ? typeDefn : checkDefn, resource, ei.getElement(), localStack, false); 4934 } 4935 4936 ei.getElement().markValidation(profile, checkDefn); 4937 boolean elementValidated = false; 4938 if (type != null) { 4939 if (isPrimitiveType(type)) { 4940 checkPrimitive(hostContext, errors, ei.getPath(), type, checkDefn, ei.getElement(), profile, stack); 4941 } else { 4942 if (checkDefn.hasFixed()) { 4943 checkFixedValue(errors, ei.getPath(), ei.getElement(), checkDefn.getFixed(), profile.getUrl(), checkDefn.getSliceName(), null, false); 4944 } 4945 if (checkDefn.hasPattern()) { 4946 checkFixedValue(errors, ei.getPath(), ei.getElement(), checkDefn.getPattern(), profile.getUrl(), checkDefn.getSliceName(), null, true); 4947 } 4948 } 4949 if (type.equals("Identifier")) { 4950 checkIdentifier(errors, ei.getPath(), ei.getElement(), checkDefn); 4951 } else if (type.equals("Coding")) { 4952 checkCoding(errors, ei.getPath(), ei.getElement(), profile, checkDefn, inCodeableConcept, checkDisplayInContext, stack); 4953 } else if (type.equals("Quantity")) { 4954 checkQuantity(errors, ei.getPath(), ei.getElement(), profile, checkDefn, stack); 4955 } else if (type.equals("Attachment")) { 4956 checkAttachment(errors, ei.getPath(), ei.getElement(), profile, checkDefn, inCodeableConcept, checkDisplayInContext, stack); 4957 } else if (type.equals("CodeableConcept")) { 4958 checkDisplay = checkCodeableConcept(errors, ei.getPath(), ei.getElement(), profile, checkDefn, stack); 4959 thisIsCodeableConcept = true; 4960 } else if (type.equals("Reference")) { 4961 checkReference(hostContext, errors, ei.getPath(), ei.getElement(), profile, checkDefn, actualType, localStack); 4962 // 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 4963 } else if (type.equals("Extension")) { 4964 Element eurl = ei.getElement().getNamedChild("url"); 4965 if (rule(errors, IssueType.INVALID, ei.getPath(), eurl != null, I18nConstants.EXTENSION_EXT_URL_NOTFOUND)) { 4966 String url = eurl.primitiveValue(); 4967 thisExtension = url; 4968 if (rule(errors, IssueType.INVALID, ei.getPath(), !Utilities.noString(url), I18nConstants.EXTENSION_EXT_URL_NOTFOUND)) { 4969 if (rule(errors, IssueType.INVALID, ei.getPath(), (extensionUrl != null) || Utilities.isAbsoluteUrl(url), I18nConstants.EXTENSION_EXT_URL_ABSOLUTE)) { 4970 checkExtension(hostContext, errors, ei.getPath(), resource, element, ei.getElement(), checkDefn, profile, localStack, stack, extensionUrl); 4971 } 4972 } 4973 } 4974 } else if (type.equals("Resource") || isResource(type)) { 4975 validateContains(hostContext, errors, ei.getPath(), checkDefn, definition, resource, ei.getElement(), 4976 localStack, idStatusForEntry(element, ei), profile); // if 4977 elementValidated = true; 4978 // (str.matches(".*([.,/])work\\1$")) 4979 } else if (Utilities.isAbsoluteUrl(type)) { 4980 StructureDefinition defn = context.fetchTypeDefinition(type); 4981 if (defn != null && hasMapping("http://hl7.org/fhir/terminology-pattern", defn, defn.getSnapshot().getElementFirstRep())) { 4982 List<String> txtype = getMapping("http://hl7.org/fhir/terminology-pattern", defn, defn.getSnapshot().getElementFirstRep()); 4983 if (txtype.contains("CodeableConcept")) { 4984 checkTerminologyCodeableConcept(errors, ei.getPath(), ei.getElement(), profile, checkDefn, stack, defn); 4985 thisIsCodeableConcept = true; 4986 } else if (txtype.contains("Coding")) { 4987 checkTerminologyCoding(errors, ei.getPath(), ei.getElement(), profile, checkDefn, inCodeableConcept, checkDisplayInContext, stack, defn); 4988 } 4989 } 4990 } 4991 } else { 4992 if (rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), stack.getLiteralPath(), checkDefn != null, I18nConstants.VALIDATION_VAL_CONTENT_UNKNOWN, ei.getName())) 4993 validateElement(hostContext, errors, profile, checkDefn, null, null, resource, ei.getElement(), type, localStack, false, true, null); 4994 } 4995 StructureDefinition p = null; 4996 String tail = null; 4997 if (profiles.isEmpty()) { 4998 if (type != null) { 4999 p = getProfileForType(type, checkDefn.getType()); 5000 5001 // If dealing with a primitive type, then we need to check the current child against 5002 // the invariants (constraints) on the current element, because otherwise it only gets 5003 // checked against the primary type's invariants: LLoyd 5004 //if (p.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { 5005 // checkInvariants(hostContext, errors, ei.path, profile, ei.definition, null, null, resource, ei.element); 5006 //} 5007 5008 rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.getPath(), p != null, I18nConstants.VALIDATION_VAL_NOTYPE, type); 5009 } 5010 } else if (profiles.size() == 1) { 5011 String url = profiles.get(0); 5012 if (url.contains("#")) { 5013 tail = url.substring(url.indexOf("#") + 1); 5014 url = url.substring(0, url.indexOf("#")); 5015 } 5016 p = this.context.fetchResource(StructureDefinition.class, url); 5017 rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.getPath(), p != null, I18nConstants.VALIDATION_VAL_UNKNOWN_PROFILE, profiles.get(0)); 5018 } else { 5019 elementValidated = true; 5020 HashMap<String, List<ValidationMessage>> goodProfiles = new HashMap<String, List<ValidationMessage>>(); 5021 HashMap<String, List<ValidationMessage>> badProfiles = new HashMap<String, List<ValidationMessage>>(); 5022 for (String typeProfile : profiles) { 5023 String url = typeProfile; 5024 tail = null; 5025 if (url.contains("#")) { 5026 tail = url.substring(url.indexOf("#") + 1); 5027 url = url.substring(0, url.indexOf("#")); 5028 } 5029 p = this.context.fetchResource(StructureDefinition.class, typeProfile); 5030 if (rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.getPath(), p != null, I18nConstants.VALIDATION_VAL_UNKNOWN_PROFILE, typeProfile)) { 5031 List<ValidationMessage> profileErrors = new ArrayList<ValidationMessage>(); 5032 validateElement(hostContext, profileErrors, p, getElementByTail(p, tail), profile, checkDefn, resource, ei.getElement(), type, localStack, thisIsCodeableConcept, checkDisplay, thisExtension); 5033 if (hasErrors(profileErrors)) 5034 badProfiles.put(typeProfile, profileErrors); 5035 else 5036 goodProfiles.put(typeProfile, profileErrors); 5037 } 5038 } 5039 if (goodProfiles.size() == 1) { 5040 errors.addAll(goodProfiles.values().iterator().next()); 5041 } else if (goodProfiles.size() == 0) { 5042 rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.getPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_NOMATCH, StringUtils.join("; ", profiles)); 5043 for (String m : badProfiles.keySet()) { 5044 p = this.context.fetchResource(StructureDefinition.class, m); 5045 for (ValidationMessage message : badProfiles.get(m)) { 5046 message.setMessage(message.getMessage() + " (validating against " + p.getUrl() + (p.hasVersion() ? "|" + p.getVersion() : "") + " [" + p.getName() + "])"); 5047 errors.add(message); 5048 } 5049 } 5050 } else { 5051 warning(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.getPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_MULTIPLEMATCHES, StringUtils.join("; ", goodProfiles.keySet())); 5052 for (String m : goodProfiles.keySet()) { 5053 p = this.context.fetchResource(StructureDefinition.class, m); 5054 for (ValidationMessage message : goodProfiles.get(m)) { 5055 message.setMessage(message.getMessage() + " (validating against " + p.getUrl() + (p.hasVersion() ? "|" + p.getVersion() : "") + " [" + p.getName() + "])"); 5056 errors.add(message); 5057 } 5058 } 5059 } 5060 } 5061 if (p != null) { 5062 trackUsage(p, hostContext, element); 5063 5064 if (!elementValidated) { 5065 if (ei.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY || ei.getElement().getSpecial() == SpecialElement.BUNDLE_OUTCOME || ei.getElement().getSpecial() == SpecialElement.PARAMETER) 5066 validateElement(hostContext, errors, p, getElementByTail(p, tail), profile, checkDefn, ei.getElement(), ei.getElement(), type, localStack.resetIds(), thisIsCodeableConcept, checkDisplay, thisExtension); 5067 else 5068 validateElement(hostContext, errors, p, getElementByTail(p, tail), profile, checkDefn, resource, ei.getElement(), type, localStack, thisIsCodeableConcept, checkDisplay, thisExtension); 5069 } 5070 int index = profile.getSnapshot().getElement().indexOf(checkDefn); 5071 if (index < profile.getSnapshot().getElement().size() - 1) { 5072 String nextPath = profile.getSnapshot().getElement().get(index + 1).getPath(); 5073 if (!nextPath.equals(checkDefn.getPath()) && nextPath.startsWith(checkDefn.getPath())) 5074 validateElement(hostContext, errors, profile, checkDefn, null, null, resource, ei.getElement(), type, localStack, thisIsCodeableConcept, checkDisplay, thisExtension); 5075 } 5076 } 5077 } 5078 5079 private boolean isResourceAndTypes(ElementDefinition ed) { 5080 if (!Utilities.existsInList(ed.getBase().getPath(), "Bundle.entry.resource", "Bundle.entry.response.outcome", "DomainResource.contained", "Parameters.parameter.resource", "Parameters.parameter.part.resource")) { 5081 return false; 5082 } 5083 for (TypeRefComponent tr : ed.getType()) { 5084 if (!isResource(tr.getCode())) { 5085 return false; 5086 } 5087 } 5088 return true; 5089 } 5090 5091 private boolean isResource(String type) { 5092 StructureDefinition sd = context.fetchTypeDefinition(type); 5093 return sd != null && sd.getKind().equals(StructureDefinitionKind.RESOURCE); 5094 } 5095 5096 private void trackUsage(StructureDefinition profile, ValidatorHostContext hostContext, Element element) { 5097 if (tracker != null) { 5098 tracker.recordProfileUsage(profile, hostContext.getAppContext(), element); 5099 } 5100 } 5101 5102 private boolean hasMapping(String url, StructureDefinition defn, ElementDefinition elem) { 5103 String id = null; 5104 for (StructureDefinitionMappingComponent m : defn.getMapping()) { 5105 if (url.equals(m.getUri())) { 5106 id = m.getIdentity(); 5107 break; 5108 } 5109 } 5110 if (id != null) { 5111 for (ElementDefinitionMappingComponent m : elem.getMapping()) { 5112 if (id.equals(m.getIdentity())) { 5113 return true; 5114 } 5115 } 5116 5117 } 5118 return false; 5119 } 5120 5121 private List<String> getMapping(String url, StructureDefinition defn, ElementDefinition elem) { 5122 List<String> res = new ArrayList<>(); 5123 String id = null; 5124 for (StructureDefinitionMappingComponent m : defn.getMapping()) { 5125 if (url.equals(m.getUri())) { 5126 id = m.getIdentity(); 5127 break; 5128 } 5129 } 5130 if (id != null) { 5131 for (ElementDefinitionMappingComponent m : elem.getMapping()) { 5132 if (id.equals(m.getIdentity())) { 5133 res.add(m.getMap()); 5134 } 5135 } 5136 } 5137 return res; 5138 } 5139 5140 public void checkMustSupport(StructureDefinition profile, ElementInfo ei) { 5141 String usesMustSupport = profile.getUserString("usesMustSupport"); 5142 if (usesMustSupport == null) { 5143 usesMustSupport = "N"; 5144 for (ElementDefinition pe : profile.getSnapshot().getElement()) { 5145 if (pe.getMustSupport()) { 5146 usesMustSupport = "Y"; 5147 break; 5148 } 5149 } 5150 profile.setUserData("usesMustSupport", usesMustSupport); 5151 } 5152 if (usesMustSupport.equals("Y")) { 5153 String elementSupported = ei.getElement().getUserString("elementSupported"); 5154 if (elementSupported == null || ei.definition.getMustSupport()) 5155 if (ei.definition.getMustSupport()) { 5156 ei.getElement().setUserData("elementSupported", "Y"); 5157 } 5158 } 5159 } 5160 5161 public void checkCardinalities(List<ValidationMessage> errors, StructureDefinition profile, Element element, NodeStack stack, 5162 List<ElementDefinition> childDefinitions, List<ElementInfo> children, List<String> problematicPaths) throws DefinitionException { 5163 // 3. report any definitions that have a cardinality problem 5164 for (ElementDefinition ed : childDefinitions) { 5165 if (ed.getRepresentation().isEmpty()) { // ignore xml attributes 5166 int count = 0; 5167 List<ElementDefinition> slices = null; 5168 if (ed.hasSlicing()) 5169 slices = profileUtilities.getSliceList(profile, ed); 5170 for (ElementInfo ei : children) 5171 if (ei.definition == ed) 5172 count++; 5173 else if (slices != null) { 5174 for (ElementDefinition sed : slices) { 5175 if (ei.definition == sed) { 5176 count++; 5177 break; 5178 } 5179 } 5180 } 5181 if (ed.getMin() > 0) { 5182 if (problematicPaths.contains(ed.getPath())) 5183 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())); 5184 else { 5185 if (count < ed.getMin()) { 5186 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)); 5187 } 5188 } 5189 } 5190 if (ed.hasMax() && !ed.getMax().equals("*")) { 5191 if (problematicPaths.contains(ed.getPath())) 5192 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()); 5193 else if (count > Integer.parseInt(ed.getMax())) { 5194 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)); 5195 } 5196 } 5197 } 5198 } 5199 } 5200 5201 public List<String> assignChildren(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, Element resource, 5202 NodeStack stack, List<ElementDefinition> childDefinitions, List<ElementInfo> children) throws DefinitionException { 5203 // 2. assign children to a definition 5204 // for each definition, for each child, check whether it belongs in the slice 5205 ElementDefinition slicer = null; 5206 boolean unsupportedSlicing = false; 5207 List<String> problematicPaths = new ArrayList<String>(); 5208 String slicingPath = null; 5209 int sliceOffset = 0; 5210 for (int i = 0; i < childDefinitions.size(); i++) { 5211 ElementDefinition ed = childDefinitions.get(i); 5212 boolean childUnsupportedSlicing = false; 5213 boolean process = true; 5214 if (ed.hasSlicing() && !ed.getSlicing().getOrdered()) { 5215 slicingPath = ed.getPath(); 5216 } else if (slicingPath != null && ed.getPath().equals(slicingPath)) { 5217 ; // nothing 5218 } else if (slicingPath != null && !ed.getPath().startsWith(slicingPath)) { 5219 slicingPath = null; 5220 } 5221 // where are we with slicing 5222 if (ed.hasSlicing()) { 5223 if (slicer != null && slicer.getPath().equals(ed.getPath())) { 5224 String errorContext = "profile " + profile.getUrl(); 5225 if (!resource.getChildValue(ID).isEmpty()) { 5226 errorContext += "; instance " + resource.getChildValue("id"); 5227 } 5228 throw new DefinitionException(context.formatMessage(I18nConstants.SLICE_ENCOUNTERED_MIDWAY_THROUGH_SET_PATH___ID___, slicer.getPath(), slicer.getId(), errorContext)); 5229 } 5230 slicer = ed; 5231 process = false; 5232 sliceOffset = i; 5233 } else if (slicer != null && !slicer.getPath().equals(ed.getPath())) 5234 slicer = null; 5235 5236 for (ElementInfo ei : children) { 5237 if (ei.sliceInfo == null) { 5238 ei.sliceInfo = new ArrayList<>(); 5239 } 5240 unsupportedSlicing = matchSlice(hostContext, errors, ei.sliceInfo, profile, stack, slicer, unsupportedSlicing, problematicPaths, sliceOffset, i, ed, childUnsupportedSlicing, ei); 5241 } 5242 } 5243 int last = -1; 5244 ElementInfo lastei = null; 5245 int lastSlice = -1; 5246 for (ElementInfo ei : children) { 5247 String sliceInfo = ""; 5248 if (slicer != null) { 5249 sliceInfo = " (slice: " + slicer.getPath() + ")"; 5250 } 5251 if (!unsupportedSlicing) { 5252 if (ei.additionalSlice && ei.definition != null) { 5253 if (ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPEN) || 5254 ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPENATEND) && true /* TODO: replace "true" with condition to check that this element is at "end" */) { 5255 slicingHint(errors, IssueType.INFORMATIONAL, ei.line(), ei.col(), ei.getPath(), false, isProfile(slicer) || isCritical(ei.sliceInfo), 5256 context.formatMessage(I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_, 5257 profile == null ? "" : " defined in the profile " + profile.getUrl()), 5258 context.formatMessage(I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_, profile == null ? "" : I18nConstants.DEFINED_IN_THE_PROFILE + profile.getUrl()) + errorSummaryForSlicingAsHtml(ei.sliceInfo), 5259 errorSummaryForSlicingAsText(ei.sliceInfo)); 5260 } else if (ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.CLOSED)) { 5261 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)); 5262 } 5263 } else { 5264 // Don't raise this if we're in an abstract profile, like Resource 5265 if (!profile.getAbstract()) { 5266 rule(errors, IssueType.NOTSUPPORTED, ei.line(), ei.col(), ei.getPath(), (ei.definition != null), I18nConstants.VALIDATION_VAL_PROFILE_NOTALLOWED, profile.getUrl()); 5267 } 5268 } 5269 } 5270 // TODO: Should get the order of elements correct when parsing elements that are XML attributes vs. elements 5271 boolean isXmlAttr = false; 5272 if (ei.definition != null) { 5273 for (Enumeration<PropertyRepresentation> r : ei.definition.getRepresentation()) { 5274 if (r.getValue() == PropertyRepresentation.XMLATTR) { 5275 isXmlAttr = true; 5276 break; 5277 } 5278 } 5279 } 5280 5281 if (!ToolingExtensions.readBoolExtension(profile, "http://hl7.org/fhir/StructureDefinition/structuredefinition-xml-no-order")) { 5282 boolean ok = (ei.definition == null) || (ei.index >= last) || isXmlAttr; 5283 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()); 5284 } 5285 if (ei.slice != null && ei.index == last && ei.slice.getSlicing().getOrdered()) { 5286 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()); 5287 } 5288 if (ei.definition == null || !isXmlAttr) { 5289 last = ei.index; 5290 lastei = ei; 5291 } 5292 if (ei.slice != null) { 5293 lastSlice = ei.sliceindex; 5294 } else { 5295 lastSlice = -1; 5296 } 5297 } 5298 return problematicPaths; 5299 } 5300 5301 5302 public List<ElementInfo> listChildren(Element element, NodeStack stack) { 5303 // 1. List the children, and remember their exact path (convenience) 5304 List<ElementInfo> children = new ArrayList<ElementInfo>(); 5305 ChildIterator iter = new ChildIterator(this, stack.getLiteralPath(), element); 5306 while (iter.next()) 5307 children.add(new ElementInfo(iter.name(), iter.element(), iter.path(), iter.count())); 5308 return children; 5309 } 5310 5311 public void checkInvariants(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition, Element resource, Element element, NodeStack stack, boolean onlyNonInherited) throws FHIRException { 5312 checkInvariants(hostContext, errors, stack.getLiteralPath(), profile, definition, null, null, resource, element, onlyNonInherited); 5313 } 5314 5315 public boolean matchSlice(ValidatorHostContext hostContext, List<ValidationMessage> errors, List<ValidationMessage> sliceInfo, StructureDefinition profile, NodeStack stack, 5316 ElementDefinition slicer, boolean unsupportedSlicing, List<String> problematicPaths, int sliceOffset, int i, ElementDefinition ed, 5317 boolean childUnsupportedSlicing, ElementInfo ei) { 5318 boolean match = false; 5319 if (slicer == null || slicer == ed) { 5320 match = nameMatches(ei.getName(), tail(ed.getPath())); 5321 } else { 5322 if (nameMatches(ei.getName(), tail(ed.getPath()))) 5323 try { 5324// System.out.println("match slices for "+stack.getLiteralPath()+": "+slicer.getId()+" = "+slicingSummary(slicer.getSlicing())); 5325 match = sliceMatches(hostContext, ei.getElement(), ei.getPath(), slicer, ed, profile, errors, sliceInfo, stack, profile); 5326 if (match) { 5327 ei.slice = slicer; 5328 5329 // Since a defined slice was found, this is not an additional (undefined) slice. 5330 ei.additionalSlice = false; 5331 } else if (ei.slice == null) { 5332 // 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 5333 ei.additionalSlice = true; 5334 } 5335 } catch (FHIRException e) { 5336 rule(errors, IssueType.PROCESSING, ei.line(), ei.col(), ei.getPath(), false, I18nConstants.SLICING_CANNOT_BE_EVALUATED, e.getMessage()); 5337 unsupportedSlicing = true; 5338 childUnsupportedSlicing = true; 5339 } 5340 } 5341 if (match) { 5342 boolean isOk = ei.definition == null || ei.definition == slicer || (ei.definition.getPath().endsWith("[x]") && ed.getPath().startsWith(ei.definition.getPath().replace("[x]", ""))); 5343 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() : ""))) { 5344 ei.definition = ed; 5345 if (ei.slice == null) { 5346 ei.index = i; 5347 } else { 5348 ei.index = sliceOffset; 5349 ei.sliceindex = i - (sliceOffset + 1); 5350 } 5351 } 5352 } else if (childUnsupportedSlicing) { 5353 problematicPaths.add(ed.getPath()); 5354 } 5355 return unsupportedSlicing; 5356 } 5357 5358 private String slicingSummary(ElementDefinitionSlicingComponent slicing) { 5359 StringBuilder b = new StringBuilder(); 5360 b.append('['); 5361 boolean first = true; 5362 for (ElementDefinitionSlicingDiscriminatorComponent t : slicing.getDiscriminator()) { 5363 if (first) first = false; else b.append(","); 5364 b.append(t.getType().toCode()); 5365 b.append(":"); 5366 b.append(t.getPath()); 5367 } 5368 b.append(']'); 5369 b.append(slicing.getOrdered() ? ";ordered" : ""); 5370 b.append(slicing.getRules().toString()); 5371 return b.toString(); 5372 } 5373 5374 private ElementDefinition getElementByTail(StructureDefinition p, String tail) throws DefinitionException { 5375 if (tail == null) 5376 return p.getSnapshot().getElement().get(0); 5377 for (ElementDefinition t : p.getSnapshot().getElement()) { 5378 if (tail.equals(t.getId())) 5379 return t; 5380 } 5381 throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_ELEMENT_WITH_ID_, tail)); 5382 } 5383 5384 private IdStatus idStatusForEntry(Element ep, ElementInfo ei) { 5385 if (isBundleEntry(ei.getPath())) { 5386 Element req = ep.getNamedChild("request"); 5387 Element resp = ep.getNamedChild("response"); 5388 Element fullUrl = ep.getNamedChild(FULL_URL); 5389 Element method = null; 5390 Element url = null; 5391 if (req != null) { 5392 method = req.getNamedChild("method"); 5393 url = req.getNamedChild("url"); 5394 } 5395 if (resp != null) { 5396 return IdStatus.OPTIONAL; 5397 } 5398 if (method == null) { 5399 if (fullUrl == null) 5400 return IdStatus.REQUIRED; 5401 else if (fullUrl.primitiveValue().startsWith("urn:uuid:") || fullUrl.primitiveValue().startsWith("urn:oid:")) 5402 return IdStatus.OPTIONAL; 5403 else 5404 return IdStatus.REQUIRED; 5405 } else { 5406 String s = method.primitiveValue(); 5407 if (s.equals("PUT")) { 5408 if (url == null) 5409 return IdStatus.REQUIRED; 5410 else 5411 return IdStatus.OPTIONAL; // or maybe prohibited? not clear 5412 } else if (s.equals("POST")) 5413 return IdStatus.OPTIONAL; // this should be prohibited, but see task 9102 5414 else // actually, we should never get to here; a bundle entry with method get/delete should not have a resource 5415 return IdStatus.OPTIONAL; 5416 } 5417 } else if (isParametersEntry(ei.getPath()) || isBundleOutcome(ei.getPath())) 5418 return IdStatus.OPTIONAL; 5419 else 5420 return IdStatus.REQUIRED; 5421 } 5422 5423 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 { 5424 if (noInvariantChecks) 5425 return; 5426 5427 for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) { 5428 if (inv.hasExpression() && (!onlyNonInherited || !inv.hasSource() || (!isInheritedProfile(profile, inv.getSource()) && !isInheritedProfile(ed.getType(), inv.getSource())) )) { 5429 @SuppressWarnings("unchecked") 5430 Map<String, List<ValidationMessage>> invMap = executionId.equals(element.getUserString(EXECUTION_ID)) ? (Map<String, List<ValidationMessage>>) element.getUserData(EXECUTED_CONSTRAINT_LIST) : null; 5431 if (invMap == null) { 5432 invMap = new HashMap<>(); 5433 element.setUserData(EXECUTED_CONSTRAINT_LIST, invMap); 5434 element.setUserData(EXECUTION_ID, executionId); 5435 } 5436 List<ValidationMessage> invErrors = null; 5437 // 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. 5438 String key = fixExpr(inv.getExpression(), inv.getKey()); 5439 if (!invMap.keySet().contains(key)) { 5440 invErrors = new ArrayList<ValidationMessage>(); 5441 invMap.put(key, invErrors); 5442 checkInvariant(hostContext, invErrors, path, profile, resource, element, inv); 5443 } else { 5444 invErrors = (ArrayList<ValidationMessage>)invMap.get(key); 5445 } 5446 errors.addAll(invErrors); 5447 } 5448 } 5449 } 5450 5451 private boolean isInheritedProfile(List<TypeRefComponent> types, String source) { 5452 for (TypeRefComponent type : types) { 5453 for (CanonicalType c : type.getProfile()) { 5454 StructureDefinition sd = context.fetchResource(StructureDefinition.class, c.asStringValue()); 5455 if (sd != null) { 5456 if (sd.getUrl().equals(source)) { 5457 return true; 5458 } 5459 if (isInheritedProfile(sd, source)) { 5460 return true; 5461 } 5462 } 5463 } 5464 } 5465 return false; 5466 } 5467 5468 private boolean isInheritedProfile(StructureDefinition profile, String source) { 5469 if (source.equals(profile.getUrl())) { 5470 return false; 5471 } 5472 while (profile != null) { 5473 profile = context.fetchResource(StructureDefinition.class, profile.getBaseDefinition()); 5474 if (profile != null) { 5475 if (source.equals(profile.getUrl())) { 5476 return true; 5477 } 5478 } 5479 } 5480 return false; 5481 } 5482 5483 public void checkInvariant(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, StructureDefinition profile, Element resource, Element element, ElementDefinitionConstraintComponent inv) throws FHIRException { 5484// if (debug) { 5485// System.out.println("inv "+inv.getKey()+" on "+path+" in "+resource.fhirType()+" {{ "+inv.getExpression()+" }}"); 5486// } 5487 ExpressionNode n = (ExpressionNode) inv.getUserData("validator.expression.cache"); 5488 if (n == null) { 5489 long t = System.nanoTime(); 5490 try { 5491 n = fpe.parse(fixExpr(inv.getExpression(), inv.getKey())); 5492 } catch (FHIRLexerException e) { 5493 rule(errors, IssueType.INVARIANT, element.line(), element.col(), path, false, I18nConstants.PROBLEM_PROCESSING_EXPRESSION__IN_PROFILE__PATH__, inv.getExpression(), profile.getUrl(), path, e.getMessage()); 5494 return; 5495 } 5496 timeTracker.fpe(t); 5497 inv.setUserData("validator.expression.cache", n); 5498 } 5499 5500 String msg; 5501 boolean ok; 5502 try { 5503 long t = System.nanoTime(); 5504 ok = fpe.evaluateToBoolean(hostContext, resource, hostContext.getRootResource(), element, n); 5505 timeTracker.fpe(t); 5506 msg = fpe.forLog(); 5507 } catch (Exception ex) { 5508 ok = false; 5509 msg = ex.getMessage(); 5510 } 5511 if (!ok) { 5512 if (!Utilities.noString(msg)) { 5513 msg = "'" + inv.getHuman()+"' (" + msg + ")"; 5514 } else if (wantInvariantInMessage) { 5515 msg = "'" + inv.getHuman()+"' [" + n.toString() + "]"; 5516 } else { 5517 msg = context.formatMessage(I18nConstants.INV_FAILED, "'" + inv.getHuman()+"'"); 5518 } 5519 if (inv.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice") && 5520 ToolingExtensions.readBooleanExtension(inv, "http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice")) { 5521 if (bpWarnings == BestPracticeWarningLevel.Hint) 5522 hint(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": " + msg); 5523 else if (bpWarnings == BestPracticeWarningLevel.Warning) 5524 warning(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": '" + inv.getHuman()+"' " + msg); 5525 else if (bpWarnings == BestPracticeWarningLevel.Error) 5526 rule(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": '" + inv.getHuman()+"' " + msg); 5527 } else if (inv.getSeverity() == ConstraintSeverity.ERROR) { 5528 rule(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": '" + inv.getHuman()+"' " + msg); 5529 } else if (inv.getSeverity() == ConstraintSeverity.WARNING) { 5530 warning(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": '" + inv.getHuman()+"' " + msg); 5531 } 5532 } 5533 } 5534 5535 private void validateObservation(List<ValidationMessage> errors, Element element, NodeStack stack) { 5536 // all observations should have a subject, a performer, and a time 5537 5538 bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), element.getNamedChild("subject") != null, I18nConstants.ALL_OBSERVATIONS_SHOULD_HAVE_A_SUBJECT); 5539 List<Element> performers = new ArrayList<>(); 5540 element.getNamedChildren("performer", performers); 5541 bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), performers.size() > 0, I18nConstants.ALL_OBSERVATIONS_SHOULD_HAVE_A_PERFORMER); 5542 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); 5543 } 5544 5545 /* 5546 * The actual base entry point for internal use (re-entrant) 5547 */ 5548 private void validateResource(ValidatorHostContext hostContext, List<ValidationMessage> errors, Element resource, 5549 Element element, StructureDefinition defn, IdStatus idstatus, NodeStack stack) throws FHIRException { 5550 5551 // check here if we call validation policy here, and then change it to the new interface 5552 assert stack != null; 5553 assert resource != null; 5554 boolean ok = true; 5555 String resourceName = element.getType(); // todo: consider namespace...? 5556 5557 if (defn == null) { 5558 long t = System.nanoTime(); 5559 defn = element.getProperty().getStructure(); 5560 if (defn == null) 5561 defn = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + resourceName); 5562 timeTracker.sd(t); 5563 //check exists 5564 ok = rule(errors, IssueType.INVALID, element.line(), element.col(), stack.addToLiteralPath(resourceName), 5565 defn != null, I18nConstants.VALIDATION_VAL_PROFILE_NODEFINITION, resourceName); 5566 } 5567 5568 // special case: we have a bundle, and the profile is not for a bundle. We'll try the first entry instead 5569 if (!typeMatchesDefn(resourceName, defn) && resourceName.equals(BUNDLE)) { 5570 NodeStack first = getFirstEntry(stack); 5571 if (first != null && typeMatchesDefn(first.getElement().getType(), defn)) { 5572 element = first.getElement(); 5573 stack = first; 5574 resourceName = element.getType(); // todo: consider namespace...? 5575 idstatus = IdStatus.OPTIONAL; // why? 5576 } 5577 // todo: validate everything in this bundle. 5578 } 5579 if (ok) { 5580 if (idstatus == IdStatus.REQUIRED && (element.getNamedChild(ID) == null)) { 5581 rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.RESOURCE_RES_ID_MISSING); 5582 } else if (idstatus == IdStatus.PROHIBITED && (element.getNamedChild(ID) != null)) { 5583 rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.RESOURCE_RES_ID_PROHIBITED); 5584 } 5585 if (element.getNamedChild(ID) != null) { 5586 Element eid = element.getNamedChild(ID); 5587 if (eid.getProperty() != null && eid.getProperty().getDefinition() != null && eid.getProperty().getDefinition().getBase().getPath().equals("Resource.id")) { 5588 NodeStack ns = stack.push(eid, -1, eid.getProperty().getDefinition(), null); 5589 rule(errors, IssueType.INVALID, eid.line(), eid.col(), ns.getLiteralPath(), FormatUtilities.isValidId(eid.primitiveValue()), I18nConstants.RESOURCE_RES_ID_MALFORMED); 5590 } 5591 } 5592 // validate 5593 if (rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), resourceName.equals(defn.getType()), I18nConstants.VALIDATION_VAL_PROFILE_WRONGTYPE, 5594 defn.getType(), resourceName, defn.getUrl())) { 5595 start(hostContext, errors, element, element, defn, stack); // root is both definition and type 5596 } 5597 } 5598 } 5599 5600 private boolean typeMatchesDefn(String name, StructureDefinition defn) { 5601 if (defn.getKind() == StructureDefinitionKind.LOGICAL) { 5602 return name.equals(defn.getType()) || name.equals(defn.getName()) || name.equals(defn.getId()); 5603 } else { 5604 return name.matches(defn.getType()); 5605 } 5606 } 5607 5608 private NodeStack getFirstEntry(NodeStack bundle) { 5609 List<Element> list = new ArrayList<Element>(); 5610 bundle.getElement().getNamedChildren(ENTRY, list); 5611 if (list.isEmpty()) 5612 return null; 5613 Element resource = list.get(0).getNamedChild(RESOURCE); 5614 if (resource == null) 5615 return null; 5616 else { 5617 NodeStack entry = bundle.push(list.get(0), 0, list.get(0).getProperty().getDefinition(), list.get(0).getProperty().getDefinition()); 5618 return entry.push(resource, -1, resource.getProperty().getDefinition(), context.fetchTypeDefinition(resource.fhirType()).getSnapshot().getElementFirstRep()); 5619 } 5620 } 5621 5622 private boolean valueMatchesCriteria(Element value, ElementDefinition criteria, StructureDefinition profile) throws FHIRException { 5623 if (criteria.hasFixed()) { 5624 List<ValidationMessage> msgs = new ArrayList<ValidationMessage>(); 5625 checkFixedValue(msgs, "{virtual}", value, criteria.getFixed(), profile.getUrl(), "value", null, false); 5626 return msgs.size() == 0; 5627 } else if (criteria.hasBinding() && criteria.getBinding().getStrength() == BindingStrength.REQUIRED && criteria.getBinding().hasValueSet()) { 5628 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SLICE_MATCHING__SLICE_MATCHING_BY_VALUE_SET_NOT_DONE)); 5629 } else { 5630 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SLICE_MATCHING__NO_FIXED_VALUE_OR_REQUIRED_VALUE_SET)); 5631 } 5632 } 5633 5634 private boolean yearIsValid(String v) { 5635 if (v == null) { 5636 return false; 5637 } 5638 try { 5639 int i = Integer.parseInt(v.substring(0, Math.min(4, v.length()))); 5640 return i >= 1800 && i <= thisYear() + 80; 5641 } catch (NumberFormatException e) { 5642 return false; 5643 } 5644 } 5645 5646 private int thisYear() { 5647 return Calendar.getInstance().get(Calendar.YEAR); 5648 } 5649 5650 5651 public String reportTimes() { 5652 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); 5653 timeTracker.reset(); 5654 return s; 5655 } 5656 5657 public boolean isNoBindingMsgSuppressed() { 5658 return noBindingMsgSuppressed; 5659 } 5660 5661 public IResourceValidator setNoBindingMsgSuppressed(boolean noBindingMsgSuppressed) { 5662 this.noBindingMsgSuppressed = noBindingMsgSuppressed; 5663 return this; 5664 } 5665 5666 5667 public boolean isNoTerminologyChecks() { 5668 return noTerminologyChecks; 5669 } 5670 5671 public IResourceValidator setNoTerminologyChecks(boolean noTerminologyChecks) { 5672 this.noTerminologyChecks = noTerminologyChecks; 5673 return this; 5674 } 5675 5676 public void checkAllInvariants() { 5677 for (StructureDefinition sd : context.allStructures()) { 5678 if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 5679 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 5680 for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) { 5681 if (inv.hasExpression()) { 5682 try { 5683 ExpressionNode n = (ExpressionNode) inv.getUserData("validator.expression.cache"); 5684 if (n == null) { 5685 n = fpe.parse(fixExpr(inv.getExpression(), inv.getKey())); 5686 inv.setUserData("validator.expression.cache", n); 5687 } 5688 fpe.check(null, sd.getKind() == StructureDefinitionKind.RESOURCE ? sd.getType() : "DomainResource", ed.getPath(), n); 5689 } catch (Exception e) { 5690 System.out.println("Error processing structure [" + sd.getId() + "] path " + ed.getPath() + ":" + inv.getKey() + " ('" + inv.getExpression() + "'): " + e.getMessage()); 5691 } 5692 } 5693 } 5694 } 5695 } 5696 } 5697 } 5698 5699 private String fixExpr(String expr, String key) { 5700 // this is a hack work around for past publication of wrong FHIRPath expressions 5701 // R4 5702 // waiting for 4.0.2 5703 //TODO is this expression below correct? @grahamegrieve 5704 if ("probability is decimal implies (probability as decimal) <= 100".equals(expr)) { 5705 return "probability.empty() or ((probability is decimal) implies ((probability as decimal) <= 100))"; 5706 } 5707 if ("enableWhen.count() > 2 implies enableBehavior.exists()".equals(expr)) { 5708 return "enableWhen.count() >= 2 implies enableBehavior.exists()"; 5709 } 5710 if ("txt-2".equals(key)) { 5711 return "htmlChecks2()"; 5712 } 5713 5714 // handled in 4.0.1 5715 if ("(component.empty() and hasMember.empty()) implies (dataAbsentReason or value)".equals(expr)) { 5716 return "(component.empty() and hasMember.empty()) implies (dataAbsentReason.exists() or value.exists())"; 5717 } 5718 if ("isModifier implies isModifierReason.exists()".equals(expr)) { 5719 return "(isModifier.exists() and isModifier) implies isModifierReason.exists()"; 5720 } 5721 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)) { 5722 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('\\\\..*','')&'.')))"; 5723 } 5724 if ("differential.element.all(id) and differential.element.id.trace('ids').isDistinct()".equals(expr)) { 5725 return "differential.element.all(id.exists()) and differential.element.id.trace('ids').isDistinct()"; 5726 } 5727 if ("snapshot.element.all(id) and snapshot.element.id.trace('ids').isDistinct()".equals(expr)) { 5728 return "snapshot.element.all(id.exists()) and snapshot.element.id.trace('ids').isDistinct()"; 5729 } 5730 5731 // R3 5732 if ("(code or value.empty()) and (system.empty() or system = 'urn:iso:std:iso:4217')".equals(expr)) { 5733 return "(code.exists() or value.empty()) and (system.empty() or system = 'urn:iso:std:iso:4217')"; 5734 } 5735 if ("value.empty() or code!=component.code".equals(expr)) { 5736 return "value.empty() or (code in component.code).not()"; 5737 } 5738 if ("(code or value.empty()) and (system.empty() or system = %ucum) and (value.empty() or value > 0)".equals(expr)) { 5739 return "(code.exists() or value.empty()) and (system.empty() or system = %ucum) and (value.empty() or value > 0)"; 5740 } 5741 if ("element.all(definition and min and max)".equals(expr)) { 5742 return "element.all(definition.exists() and min.exists() and max.exists())"; 5743 } 5744 if ("telecom or endpoint".equals(expr)) { 5745 return "telecom.exists() or endpoint.exists()"; 5746 } 5747 if ("(code or value.empty()) and (system.empty() or system = %ucum) and (value.empty() or value > 0)".equals(expr)) { 5748 return "(code.exists() or value.empty()) and (system.empty() or system = %ucum) and (value.empty() or value > 0)"; 5749 } 5750 if ("searchType implies type = 'string'".equals(expr)) { 5751 return "searchType.exists() implies type = 'string'"; 5752 } 5753 if ("abatement.empty() or (abatement as boolean).not() or clinicalStatus='resolved' or clinicalStatus='remission' or clinicalStatus='inactive'".equals(expr)) { 5754 return "abatement.empty() or (abatement is boolean).not() or (abatement as boolean).not() or (clinicalStatus = 'resolved') or (clinicalStatus = 'remission') or (clinicalStatus = 'inactive')"; 5755 } 5756 if ("(component.empty() and related.empty()) implies (dataAbsentReason or value)".equals(expr)) { 5757 return "(component.empty() and related.empty()) implies (dataAbsentReason.exists() or value.exists())"; 5758 } 5759 if ("reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids'))".equals(expr)) { 5760 return "(reference = '#') or reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids'))"; 5761 } 5762 if ("reference.startsWith('#').not() or (reference.substring(1).trace('url') in %resource.contained.id.trace('ids'))".equals(expr)) { 5763 return "(reference = '#') or reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids'))"; 5764 } 5765 if ("probability is decimal implies probability.as(decimal) <= 100".equals(expr)) { 5766 if (key.equals("ras-2")) { 5767 return "probability.empty() or (probability is decimal implies probability.as(decimal) <= 100)"; 5768 } 5769 } 5770 if ("".equals(expr)) { 5771 return ""; 5772 } 5773 return expr; 5774 } 5775 5776 public IEvaluationContext getExternalHostServices() { 5777 return externalHostServices; 5778 } 5779 5780 public String getValidationLanguage() { 5781 return validationLanguage; 5782 } 5783 5784 public void setValidationLanguage(String validationLanguage) { 5785 this.validationLanguage = validationLanguage; 5786 } 5787 5788 public boolean isDebug() { 5789 return debug; 5790 } 5791 5792 public void setDebug(boolean debug) { 5793 this.debug = debug; 5794 } 5795 private String tail(String path) { 5796 return path.substring(path.lastIndexOf(".") + 1); 5797 } 5798 private String tryParse(String ref) { 5799 String[] parts = ref.split("\\/"); 5800 switch (parts.length) { 5801 case 1: 5802 return null; 5803 case 2: 5804 return checkResourceType(parts[0]); 5805 default: 5806 if (parts[parts.length - 2].equals("_history") && parts.length >= 4) 5807 return checkResourceType(parts[parts.length - 4]); 5808 else 5809 return checkResourceType(parts[parts.length - 2]); 5810 } 5811 } 5812 5813 private boolean typesAreAllReference(List<TypeRefComponent> theType) { 5814 for (TypeRefComponent typeRefComponent : theType) { 5815 if (typeRefComponent.getCode().equals("Reference") == false) { 5816 return false; 5817 } 5818 } 5819 return true; 5820 } 5821 5822 5823 public ValidationResult checkCodeOnServer(NodeStack stack, ValueSet vs, String value, ValidationOptions options) { 5824 return context.validateCode(options, value, vs); 5825 } 5826 5827 // no delay on this one? 5828 public ValidationResult checkCodeOnServer(NodeStack stack, String code, String system, String version, String display, boolean checkDisplay) { 5829 return context.validateCode(baseOptions.setLanguage(stack.getWorkingLang()), system, version, code, checkDisplay ? display : null); 5830 } 5831 5832 public ValidationResult checkCodeOnServer(NodeStack stack, ValueSet valueset, Coding c, boolean checkMembership) { 5833 if (checkMembership) { 5834 return context.validateCode(baseOptions.setLanguage(stack.getWorkingLang()).checkValueSetOnly(), c, valueset); 5835 } else { 5836 return context.validateCode(baseOptions.setLanguage(stack.getWorkingLang()).noCheckValueSetMembership(), c, valueset); 5837 } 5838 } 5839 5840 public ValidationResult checkCodeOnServer(NodeStack stack, ValueSet valueset, CodeableConcept cc, boolean vsOnly) { 5841 if (vsOnly) { 5842 return context.validateCode(baseOptions.setLanguage(stack.getWorkingLang()).checkValueSetOnly(), cc, valueset); 5843 } else { 5844 return context.validateCode(baseOptions.setLanguage(stack.getWorkingLang()), cc, valueset); 5845 } 5846 } 5847 5848 public boolean isSecurityChecks() { 5849 return securityChecks; 5850 } 5851 5852 public void setSecurityChecks(boolean securityChecks) { 5853 this.securityChecks = securityChecks; 5854 } 5855 5856 @Override 5857 public List<BundleValidationRule> getBundleValidationRules() { 5858 return bundleValidationRules ; 5859 } 5860 5861 @Override 5862 public boolean isValidateValueSetCodesOnTxServer() { 5863 return validateValueSetCodesOnTxServer; 5864 } 5865 5866 @Override 5867 public void setValidateValueSetCodesOnTxServer(boolean value) { 5868 this.validateValueSetCodesOnTxServer = value; 5869 } 5870 5871 public boolean isNoCheckAggregation() { 5872 return noCheckAggregation; 5873 } 5874 5875 public void setNoCheckAggregation(boolean noCheckAggregation) { 5876 this.noCheckAggregation = noCheckAggregation; 5877 } 5878 5879 5880 public static void setParents(Element element) { 5881 if (element != null && !element.hasParentForValidator()) { 5882 element.setParentForValidator(null); 5883 setParentsInner(element); 5884 } 5885 } 5886 5887 public static void setParentsInner(Element element) { 5888 for (Element child : element.getChildren()) { 5889 child.setParentForValidator(element); 5890 setParentsInner(child); 5891 } 5892 5893 } 5894 5895 public void setQuestionnaireMode(QuestionnaireMode questionnaireMode) { 5896 this.questionnaireMode = questionnaireMode; 5897 } 5898 5899 public QuestionnaireMode getQuestionnaireMode() { 5900 return questionnaireMode; 5901 } 5902 5903 public boolean isWantCheckSnapshotUnchanged() { 5904 return wantCheckSnapshotUnchanged; 5905 } 5906 5907 public void setWantCheckSnapshotUnchanged(boolean wantCheckSnapshotUnchanged) { 5908 this.wantCheckSnapshotUnchanged = wantCheckSnapshotUnchanged; 5909 } 5910 5911 public ValidationOptions getBaseOptions() { 5912 return baseOptions; 5913 } 5914 5915 public void setBaseOptions(ValidationOptions baseOptions) { 5916 this.baseOptions = baseOptions; 5917 } 5918 5919 public boolean isNoUnicodeBiDiControlChars() { 5920 return noUnicodeBiDiControlChars; 5921 } 5922 5923 public void setNoUnicodeBiDiControlChars(boolean noUnicodeBiDiControlChars) { 5924 this.noUnicodeBiDiControlChars = noUnicodeBiDiControlChars; 5925 } 5926 5927}