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