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