
001package org.hl7.fhir.r5.conformance.profile; 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 java.io.IOException; 034import java.io.OutputStream; 035import java.util.ArrayList; 036import java.util.Arrays; 037import java.util.Collections; 038import java.util.Comparator; 039import java.util.Date; 040import java.util.HashMap; 041import java.util.HashSet; 042import java.util.Iterator; 043import java.util.List; 044import java.util.Map; 045import java.util.Set; 046 047import lombok.extern.slf4j.Slf4j; 048import org.hl7.fhir.exceptions.DefinitionException; 049import org.hl7.fhir.exceptions.FHIRException; 050import org.hl7.fhir.exceptions.FHIRFormatError; 051import org.hl7.fhir.r5.conformance.ElementRedirection; 052import org.hl7.fhir.r5.conformance.profile.MappingAssistant.MappingMergeModeOption; 053import org.hl7.fhir.r5.context.IWorkerContext; 054import org.hl7.fhir.r5.elementmodel.ObjectConverter; 055import org.hl7.fhir.r5.elementmodel.Property; 056import org.hl7.fhir.r5.extensions.ExtensionDefinitions; 057import org.hl7.fhir.r5.extensions.ExtensionUtilities; 058import org.hl7.fhir.r5.fhirpath.ExpressionNode; 059import org.hl7.fhir.r5.fhirpath.ExpressionNode.Kind; 060import org.hl7.fhir.r5.fhirpath.ExpressionNode.Operation; 061import org.hl7.fhir.r5.fhirpath.FHIRPathEngine; 062import org.hl7.fhir.r5.model.Base; 063import org.hl7.fhir.r5.model.BooleanType; 064import org.hl7.fhir.r5.model.CanonicalType; 065import org.hl7.fhir.r5.model.Coding; 066import org.hl7.fhir.r5.model.DataType; 067import org.hl7.fhir.r5.model.Element; 068import org.hl7.fhir.r5.model.ElementDefinition; 069import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType; 070import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBaseComponent; 071import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingAdditionalComponent; 072import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent; 073import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent; 074import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionExampleComponent; 075import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent; 076import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent; 077import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent; 078import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules; 079import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; 080import org.hl7.fhir.r5.model.Enumerations.BindingStrength; 081import org.hl7.fhir.r5.model.Enumerations.FHIRVersion; 082import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; 083import org.hl7.fhir.r5.model.Extension; 084import org.hl7.fhir.r5.model.IdType; 085import org.hl7.fhir.r5.model.MarkdownType; 086import org.hl7.fhir.r5.model.Resource; 087import org.hl7.fhir.r5.model.StringType; 088import org.hl7.fhir.r5.model.StructureDefinition; 089import org.hl7.fhir.r5.model.StructureDefinition.ExtensionContextType; 090import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionContextComponent; 091import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionDifferentialComponent; 092import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 093import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionSnapshotComponent; 094import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; 095import org.hl7.fhir.r5.model.UriType; 096import org.hl7.fhir.r5.model.UsageContext; 097import org.hl7.fhir.r5.model.ValueSet; 098import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent; 099import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 100import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome; 101import org.hl7.fhir.r5.terminologies.utilities.ValidationResult; 102 103import org.hl7.fhir.r5.utils.UserDataNames; 104import org.hl7.fhir.r5.utils.xver.XVerExtensionManager; 105import org.hl7.fhir.r5.utils.xver.XVerExtensionManager.XVerExtensionStatus; 106import org.hl7.fhir.r5.utils.formats.CSVWriter; 107import org.hl7.fhir.r5.utils.xver.XVerExtensionManagerFactory; 108import org.hl7.fhir.utilities.*; 109import org.hl7.fhir.utilities.i18n.I18nConstants; 110import org.hl7.fhir.utilities.validation.ValidationMessage; 111import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 112import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 113import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 114import org.hl7.fhir.utilities.validation.ValidationOptions; 115import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row; 116import org.hl7.fhir.utilities.xml.SchematronWriter; 117import org.hl7.fhir.utilities.xml.SchematronWriter.Rule; 118import org.hl7.fhir.utilities.xml.SchematronWriter.SchematronType; 119import org.hl7.fhir.utilities.xml.SchematronWriter.Section; 120 121 122/** 123 * This class provides a set of utility operations for working with Profiles. 124 * Key functionality: 125 * * getChildMap --? 126 * * getChildList 127 * * generateSnapshot: Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile 128 * * closeDifferential: fill out a differential by excluding anything not mentioned 129 * * generateExtensionsTable: generate the HTML for a hierarchical table presentation of the extensions 130 * * generateTable: generate the HTML for a hierarchical table presentation of a structure 131 * * generateSpanningTable: generate the HTML for a table presentation of a network of structures, starting at a nominated point 132 * * summarize: describe the contents of a profile 133 * 134 * note to maintainers: Do not make modifications to the snapshot generation without first changing the snapshot generation test cases to demonstrate the grounds for your change 135 * 136 * @author Grahame 137 * 138 */ 139@MarkedToMoveToAdjunctPackage 140@Slf4j 141public class ProfileUtilities { 142 143 private static boolean suppressIgnorableExceptions; 144 145 146 public class ElementDefinitionCounter { 147 int countMin = 0; 148 int countMax = 0; 149 int index = 0; 150 ElementDefinition focus; 151 Set<String> names = new HashSet<>(); 152 153 public ElementDefinitionCounter(ElementDefinition ed, int i) { 154 focus = ed; 155 index = i; 156 } 157 158 public int checkMin() { 159 if (countMin > focus.getMin()) { 160 return countMin; 161 } else { 162 return -1; 163 } 164 } 165 166 public int checkMax() { 167 if (countMax > max(focus.getMax())) { 168 return countMax; 169 } else { 170 return -1; 171 } 172 } 173 174 private int max(String max) { 175 if ("*".equals(max)) { 176 return Integer.MAX_VALUE; 177 } else { 178 return Integer.parseInt(max); 179 } 180 } 181 182 public boolean count(ElementDefinition ed, String name) { 183 countMin = countMin + ed.getMin(); 184 if (countMax < Integer.MAX_VALUE) { 185 int m = max(ed.getMax()); 186 if (m == Integer.MAX_VALUE) { 187 countMax = m; 188 } else { 189 countMax = countMax + m; 190 } 191 } 192 boolean ok = !names.contains(name); 193 names.add(name); 194 return ok; 195 } 196 197 public ElementDefinition getFocus() { 198 return focus; 199 } 200 201 public boolean checkMinMax() { 202 return countMin <= countMax; 203 } 204 205 public int getIndex() { 206 return index; 207 } 208 209 } 210 211 public enum AllowUnknownProfile { 212 NONE, // exception if there's any unknown profiles (the default) 213 NON_EXTNEIONS, // don't raise an exception except on Extension (because more is going on there 214 ALL_TYPES // allow any unknow profile 215 } 216 217 /** 218 * These extensions are stripped in inherited profiles (and may be replaced by 219 */ 220 221 public static final List<String> NON_INHERITED_ED_URLS = Arrays.asList( 222 "http://hl7.org/fhir/tools/StructureDefinition/binding-definition", 223 "http://hl7.org/fhir/tools/StructureDefinition/no-binding", 224 "http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding", 225 "http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status", 226 "http://hl7.org/fhir/StructureDefinition/structuredefinition-category", 227 "http://hl7.org/fhir/StructureDefinition/structuredefinition-fmm", 228 "http://hl7.org/fhir/StructureDefinition/structuredefinition-implements", 229 "http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name", 230 "http://hl7.org/fhir/StructureDefinition/structuredefinition-security-category", 231 "http://hl7.org/fhir/StructureDefinition/structuredefinition-wg", 232 "http://hl7.org/fhir/StructureDefinition/structuredefinition-normative-version", 233 "http://hl7.org/fhir/tools/StructureDefinition/obligation-profile", 234 "http://hl7.org/fhir/StructureDefinition/obligation-profile", 235 "http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status-reason", 236 ExtensionDefinitions.EXT_SUMMARY/*, 237 ExtensionDefinitions.EXT_OBLIGATION_CORE, 238 ExtensionDefinitions.EXT_OBLIGATION_TOOLS*/); 239 240 public static final List<String> DEFAULT_INHERITED_ED_URLS = Arrays.asList( 241 "http://hl7.org/fhir/StructureDefinition/questionnaire-optionRestriction", 242 "http://hl7.org/fhir/StructureDefinition/questionnaire-referenceProfile", 243 "http://hl7.org/fhir/StructureDefinition/questionnaire-referenceResource", 244 "http://hl7.org/fhir/StructureDefinition/questionnaire-unitOption", 245 246 "http://hl7.org/fhir/StructureDefinition/mimeType"); 247 248 /** 249 * These extensions are ignored when found in differentials 250 */ 251 public static final List<String> NON_OVERRIDING_ED_URLS = Arrays.asList( 252 "http://hl7.org/fhir/StructureDefinition/elementdefinition-translatable", 253 ExtensionDefinitions.EXT_JSON_NAME, ExtensionDefinitions.EXT_JSON_NAME_DEPRECATED, 254 "http://hl7.org/fhir/tools/StructureDefinition/implied-string-prefix", 255 "http://hl7.org/fhir/tools/StructureDefinition/json-empty-behavior", 256 "http://hl7.org/fhir/tools/StructureDefinition/json-nullable", 257 "http://hl7.org/fhir/tools/StructureDefinition/json-primitive-choice", 258 "http://hl7.org/fhir/tools/StructureDefinition/json-property-key", 259 "http://hl7.org/fhir/tools/StructureDefinition/type-specifier", 260 "http://hl7.org/fhir/tools/StructureDefinition/xml-choice-group", 261 ExtensionDefinitions.EXT_XML_NAMESPACE, ExtensionDefinitions.EXT_XML_NAMESPACE_DEPRECATED, 262 ExtensionDefinitions.EXT_XML_NAME, ExtensionDefinitions.EXT_XML_NAME_DEPRECATED, 263 "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype" 264 ); 265 266 /** 267 * When these extensions are found, they override whatever is set on the ancestor element 268 */ 269 public static final List<String> OVERRIDING_ED_URLS = Arrays.asList( 270 "http://hl7.org/fhir/tools/StructureDefinition/elementdefinition-date-format", 271 ExtensionDefinitions.EXT_DATE_RULES, 272 "http://hl7.org/fhir/StructureDefinition/designNote", 273 "http://hl7.org/fhir/StructureDefinition/elementdefinition-allowedUnits", 274 "http://hl7.org/fhir/StructureDefinition/elementdefinition-question", 275 "http://hl7.org/fhir/StructureDefinition/entryFormat", 276 "http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces", 277 "http://hl7.org/fhir/StructureDefinition/maxSize", 278 "http://hl7.org/fhir/StructureDefinition/minLength", 279 "http://hl7.org/fhir/StructureDefinition/questionnaire-choiceOrientation", 280 "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory", 281 "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden", 282 "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl", 283 "http://hl7.org/fhir/StructureDefinition/questionnaire-signatureRequired", 284 "http://hl7.org/fhir/StructureDefinition/questionnaire-sliderStepValue", 285 "http://hl7.org/fhir/StructureDefinition/questionnaire-supportLink", 286 "http://hl7.org/fhir/StructureDefinition/questionnaire-unit", 287 "http://hl7.org/fhir/StructureDefinition/questionnaire-unitValueSet", 288 "http://hl7.org/fhir/StructureDefinition/questionnaire-usageMode", 289 "http://hl7.org/fhir/StructureDefinition/structuredefinition-display-hint", 290 "http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name" 291 ); 292 293 public IWorkerContext getContext() { 294 return this.context; 295 } 296 297 public static class SourcedChildDefinitions { 298 private StructureDefinition source; 299 private List<ElementDefinition> list; 300 private String path; 301 public SourcedChildDefinitions(StructureDefinition source, List<ElementDefinition> list) { 302 super(); 303 this.source = source; 304 this.list = list; 305 } 306 public SourcedChildDefinitions(StructureDefinition source, List<ElementDefinition> list, String path) { 307 super(); 308 this.source = source; 309 this.list = list; 310 this.path = path; 311 } 312 public StructureDefinition getSource() { 313 return source; 314 } 315 public List<ElementDefinition> getList() { 316 return list; 317 } 318 public String getPath() { 319 return path; 320 } 321 322 } 323 324 public class ElementDefinitionResolution { 325 326 private StructureDefinition source; 327 private ElementDefinition element; 328 329 public ElementDefinitionResolution(StructureDefinition source, ElementDefinition element) { 330 this.source = source; 331 this.element = element; 332 } 333 334 public StructureDefinition getSource() { 335 return source; 336 } 337 338 public ElementDefinition getElement() { 339 return element; 340 } 341 342 } 343 344 public static class ElementChoiceGroup { 345 private Row row; 346 private String name; 347 private boolean mandatory; 348 private List<String> elements = new ArrayList<>(); 349 350 public ElementChoiceGroup(String name, boolean mandatory) { 351 super(); 352 this.name = name; 353 this.mandatory = mandatory; 354 } 355 public Row getRow() { 356 return row; 357 } 358 public List<String> getElements() { 359 return elements; 360 } 361 public void setRow(Row row) { 362 this.row = row; 363 } 364 public String getName() { 365 return name; 366 } 367 public boolean isMandatory() { 368 return mandatory; 369 } 370 public void setMandatory(boolean mandatory) { 371 this.mandatory = mandatory; 372 } 373 374 } 375 376 private static final int MAX_RECURSION_LIMIT = 10; 377 378 public static class ExtensionContext { 379 380 private ElementDefinition element; 381 private StructureDefinition defn; 382 383 public ExtensionContext(StructureDefinition ext, ElementDefinition ed) { 384 this.defn = ext; 385 this.element = ed; 386 } 387 388 public ElementDefinition getElement() { 389 return element; 390 } 391 392 public StructureDefinition getDefn() { 393 return defn; 394 } 395 396 public String getUrl() { 397 if (element == defn.getSnapshot().getElement().get(0)) 398 return defn.getUrl(); 399 else 400 return element.getSliceName(); 401 } 402 403 public ElementDefinition getExtensionValueDefinition() { 404 int i = defn.getSnapshot().getElement().indexOf(element)+1; 405 while (i < defn.getSnapshot().getElement().size()) { 406 ElementDefinition ed = defn.getSnapshot().getElement().get(i); 407 if (ed.getPath().equals(element.getPath())) 408 return null; 409 if (ed.getPath().startsWith(element.getPath()+".value") && !ed.hasSlicing()) 410 return ed; 411 i++; 412 } 413 return null; 414 } 415 } 416 417 private static final boolean COPY_BINDING_EXTENSIONS = false; 418 private static final boolean DONT_DO_THIS = false; 419 420 private boolean debug; 421 // note that ProfileUtilities are used re-entrantly internally, so nothing with process state can be here 422 private final IWorkerContext context; 423 private FHIRPathEngine fpe; 424 private List<ValidationMessage> messages = new ArrayList<ValidationMessage>(); 425 private List<String> snapshotStack = new ArrayList<String>(); 426 private ProfileKnowledgeProvider pkp; 427// private boolean igmode; 428 private ValidationOptions terminologyServiceOptions = new ValidationOptions(FhirPublication.R5); 429 private boolean newSlicingProcessing; 430 private String defWebRoot; 431 private boolean autoFixSliceNames; 432 private XVerExtensionManager xver; 433 private boolean wantFixDifferentialFirstElementType; 434 private Set<String> masterSourceFileNames; 435 private Set<String> localFileNames; 436 private Map<String, SourcedChildDefinitions> childMapCache = new HashMap<>(); 437 private AllowUnknownProfile allowUnknownProfile = AllowUnknownProfile.ALL_TYPES; 438 private MappingMergeModeOption mappingMergeMode = MappingMergeModeOption.APPEND; 439 private boolean forPublication; 440 private List<StructureDefinition> obligationProfiles = new ArrayList<>(); 441 private boolean wantThrowExceptions; 442 private List<String> suppressedMappings= new ArrayList<>(); 443 444 public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp, FHIRPathEngine fpe) { 445 super(); 446 this.context = context; 447 if (messages != null) { 448 this.messages = messages; 449 } else { 450 wantThrowExceptions = true; 451 } 452 this.pkp = pkp; 453 454 this.fpe = fpe; 455 if (context != null && this.fpe == null) { 456 this.fpe = new FHIRPathEngine(context, this); 457 } 458 } 459 460 public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp) { 461 super(); 462 this.context = context; 463 if (messages != null) { 464 this.messages = messages; 465 } else { 466 wantThrowExceptions = true; 467 } 468 this.pkp = pkp; 469 if (context != null) { 470 this.fpe = new FHIRPathEngine(context, this); 471 } 472 } 473 474 public boolean isWantFixDifferentialFirstElementType() { 475 return wantFixDifferentialFirstElementType; 476 } 477 478 public void setWantFixDifferentialFirstElementType(boolean wantFixDifferentialFirstElementType) { 479 this.wantFixDifferentialFirstElementType = wantFixDifferentialFirstElementType; 480 } 481 482 public boolean isAutoFixSliceNames() { 483 return autoFixSliceNames; 484 } 485 486 public ProfileUtilities setAutoFixSliceNames(boolean autoFixSliceNames) { 487 this.autoFixSliceNames = autoFixSliceNames; 488 return this; 489 } 490 491 public SourcedChildDefinitions getChildMap(StructureDefinition profile, ElementDefinition element, boolean chaseTypes) throws DefinitionException { 492 return getChildMap(profile, element, chaseTypes, null); 493 } 494 public SourcedChildDefinitions getChildMap(StructureDefinition profile, ElementDefinition element, boolean chaseTypes, String type) throws DefinitionException { 495 String cacheKey = "cm."+profile.getVersionedUrl()+"#"+(element.hasId() ? element.getId() : element.getPath())+"."+chaseTypes; 496 if (childMapCache.containsKey(cacheKey)) { 497 return childMapCache.get(cacheKey); 498 } 499 StructureDefinition src = profile; 500 List<ElementDefinition> res = new ArrayList<ElementDefinition>(); 501 List<ElementDefinition> elements = profile.getSnapshot().getElement(); 502 int iOffs = elements.indexOf(element) + 1; 503 boolean walksIntoElement = elements.size() > iOffs && elements.get(iOffs).getPath().startsWith(element.getPath()); 504 if (element.getContentReference() != null && !walksIntoElement) { 505 List<ElementDefinition> list = null; 506 String id = null; 507 if (element.getContentReference().startsWith("#")) { 508 // internal reference 509 id = element.getContentReference().substring(1); 510 list = profile.getSnapshot().getElement(); 511 } else if (element.getContentReference().contains("#")) { 512 // external reference 513 String ref = element.getContentReference(); 514 StructureDefinition sd = context.fetchResource(StructureDefinition.class, ref.substring(0, ref.indexOf("#")), profile); 515 if (sd == null) { 516 throw new DefinitionException("unable to process contentReference '"+element.getContentReference()+"' on element '"+element.getId()+"'"); 517 } 518 src = sd; 519 list = sd.getSnapshot().getElement(); 520 id = ref.substring(ref.indexOf("#")+1); 521 } else { 522 throw new DefinitionException("unable to process contentReference '"+element.getContentReference()+"' on element '"+element.getId()+"'"); 523 } 524 525 for (ElementDefinition e : list) { 526 if (id.equals(e.getId())) 527 return getChildMap(src, e, true); 528 } 529 throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_NAME_REFERENCE__AT_PATH_, element.getContentReference(), element.getPath())); 530 531 } else { 532 String path = element.getPath(); 533 for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) { 534 ElementDefinition e = elements.get(index); 535 if (e.getPath().startsWith(path + ".")) { 536 // We only want direct children, not all descendants 537 if (!e.getPath().substring(path.length()+1).contains(".")) 538 res.add(e); 539 } else 540 break; 541 } 542 if (res.isEmpty() && chaseTypes) { 543 // we've got no in-line children. Some consumers of this routine will figure this out for themselves but most just want to walk into 544 // the type children. 545 src = null; 546 if (type != null) { 547 src = context.fetchTypeDefinition(type); 548 } else if (element.getType().isEmpty()) { 549 throw new DefinitionException("No defined children and no type information on element '"+element.getId()+"'"); 550 } else if (element.getType().size() > 1) { 551 // this is a problem. There's two separate but related issues 552 // the first is what's going on here - the profile has walked into an element without fixing the type 553 // this might be ok - maybe it's just going to constrain extensions for all types, though this is generally a bad idea 554 // but if that's all it's doing, we'll just pretend we have an element. Only, it's not really an element so that might 555 // blow up on us later in mystifying ways. We'll have to wear it though, because there's profiles out there that do this 556 // the second problem is whether this should be some common descendent of Element - I'm not clear about that 557 // left as a problem for the future. 558 // 559 // this is what the code was prior to 2025-08-27: 560 // throw new DefinitionException("No defined children and multiple possible types '"+element.typeSummary()+"' on element '"+element.getId()+"'"); 561 src = context.fetchTypeDefinition("Element"); 562 } else if (element.getType().get(0).getProfile().size() > 1) { 563 throw new DefinitionException("No defined children and multiple possible type profiles '"+element.typeSummary()+"' on element '"+element.getId()+"'"); 564 } else if (element.getType().get(0).hasProfile()) { 565 src = context.fetchResource(StructureDefinition.class, element.getType().get(0).getProfile().get(0).getValue()); 566 if (src == null) { 567 throw new DefinitionException("No defined children and unknown type profile '"+element.typeSummary()+"' on element '"+element.getId()+"'"); 568 } 569 } else { 570 src = context.fetchTypeDefinition(element.getType().get(0).getWorkingCode()); 571 if (src == null) { 572 throw new DefinitionException("No defined children and unknown type '"+element.typeSummary()+"' on element '"+element.getId()+"'"); 573 } 574 } 575 SourcedChildDefinitions scd = getChildMap(src, src.getSnapshot().getElementFirstRep(), false); 576 res = scd.list; 577 } 578 SourcedChildDefinitions result = new SourcedChildDefinitions(src, res); 579 childMapCache.put(cacheKey, result); 580 return result; 581 } 582 } 583 584 585 public List<ElementDefinition> getSliceList(StructureDefinition profile, ElementDefinition element) throws DefinitionException { 586 if (!element.hasSlicing()) 587 throw new Error(context.formatMessage(I18nConstants.GETSLICELIST_SHOULD_ONLY_BE_CALLED_WHEN_THE_ELEMENT_HAS_SLICING)); 588 589 List<ElementDefinition> res = new ArrayList<ElementDefinition>(); 590 List<ElementDefinition> elements = profile.getSnapshot().getElement(); 591 String path = element.getPath(); 592 int start = findElementIndex(elements, element); 593 for (int index = start + 1; index < elements.size(); index++) { 594 ElementDefinition e = elements.get(index); 595 if (e.getPath().startsWith(path + ".") || e.getPath().equals(path)) { 596 // We want elements with the same path (until we hit an element that doesn't start with the same path) 597 if (e.getPath().equals(element.getPath())) 598 res.add(e); 599 } else 600 break; 601 } 602 return res; 603 } 604 605 606 private int findElementIndex(List<ElementDefinition> elements, ElementDefinition element) { 607 int res = elements.indexOf(element); 608 if (res == -1) { 609 for (int i = 0; i < elements.size(); i++) { 610 Element t = elements.get(i); 611 if (t.getId().equals(element.getId())) { 612 res = i; 613 } 614 } 615 } 616 return res; 617 } 618 619 /** 620 * Given a Structure, navigate to the element given by the path and return the direct children of that element 621 * 622 * @param profile The structure to navigate into 623 * @param path The path of the element within the structure to get the children for 624 * @return A List containing the element children (all of them are Elements) 625 */ 626 public List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id) { 627 return getChildList(profile, path, id, false); 628 } 629 630 public List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id, boolean diff) { 631 return getChildList(profile, path, id, diff, false); 632 } 633 634 public List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id, boolean diff, boolean refs) { 635 List<ElementDefinition> res = new ArrayList<ElementDefinition>(); 636 637 boolean capturing = id==null; 638 if (id==null && !path.contains(".")) 639 capturing = true; 640 641 List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement(); 642 for (ElementDefinition e : list) { 643 if (e == null) 644 throw new Error(context.formatMessage(I18nConstants.ELEMENT__NULL_, profile.getUrl())); 645// if (e.getId() == null) // this is sort of true, but in some corner cases it's not, and in those cases, we don't care 646// throw new Error(context.formatMessage(I18nConstants.ELEMENT_ID__NULL__ON_, e.toString(), profile.getUrl())); 647 648 if (!capturing && id!=null && e.getId().equals(id)) { 649 capturing = true; 650 } 651 652 // If our element is a slice, stop capturing children as soon as we see the next slice 653 if (capturing && e.hasId() && id!= null && !e.getId().equals(id) && e.getPath().equals(path)) 654 break; 655 656 if (capturing) { 657 String p = e.getPath(); 658 659 if (refs && !Utilities.noString(e.getContentReference()) && path.startsWith(p)) { 660 if (path.length() > p.length()) { 661 return getChildList(profile, e.getContentReference()+"."+path.substring(p.length()+1), null, diff); 662 } else if (e.getContentReference().startsWith("#")) { 663 return getChildList(profile, e.getContentReference().substring(1), null, diff); 664 } else if (e.getContentReference().contains("#")) { 665 String url = e.getContentReference().substring(0, e.getContentReference().indexOf("#")); 666 StructureDefinition sd = context.fetchResource(StructureDefinition.class, url, profile); 667 if (sd == null) { 668 throw new DefinitionException("Unable to find Structure "+url); 669 } 670 return getChildList(sd, e.getContentReference().substring(e.getContentReference().indexOf("#")+1), null, diff); 671 } else { 672 return getChildList(profile, e.getContentReference(), null, diff); 673 } 674 675 } else if (p.startsWith(path+".") && !p.equals(path)) { 676 String tail = p.substring(path.length()+1); 677 if (!tail.contains(".")) { 678 res.add(e); 679 } 680 } 681 } 682 } 683 684 return res; 685 } 686 687 public List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element, boolean diff, boolean refs) { 688 return getChildList(structure, element.getPath(), element.getId(), diff, refs); 689 } 690 691 public List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element, boolean diff) { 692 return getChildList(structure, element.getPath(), element.getId(), diff); 693 } 694 695 public List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element) { 696 if (element.hasContentReference()) { 697 ElementDefinition target = element; 698 for (ElementDefinition t : structure.getSnapshot().getElement()) { 699 if (t.getId().equals(element.getContentReference().substring(1))) { 700 target = t; 701 } 702 } 703 return getChildList(structure, target.getPath(), target.getId(), false); 704 } else { 705 return getChildList(structure, element.getPath(), element.getId(), false); 706 } 707 } 708 709 /** 710 * Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile 711 * 712 * @param base - the base structure on which the differential will be applied 713 * @param derived - the differential to apply to the base 714 * @param url - where the base has relative urls for profile references, these need to be converted to absolutes by prepending this URL (e.g. the canonical URL) 715 * @param webUrl - where the base has relative urls in markdown, these need to be converted to absolutes by prepending this URL (this is not the same as the canonical URL) 716 * @return 717 * @throws FHIRException 718 * @throws DefinitionException 719 * @throws Exception 720 */ 721 public void generateSnapshot(StructureDefinition base, StructureDefinition derived, String url, String webUrl, String profileName) throws DefinitionException, FHIRException { 722 if (base == null) { 723 throw new DefinitionException(context.formatMessage(I18nConstants.NO_BASE_PROFILE_PROVIDED)); 724 } 725 if (derived == null) { 726 throw new DefinitionException(context.formatMessage(I18nConstants.NO_DERIVED_STRUCTURE_PROVIDED)); 727 } 728 checkNotGenerating(base, "Base for generating a snapshot for the profile "+derived.getUrl()); 729 checkNotGenerating(derived, "Focus for generating a snapshot"); 730 731 if (!base.hasType()) { 732 throw new DefinitionException(context.formatMessage(I18nConstants.BASE_PROFILE__HAS_NO_TYPE, base.getUrl())); 733 } 734 if (!derived.hasType()) { 735 throw new DefinitionException(context.formatMessage(I18nConstants.DERIVED_PROFILE__HAS_NO_TYPE, derived.getUrl())); 736 } 737 if (!derived.hasDerivation()) { 738 throw new DefinitionException(context.formatMessage(I18nConstants.DERIVED_PROFILE__HAS_NO_DERIVATION_VALUE_AND_SO_CANT_BE_PROCESSED, derived.getUrl())); 739 } 740 if (!base.getType().equals(derived.getType()) && derived.getDerivation() == TypeDerivationRule.CONSTRAINT) { 741 throw new DefinitionException(context.formatMessage(I18nConstants.BASE__DERIVED_PROFILES_HAVE_DIFFERENT_TYPES____VS___, base.getUrl(), base.getType(), derived.getUrl(), derived.getType())); 742 } 743 if (!base.hasSnapshot()) { 744 StructureDefinition sdb = context.fetchResource(StructureDefinition.class, base.getBaseDefinition()); 745 if (sdb == null) 746 throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_BASE__FOR_, base.getBaseDefinition(), base.getUrl())); 747 checkNotGenerating(sdb, "an extension base"); 748 generateSnapshot(sdb, base, base.getUrl(), (sdb.hasWebPath()) ? Utilities.extractBaseUrl(sdb.getWebPath()) : webUrl, base.getName()); 749 } 750 fixTypeOfResourceId(base); 751 if (base.hasExtension(ExtensionDefinitions.EXT_TYPE_PARAMETER)) { 752 checkTypeParameters(base, derived); 753 } 754 755 if (snapshotStack.contains(derived.getUrl())) { 756 throw new DefinitionException(context.formatMessage(I18nConstants.CIRCULAR_SNAPSHOT_REFERENCES_DETECTED_CANNOT_GENERATE_SNAPSHOT_STACK__, snapshotStack.toString())); 757 } 758 derived.setGeneratingSnapshot(true); 759 snapshotStack.add(derived.getUrl()); 760 boolean oldCopyUserData = Base.isCopyUserData(); 761 Base.setCopyUserData(true); 762 try { 763 764 if (!Utilities.noString(webUrl) && !webUrl.endsWith("/")) 765 webUrl = webUrl + '/'; 766 767 if (defWebRoot == null) 768 defWebRoot = webUrl; 769 derived.setSnapshot(new StructureDefinitionSnapshotComponent()); 770 771 try { 772 checkDifferential(derived.getDifferential().getElement(), derived.getTypeName(), derived.getUrl()); 773 checkDifferentialBaseType(derived); 774 775 log.debug("Differential: "); 776 int debugPadding = 0; 777 for (ElementDefinition ed : derived.getDifferential().getElement()) { 778 log.debug(" "+Utilities.padLeft(Integer.toString(debugPadding), ' ', 3)+" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed)); 779 debugPadding++; 780 } 781 log.debug("Snapshot: "); 782 debugPadding = 0; 783 for (ElementDefinition ed : base.getSnapshot().getElement()) { 784 log.debug(" "+Utilities.padLeft(Integer.toString(debugPadding), ' ', 3)+" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed)); 785 debugPadding++; 786 } 787 788 789 copyInheritedExtensions(base, derived, webUrl); 790 791 findInheritedObligationProfiles(derived); 792 // so we have two lists - the base list, and the differential list 793 // the differential list is only allowed to include things that are in the base list, but 794 // is allowed to include them multiple times - thereby slicing them 795 796 // our approach is to walk through the base list, and see whether the differential 797 // says anything about them. 798 // we need a diff cursor because we can only look ahead, in the bound scoped by longer paths 799 800 801 for (ElementDefinition e : derived.getDifferential().getElement()) 802 e.clearUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT); 803 804 // we actually delegate the work to a subroutine so we can re-enter it with a different cursors 805 StructureDefinitionDifferentialComponent diff = cloneDiff(derived.getDifferential()); // we make a copy here because we're sometimes going to hack the differential while processing it. Have to migrate user data back afterwards 806 new SnapshotGenerationPreProcessor(this).process(diff, derived); 807 808 StructureDefinitionSnapshotComponent baseSnapshot = base.getSnapshot(); 809 if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 810 String derivedType = derived.getTypeName(); 811 812 baseSnapshot = cloneSnapshot(baseSnapshot, base.getTypeName(), derivedType); 813 } 814 // if (derived.getId().equals("2.16.840.1.113883.10.20.22.2.1.1")) { 815 // debug = true; 816 // } 817 818 MappingAssistant mappingDetails = new MappingAssistant(mappingMergeMode, base, derived, context.getVersion(), suppressedMappings); 819 820 ProfilePathProcessor.processPaths(this, base, derived, url, webUrl, diff, baseSnapshot, mappingDetails); 821 822 checkGroupConstraints(derived); 823 if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 824 int i = 0; 825 for (ElementDefinition e : diff.getElement()) { 826 if (!e.hasUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT) && e.getPath().contains(".")) { 827 ElementDefinition existing = getElementInCurrentContext(e.getPath(), derived.getSnapshot().getElement()); 828 if (existing != null) { 829 updateFromDefinition(existing, e, profileName, false, url, base, derived, "StructureDefinition.differential.element["+i+"]", mappingDetails, false); 830 } else { 831 ElementDefinition outcome = updateURLs(url, webUrl, e.copy(), true); 832 e.setUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT, outcome); 833 markExtensions(outcome, true, derived); 834 derived.getSnapshot().addElement(outcome); 835 if (walksInto(diff.getElement(), e)) { 836 if (e.getType().size() > 1) { 837 throw new DefinitionException("Unsupported scenario: specialization walks into multiple types at "+e.getId()); 838 } else { 839 addInheritedElementsForSpecialization(derived.getSnapshot(), outcome, outcome.getTypeFirstRep().getWorkingCode(), outcome.getPath(), url, webUrl); 840 } 841 } 842 } 843 } 844 i++; 845 } 846 } 847 848 for (int i = 0; i < derived.getSnapshot().getElement().size(); i++) { 849 ElementDefinition ed = derived.getSnapshot().getElement().get(i); 850 if (ed.getType().size() > 1) { 851 List<TypeRefComponent> toRemove = new ArrayList<ElementDefinition.TypeRefComponent>(); 852 for (TypeRefComponent tr : ed.getType()) { 853 ElementDefinition typeSlice = findTypeSlice(derived.getSnapshot().getElement(), i, ed.getPath(), tr.getWorkingCode()); 854 if (typeSlice != null && typeSlice.prohibited()) { 855 toRemove.add(tr); 856 } 857 } 858 ed.getType().removeAll(toRemove); 859 } 860 } 861 if (derived.getKind() != StructureDefinitionKind.LOGICAL && !derived.getSnapshot().getElementFirstRep().getType().isEmpty()) 862 throw new Error(context.formatMessage(I18nConstants.TYPE_ON_FIRST_SNAPSHOT_ELEMENT_FOR__IN__FROM_, derived.getSnapshot().getElementFirstRep().getPath(), derived.getUrl(), base.getUrl())); 863 mappingDetails.update(); 864 865 setIds(derived, false); 866 867 log.debug("Differential: "); 868 int debugPad = 0; 869 for (ElementDefinition ed : derived.getDifferential().getElement()) { 870 log.debug(" "+Utilities.padLeft(Integer.toString(debugPad), ' ', 3)+" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)); 871 debugPad++; 872 } 873 log.debug("Diff (processed): "); 874 debugPad = 0; 875 for (ElementDefinition ed : diff.getElement()) { 876 log.debug(" "+Utilities.padLeft(Integer.toString(debugPad), ' ', 3)+" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+ 877 " -> "+(destInfo(ed, derived.getSnapshot().getElement()))); 878 debugPad++; 879 } 880 log.debug("Snapshot: "); 881 debugPad = 0; 882 for (ElementDefinition ed : derived.getSnapshot().getElement()) { 883 log.debug(" "+Utilities.padLeft(Integer.toString(debugPad), ' ', 3)+" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed)); 884 debugPad++; 885 } 886 887 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 888 //Check that all differential elements have a corresponding snapshot element 889 int ce = 0; 890 int i = 0; 891 for (ElementDefinition e : diff.getElement()) { 892 if (!e.hasUserData(UserDataNames.SNAPSHOT_diff_source)) { 893 // was injected during preprocessing - this is ok 894 } else { 895 if (e.hasUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS)) 896 ((Base) e.getUserData(UserDataNames.SNAPSHOT_diff_source)).setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, e.getUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS)); 897 if (e.hasUserData(UserDataNames.SNAPSHOT_DERIVATION_POINTER)) 898 ((Base) e.getUserData(UserDataNames.SNAPSHOT_diff_source)).setUserData(UserDataNames.SNAPSHOT_DERIVATION_POINTER, e.getUserData(UserDataNames.SNAPSHOT_DERIVATION_POINTER)); 899 } 900 if (!e.hasUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT)) { 901 b.append(e.hasId() ? "id: "+e.getId() : "path: "+e.getPath()); 902 ce++; 903 if (e.hasId()) { 904 String msg = "No match found for "+e.getId()+" in the generated snapshot: check that the path and definitions are legal in the differential (including order)"; 905 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, "StructureDefinition.differential.element["+i+"]", msg, ValidationMessage.IssueSeverity.ERROR)); 906 } 907 } else { 908 ElementDefinition sed = (ElementDefinition) e.getUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT); 909 sed.setUserData(UserDataNames.SNAPSHOT_DERIVATION_DIFF, e); // note: this means diff/snapshot are cross-linked 910 } 911 i++; 912 } 913 if (!Utilities.noString(b.toString())) { 914 String msg = "The profile "+derived.getUrl()+" has "+ce+" "+Utilities.pluralize("element", ce)+" in the differential ("+b.toString()+") that don't have a matching element in the snapshot: check that the path and definitions are legal in the differential (including order)"; 915 916 log.debug("Error in snapshot generation: "+msg); 917 918 log.debug("Differential: "); 919 for (ElementDefinition ed : derived.getDifferential().getElement()) 920 log.debug(" "+ed.getId()+" = "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed)); 921 log.debug("Snapshot: "); 922 for (ElementDefinition ed : derived.getSnapshot().getElement()) 923 log.debug(" "+ed.getId()+" = "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed)); 924 925 926 handleError(url, msg); 927 } 928 // hack around a problem in R4 definitions (somewhere?) 929 for (ElementDefinition ed : derived.getSnapshot().getElement()) { 930 for (ElementDefinitionMappingComponent mm : ed.getMapping()) { 931 if (mm.hasMap()) { 932 mm.setMap(mm.getMap().trim()); 933 } 934 } 935 for (ElementDefinitionConstraintComponent s : ed.getConstraint()) { 936 if (s.hasSource()) { 937 String ref = s.getSource(); 938 if (!Utilities.isAbsoluteUrl(ref)) { 939 if (ref.contains(".")) { 940 s.setSource("http://hl7.org/fhir/StructureDefinition/"+ref.substring(0, ref.indexOf("."))+"#"+ref); 941 } else { 942 s.setSource("http://hl7.org/fhir/StructureDefinition/"+ref); 943 } 944 } 945 } 946 } 947 } 948 if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 949 for (ElementDefinition ed : derived.getSnapshot().getElement()) { 950 if (!ed.hasBase()) { 951 ed.getBase().setPath(ed.getPath()).setMin(ed.getMin()).setMax(ed.getMax()); 952 } 953 } 954 } 955 // check slicing is ok while we're at it. and while we're doing this. update the minimum count if we need to 956 String tn = derived.getType(); 957 if (tn.contains("/")) { 958 tn = tn.substring(tn.lastIndexOf("/")+1); 959 } 960 Map<String, ElementDefinitionCounter> slices = new HashMap<>(); 961 i = 0; 962 for (ElementDefinition ed : derived.getSnapshot().getElement()) { 963 if (ed.hasSlicing()) { 964 slices.put(ed.getPath(), new ElementDefinitionCounter(ed, i)); 965 } else { 966 Set<String> toRemove = new HashSet<>(); 967 for (String s : slices.keySet()) { 968 if (Utilities.charCount(s, '.') >= Utilities.charCount(ed.getPath(), '.') && !s.equals(ed.getPath())) { 969 toRemove.add(s); 970 } 971 } 972 for (String s : toRemove) { 973 ElementDefinitionCounter slice = slices.get(s); 974 int count = slice.checkMin(); 975 boolean repeats = !"1".equals(slice.getFocus().getBase().getMax()); // type slicing if repeats = 1 976 if (count > -1 && repeats) { 977 if (slice.getFocus().hasUserData(UserDataNames.SNAPSHOT_auto_added_slicing)) { 978 slice.getFocus().setMin(count); 979 } else { 980 String msg = "The slice definition for "+slice.getFocus().getId()+" has a minimum of "+slice.getFocus().getMin()+" but the slices add up to a minimum of "+count; 981 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, 982 "StructureDefinition.snapshot.element["+slice.getIndex()+"]", msg, forPublication ? ValidationMessage.IssueSeverity.ERROR : ValidationMessage.IssueSeverity.INFORMATION).setIgnorableError(true)); 983 } 984 } 985 count = slice.checkMax(); 986 if (count > -1 && repeats) { 987 String msg = "The slice definition for "+slice.getFocus().getId()+" has a maximum of "+slice.getFocus().getMax()+" but the slices add up to a maximum of "+count+". Check that this is what is intended"; 988 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, 989 "StructureDefinition.snapshot.element["+slice.getIndex()+"]", msg, ValidationMessage.IssueSeverity.INFORMATION)); 990 } 991 if (!slice.checkMinMax()) { 992 String msg = "The slice definition for "+slice.getFocus().getId()+" has a maximum of "+slice.getFocus().getMax()+" which is less than the minimum of "+slice.getFocus().getMin(); 993 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, 994 "StructureDefinition.snapshot.element["+slice.getIndex()+"]", msg, ValidationMessage.IssueSeverity.WARNING)); 995 } 996 slices.remove(s); 997 } 998 } 999 if (ed.getPath().contains(".") && !ed.getPath().startsWith(tn+".")) { 1000 throw new Error("The element "+ed.getId()+" in the profile '"+derived.getVersionedUrl()+" doesn't have the right path (should start with "+tn+"."); 1001 } 1002 if (ed.hasSliceName() && !slices.containsKey(ed.getPath())) { 1003 String msg = "The element "+ed.getId()+" launches straight into slicing without the slicing being set up properly first"; 1004 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, 1005 "StructureDefinition.snapshot.element["+i+"]", msg, ValidationMessage.IssueSeverity.ERROR).setIgnorableError(true)); 1006 } 1007 if (ed.hasSliceName() && slices.containsKey(ed.getPath())) { 1008 if (!slices.get(ed.getPath()).count(ed, ed.getSliceName())) { 1009 String msg = "Duplicate slice name "+ed.getSliceName()+" on "+ed.getId()+" (["+i+"])"; 1010 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, 1011 "StructureDefinition.snapshot.element["+i+"]", msg, ValidationMessage.IssueSeverity.ERROR).setIgnorableError(true)); 1012 } 1013 } 1014 i++; 1015 } 1016 1017 i = 0; 1018 // last, check for wrong profiles or target profiles, or unlabeled extensions 1019 for (ElementDefinition ed : derived.getSnapshot().getElement()) { 1020 for (TypeRefComponent t : ed.getType()) { 1021 for (UriType u : t.getProfile()) { 1022 StructureDefinition sd = context.fetchResource(StructureDefinition.class, u.getValue(), derived); 1023 if (sd == null) { 1024 if (makeXVer().matchingUrl(u.getValue()) && xver.status(u.getValue()) == XVerExtensionStatus.Valid) { 1025 sd = xver.getDefinition(u.getValue()); 1026 } 1027 } 1028 if (sd == null) { 1029 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, 1030 "StructureDefinition.snapshot.element["+i+"]", "The type of profile "+u.getValue()+" cannot be checked as the profile is not known", IssueSeverity.WARNING)); 1031 } else { 1032 String wt = t.getWorkingCode(); 1033 if (ed.getPath().equals("Bundle.entry.response.outcome")) { 1034 wt = "OperationOutcome"; 1035 } 1036 String tt = sd.getType(); 1037 boolean elementProfile = u.hasExtension(ExtensionDefinitions.EXT_PROFILE_ELEMENT); 1038 if (elementProfile) { 1039 ElementDefinition edt = sd.getSnapshot().getElementById(u.getExtensionString(ExtensionDefinitions.EXT_PROFILE_ELEMENT)); 1040 if (edt == null) { 1041 handleError(url, "The profile "+u.getValue()+" has type "+sd.getType()+" which is not consistent with the stated type "+wt); 1042 } else { 1043 tt = edt.typeSummary(); 1044 } 1045 } 1046 if (!tt.equals(wt)) { 1047 boolean ok = !elementProfile && isCompatibleType(wt, sd); 1048 if (!ok) { 1049 handleError(url, "The profile "+u.getValue()+" has type "+sd.getType()+" which is not consistent with the stated type "+wt); 1050 } 1051 } 1052 } 1053 } 1054 } 1055 i++; 1056 } 1057 } catch (Exception e) { 1058 log.error("Exception generating snapshot for "+derived.getVersionedUrl()+": " +e.getMessage()); 1059 log.debug(e.getMessage(), e); 1060 // if we had an exception generating the snapshot, make sure we don't leave any half generated snapshot behind 1061 derived.setSnapshot(null); 1062 derived.setGeneratingSnapshot(false); 1063 throw e; 1064 } 1065 } finally { 1066 Base.setCopyUserData(oldCopyUserData); 1067 derived.setGeneratingSnapshot(false); 1068 snapshotStack.remove(derived.getUrl()); 1069 } 1070 if (base.getVersion() != null) { 1071 derived.getSnapshot().addExtension(ExtensionDefinitions.EXT_VERSION_BASE, new StringType(base.getVersion())); 1072 } 1073 derived.setGeneratedSnapshot(true); 1074 //derived.setUserData(UserDataNames.SNAPSHOT_GENERATED, true); // used by the publisher 1075 derived.setUserData(UserDataNames.SNAPSHOT_GENERATED_MESSAGES, messages); // used by the publisher 1076 } 1077 1078 1079 private String destInfo(ElementDefinition ed, List<ElementDefinition> snapshot) { 1080 ElementDefinition sed = (ElementDefinition) ed.getUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT); 1081 if (sed == null) { 1082 return "(null)"; 1083 } else { 1084 int index = snapshot.indexOf(sed); 1085 return ""+index+" "+sed.getId(); 1086 } 1087 } 1088 1089 private ElementDefinition findTypeSlice(List<ElementDefinition> list, int i, String path, String typeCode) { 1090 for (int j = i+1; j < list.size(); j++) { 1091 ElementDefinition ed = list.get(j); 1092 if (pathMatches(path, ed) && typeMatches(ed, typeCode)) { 1093 return ed; 1094 } 1095 } 1096 return null; 1097 } 1098 1099 private boolean pathMatches(String path, ElementDefinition ed) { 1100 String p = ed.getPath(); 1101 if (path.equals(p)) { 1102 return true; 1103 } 1104 if (path.endsWith("[x]")) { // it should 1105 path = path.substring(0, path.length()-3); 1106 if (p.startsWith(path) && p.length() > path.length() && !p.substring(path.length()).contains(".")) { 1107 return true; 1108 } 1109 } 1110 return false; 1111 } 1112 1113 private boolean typeMatches(ElementDefinition ed, String typeCode) { 1114 return ed.getType().size() == 1 && typeCode.equals(ed.getTypeFirstRep().getWorkingCode()); 1115 } 1116 1117 private void checkTypeParameters(StructureDefinition base, StructureDefinition derived) { 1118 String bt = ExtensionUtilities.readStringSubExtension(base, ExtensionDefinitions.EXT_TYPE_PARAMETER, "type"); 1119 if (!derived.hasExtension(ExtensionDefinitions.EXT_TYPE_PARAMETER)) { 1120 throw new DefinitionException(context.formatMessage(I18nConstants.SD_TYPE_PARAMETER_MISSING, base.getVersionedUrl(), bt, derived.getVersionedUrl())); 1121 } 1122 String dt = ExtensionUtilities.readStringSubExtension(derived, ExtensionDefinitions.EXT_TYPE_PARAMETER, "type"); 1123 StructureDefinition bsd = context.fetchTypeDefinition(bt); 1124 StructureDefinition dsd = context.fetchTypeDefinition(dt); 1125 if (bsd == null) { 1126 throw new DefinitionException(context.formatMessage(I18nConstants.SD_TYPE_PARAMETER_UNKNOWN, base.getVersionedUrl(), bt)); 1127 } 1128 if (dsd == null) { 1129 throw new DefinitionException(context.formatMessage(I18nConstants.SD_TYPE_PARAMETER_UNKNOWN, derived.getVersionedUrl(), dt)); 1130 } 1131 StructureDefinition t = dsd; 1132 while (t != bsd && t != null) { 1133 t = context.fetchResource(StructureDefinition.class, t.getBaseDefinition()); 1134 } 1135 if (t == null) { 1136 throw new DefinitionException(context.formatMessage(I18nConstants.SD_TYPE_PARAMETER_INVALID, base.getVersionedUrl(), bt, derived.getVersionedUrl(), dt)); 1137 } 1138 } 1139 1140 private XVerExtensionManager makeXVer() { 1141 if (xver == null) { 1142 xver = XVerExtensionManagerFactory.createExtensionManager(context); 1143 } 1144 return xver; 1145 } 1146 1147 private ElementDefinition getElementInCurrentContext(String path, List<ElementDefinition> list) { 1148 for (int i = list.size() -1; i >= 0; i--) { 1149 ElementDefinition t = list.get(i); 1150 if (t.getPath().equals(path)) { 1151 return t; 1152 } else if (!path.startsWith(head(t.getPath()))) { 1153 return null; 1154 } 1155 } 1156 return null; 1157 } 1158 1159 private String head(String path) { 1160 return path.contains(".") ? path.substring(0, path.lastIndexOf(".")+1) : path; 1161 } 1162 1163 private void findInheritedObligationProfiles(StructureDefinition derived) { 1164 List<Extension> list = derived.getExtensionsByUrl(ExtensionDefinitions.EXT_OBLIGATION_INHERITS_NEW, ExtensionDefinitions.EXT_OBLIGATION_INHERITS_OLD); 1165 for (Extension ext : list) { 1166 StructureDefinition op = context.fetchResource(StructureDefinition.class, ext.getValueCanonicalType().primitiveValue()); 1167 if (op != null && ExtensionUtilities.readBoolExtension(op, ExtensionDefinitions.EXT_OBLIGATION_PROFILE_FLAG_NEW, ExtensionDefinitions.EXT_OBLIGATION_PROFILE_FLAG_OLD)) { 1168 if (derived.getBaseDefinitionNoVersion().equals(op.getBaseDefinitionNoVersion())) { 1169 obligationProfiles.add(op); 1170 } 1171 } 1172 } 1173 } 1174 1175 private void handleError(String url, String msg) { 1176 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url, msg, ValidationMessage.IssueSeverity.ERROR)); 1177 } 1178 1179 private void addMessage(ValidationMessage msg) { 1180 messages.add(msg); 1181 if (msg.getLevel() == IssueSeverity.ERROR && wantThrowExceptions) { 1182 throw new DefinitionException(msg.getMessage()); 1183 } 1184 } 1185 1186 private void copyInheritedExtensions(StructureDefinition base, StructureDefinition derived, String webUrl) { 1187 for (Extension ext : base.getExtension()) { 1188 if (!Utilities.existsInList(ext.getUrl(), NON_INHERITED_ED_URLS)) { 1189 String action = getExtensionAction(ext.getUrl()); 1190 if (!"ignore".equals(action)) { 1191 boolean exists = derived.hasExtension(ext.getUrl()); 1192 if ("add".equals(action) || !exists) { 1193 Extension next = ext.copy(); 1194 if (next.hasValueMarkdownType()) { 1195 MarkdownType md = next.getValueMarkdownType(); 1196 md.setValue(processRelativeUrls(md.getValue(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, false)); 1197 } 1198 derived.getExtension().add(next); 1199 } else if ("overwrite".equals(action)) { 1200 Extension oext = derived.getExtensionByUrl(ext.getUrl()); 1201 Extension next = ext.copy(); 1202 if (next.hasValueMarkdownType()) { 1203 MarkdownType md = next.getValueMarkdownType(); 1204 md.setValue(processRelativeUrls(md.getValue(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, false)); 1205 } 1206 oext.setValue(next.getValue()); 1207 } 1208 } 1209 } 1210 } 1211 } 1212 1213 private String getExtensionAction(String url) { 1214 StructureDefinition sd = context.fetchResourceRaw(StructureDefinition.class, url); 1215 if (sd != null && sd.hasExtension(ExtensionDefinitions.EXT_SNAPSHOT_BEHAVIOR)) { 1216 return ExtensionUtilities.readStringExtension(sd, ExtensionDefinitions.EXT_SNAPSHOT_BEHAVIOR); 1217 } 1218 return "defer"; 1219 } 1220 1221 private void addInheritedElementsForSpecialization(StructureDefinitionSnapshotComponent snapshot, ElementDefinition focus, String type, String path, String url, String weburl) { 1222 StructureDefinition sd = context.fetchTypeDefinition(type); 1223 if (sd != null) { 1224 // don't do this. should already be in snapshot ... addInheritedElementsForSpecialization(snapshot, focus, sd.getBaseDefinition(), path, url, weburl); 1225 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 1226 if (ed.getPath().contains(".")) { 1227 ElementDefinition outcome = updateURLs(url, weburl, ed.copy(), true); 1228 outcome.setPath(outcome.getPath().replace(sd.getTypeName(), path)); 1229 markExtensions(outcome, false, sd); 1230 snapshot.getElement().add(outcome); 1231 } else { 1232 focus.getConstraint().addAll(ed.getConstraint()); 1233 for (Extension ext : ed.getExtension()) { 1234 if (!Utilities.existsInList(ext.getUrl(), NON_INHERITED_ED_URLS) && !focus.hasExtension(ext.getUrl())) { 1235 focus.getExtension().add(markExtensionSource(ext.copy(), false, sd)); 1236 } 1237 } 1238 } 1239 } 1240 } 1241 } 1242 1243 private boolean walksInto(List<ElementDefinition> list, ElementDefinition ed) { 1244 int i = list.indexOf(ed); 1245 return (i < list.size() - 1) && list.get(i + 1).getPath().startsWith(ed.getPath()+"."); 1246 } 1247 1248 private void fixTypeOfResourceId(StructureDefinition base) { 1249 if (base.getKind() == StructureDefinitionKind.RESOURCE && (base.getFhirVersion() == null || VersionUtilities.isR4Plus(base.getFhirVersion().toCode()))) { 1250 fixTypeOfResourceId(base.getSnapshot().getElement()); 1251 fixTypeOfResourceId(base.getDifferential().getElement()); 1252 } 1253 } 1254 1255 private void fixTypeOfResourceId(List<ElementDefinition> list) { 1256 for (ElementDefinition ed : list) { 1257 if (ed.hasBase() && ed.getBase().getPath().equals("Resource.id")) { 1258 for (TypeRefComponent tr : ed.getType()) { 1259 tr.setCode("http://hl7.org/fhirpath/System.String"); 1260 tr.removeExtension(ExtensionDefinitions.EXT_FHIR_TYPE); 1261 ExtensionUtilities.addUrlExtension(tr, ExtensionDefinitions.EXT_FHIR_TYPE, "id"); 1262 } 1263 } 1264 } 1265 } 1266 1267 /** 1268 * Check if derived has the correct base type 1269 * 1270 * Clear first element of differential under certain conditions. 1271 * 1272 * @param derived 1273 * @throws Error 1274 */ 1275 private void checkDifferentialBaseType(StructureDefinition derived) throws Error { 1276 if (derived.hasDifferential() && !derived.getDifferential().getElementFirstRep().getPath().contains(".") && !derived.getDifferential().getElementFirstRep().getType().isEmpty()) { 1277 if (wantFixDifferentialFirstElementType && typeMatchesAncestor(derived.getDifferential().getElementFirstRep().getType(), derived.getBaseDefinitionNoVersion(), derived)) { 1278 derived.getDifferential().getElementFirstRep().getType().clear(); 1279 } else if (derived.getKind() != StructureDefinitionKind.LOGICAL) { 1280 throw new Error(context.formatMessage(I18nConstants.TYPE_ON_FIRST_DIFFERENTIAL_ELEMENT)); 1281 } 1282 } 1283 } 1284 1285 private boolean typeMatchesAncestor(List<TypeRefComponent> type, String baseDefinition, Resource src) { 1286 StructureDefinition sd = context.fetchResource(StructureDefinition.class, baseDefinition, src); 1287 return sd != null && type.size() == 1 && sd.getType().equals(type.get(0).getCode()); 1288 } 1289 1290 1291 private void checkGroupConstraints(StructureDefinition derived) { 1292 List<ElementDefinition> toRemove = new ArrayList<>(); 1293// List<ElementDefinition> processed = new ArrayList<>(); 1294 for (ElementDefinition element : derived.getSnapshot().getElement()) { 1295 if (!toRemove.contains(element) && !element.hasSlicing() && !"0".equals(element.getMax())) { 1296 checkForChildrenInGroup(derived, toRemove, element); 1297 } 1298 } 1299 derived.getSnapshot().getElement().removeAll(toRemove); 1300 } 1301 1302 private void checkForChildrenInGroup(StructureDefinition derived, List<ElementDefinition> toRemove, ElementDefinition element) throws Error { 1303 List<ElementDefinition> children = getChildren(derived, element); 1304 List<ElementChoiceGroup> groups = readChoices(element, children); 1305 for (ElementChoiceGroup group : groups) { 1306 String mandated = null; 1307 Set<String> names = new HashSet<>(); 1308 for (ElementDefinition ed : children) { 1309 String name = tail(ed.getPath()); 1310 if (names.contains(name)) { 1311 throw new Error("huh?"); 1312 } else { 1313 names.add(name); 1314 } 1315 if (group.getElements().contains(name)) { 1316 if (ed.getMin() == 1) { 1317 if (mandated == null) { 1318 mandated = name; 1319 } else { 1320 throw new Error("Error: there are two mandatory elements in "+derived.getUrl()+" when there can only be one: "+mandated+" and "+name); 1321 } 1322 } 1323 } 1324 } 1325 if (mandated != null) { 1326 for (ElementDefinition ed : children) { 1327 String name = tail(ed.getPath()); 1328 if (group.getElements().contains(name) && !mandated.equals(name)) { 1329 ed.setMax("0"); 1330 addAllChildren(derived, ed, toRemove); 1331 } 1332 } 1333 } 1334 } 1335 } 1336 1337 private List<ElementDefinition> getChildren(StructureDefinition derived, ElementDefinition element) { 1338 List<ElementDefinition> elements = derived.getSnapshot().getElement(); 1339 int index = elements.indexOf(element) + 1; 1340 String path = element.getPath()+"."; 1341 List<ElementDefinition> list = new ArrayList<>(); 1342 while (index < elements.size()) { 1343 ElementDefinition e = elements.get(index); 1344 String p = e.getPath(); 1345 if (p.startsWith(path) && !e.hasSliceName()) { 1346 if (!p.substring(path.length()).contains(".")) { 1347 list.add(e); 1348 } 1349 index++; 1350 } else { 1351 break; 1352 } 1353 } 1354 return list; 1355 } 1356 1357 private void addAllChildren(StructureDefinition derived, ElementDefinition element, List<ElementDefinition> toRemove) { 1358 List<ElementDefinition> children = getChildList(derived, element); 1359 for (ElementDefinition child : children) { 1360 toRemove.add(child); 1361 addAllChildren(derived, child, toRemove); 1362 } 1363 } 1364 1365 /** 1366 * Check that a differential is valid. 1367 * @param elements 1368 * @param type 1369 * @param url 1370 */ 1371 private void checkDifferential(List<ElementDefinition> elements, String type, String url) { 1372 boolean first = true; 1373 String t = urlTail(type); 1374 for (ElementDefinition ed : elements) { 1375 if (!ed.hasPath()) { 1376 throw new FHIRException(context.formatMessage(I18nConstants.NO_PATH_ON_ELEMENT_IN_DIFFERENTIAL_IN_, url)); 1377 } 1378 String p = ed.getPath(); 1379 if (p == null) { 1380 throw new FHIRException(context.formatMessage(I18nConstants.NO_PATH_VALUE_ON_ELEMENT_IN_DIFFERENTIAL_IN_, url)); 1381 } 1382 if (!((first && t.equals(p)) || p.startsWith(t+"."))) { 1383 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__MUST_START_WITH_, p, url, t, (first ? " (or be '"+t+"')" : ""))); 1384 } 1385 if (p.contains(".")) { 1386 // Element names (the parts of a path delineated by the '.' character) SHALL NOT contain whitespace (i.e. Unicode characters marked as whitespace) 1387 // Element names SHALL NOT contain the characters ,:;'"/|?!@#$%^&*()[]{} 1388 // Element names SHOULD not contain non-ASCII characters 1389 // Element names SHALL NOT exceed 64 characters in length 1390 String[] pl = p.split("\\."); 1391 for (String pp : pl) { 1392 if (pp.length() < 1) { 1393 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__NAME_PORTION_MISING_, p, url)); 1394 } 1395 if (pp.length() > 64) { 1396 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__NAME_PORTION_EXCEEDS_64_CHARS_IN_LENGTH, p, url)); 1397 } 1398 for (char ch : pp.toCharArray()) { 1399 if (Utilities.isWhitespace(ch)) { 1400 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__NO_UNICODE_WHITESPACE, p, url)); 1401 } 1402 if (Utilities.existsInList(ch, ',', ':', ';', '\'', '"', '/', '|', '?', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '{', '}')) { 1403 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__ILLEGAL_CHARACTER_, p, url, ch)); 1404 } 1405 if (ch < ' ' || ch > 'z') { 1406 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__ILLEGAL_CHARACTER_, p, url, ch)); 1407 } 1408 } 1409 if (pp.contains("[") || pp.contains("]")) { 1410 if (!pp.endsWith("[x]") || (pp.substring(0, pp.length()-3).contains("[") || (pp.substring(0, pp.length()-3).contains("]")))) { 1411 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__ILLEGAL_CHARACTERS_, p, url)); 1412 } 1413 } 1414 } 1415 } 1416 } 1417 } 1418 1419 1420 private boolean isCompatibleType(String base, StructureDefinition sdt) { 1421 StructureDefinition sdb = context.fetchTypeDefinition(base); 1422 if (sdb.getType().equals(sdt.getType())) { 1423 return true; 1424 } 1425 StructureDefinition sd = context.fetchTypeDefinition(sdt.getType()); 1426 while (sd != null) { 1427 if (sd.getType().equals(sdb.getType())) { 1428 return true; 1429 } 1430 if (sd.getUrl().equals(sdb.getUrl())) { 1431 return true; 1432 } 1433 sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); 1434 } 1435 return false; 1436 } 1437 1438 1439 private StructureDefinitionDifferentialComponent cloneDiff(StructureDefinitionDifferentialComponent source) { 1440 StructureDefinitionDifferentialComponent diff = new StructureDefinitionDifferentialComponent(); 1441 for (ElementDefinition sed : source.getElement()) { 1442 ElementDefinition ted = sed.copy(); 1443 diff.getElement().add(ted); 1444 ted.setUserData(UserDataNames.SNAPSHOT_diff_source, sed); 1445 } 1446 return diff; 1447 } 1448 1449 private StructureDefinitionSnapshotComponent cloneSnapshot(StructureDefinitionSnapshotComponent source, String baseType, String derivedType) { 1450 StructureDefinitionSnapshotComponent diff = new StructureDefinitionSnapshotComponent(); 1451 for (ElementDefinition sed : source.getElement()) { 1452 ElementDefinition ted = sed.copy(); 1453 ted.setId(ted.getId().replaceFirst(baseType,derivedType)); 1454 ted.setPath(ted.getPath().replaceFirst(baseType,derivedType)); 1455 diff.getElement().add(ted); 1456 } 1457 return diff; 1458 } 1459 1460 private String constraintSummary(ElementDefinition ed) { 1461 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1462 if (ed.hasPattern()) 1463 b.append("pattern="+ed.getPattern().fhirType()); 1464 if (ed.hasFixed()) 1465 b.append("fixed="+ed.getFixed().fhirType()); 1466 if (ed.hasConstraint()) 1467 b.append("constraints="+ed.getConstraint().size()); 1468 return b.toString(); 1469 } 1470 1471 1472 private String sliceSummary(ElementDefinition ed) { 1473 if (!ed.hasSlicing() && !ed.hasSliceName()) 1474 return ""; 1475 if (ed.hasSliceName()) 1476 return " (slicename = "+ed.getSliceName()+")"; 1477 1478 StringBuilder b = new StringBuilder(); 1479 boolean first = true; 1480 for (ElementDefinitionSlicingDiscriminatorComponent d : ed.getSlicing().getDiscriminator()) { 1481 if (first) 1482 first = false; 1483 else 1484 b.append("|"); 1485 b.append(d.getPath()); 1486 } 1487 return " (slicing by "+b.toString()+")"; 1488 } 1489 1490 1491// private String typeSummary(ElementDefinition ed) { 1492// StringBuilder b = new StringBuilder(); 1493// boolean first = true; 1494// for (TypeRefComponent tr : ed.getType()) { 1495// if (first) 1496// first = false; 1497// else 1498// b.append("|"); 1499// b.append(tr.getWorkingCode()); 1500// } 1501// return b.toString(); 1502// } 1503 1504 private String typeSummaryWithProfile(ElementDefinition ed) { 1505 StringBuilder b = new StringBuilder(); 1506 boolean first = true; 1507 for (TypeRefComponent tr : ed.getType()) { 1508 if (first) 1509 first = false; 1510 else 1511 b.append("|"); 1512 b.append(tr.getWorkingCode()); 1513 if (tr.hasProfile()) { 1514 b.append("("); 1515 b.append(tr.getProfile()); 1516 b.append(")"); 1517 1518 } 1519 } 1520 return b.toString(); 1521 } 1522 1523 1524// private boolean findMatchingElement(String id, List<ElementDefinition> list) { 1525// for (ElementDefinition ed : list) { 1526// if (ed.getId().equals(id)) 1527// return true; 1528// if (id.endsWith("[x]")) { 1529// if (ed.getId().startsWith(id.substring(0, id.length()-3)) && !ed.getId().substring(id.length()-3).contains(".")) 1530// return true; 1531// } 1532// } 1533// return false; 1534// } 1535 1536 protected ElementDefinition getById(List<ElementDefinition> list, String baseId) { 1537 for (ElementDefinition t : list) { 1538 if (baseId.equals(t.getId())) { 1539 return t; 1540 } 1541 } 1542 return null; 1543 } 1544 1545 protected void updateConstraintSources(ElementDefinition ed, String url) { 1546 for (ElementDefinitionConstraintComponent c : ed.getConstraint()) { 1547 if (!c.hasSource()) { 1548 c.setSource(url); 1549 } 1550 } 1551 1552 } 1553 1554 protected Set<String> getListOfTypes(ElementDefinition e) { 1555 Set<String> result = new HashSet<>(); 1556 for (TypeRefComponent t : e.getType()) { 1557 result.add(t.getCode()); 1558 } 1559 return result; 1560 } 1561 1562 StructureDefinition getTypeForElement(StructureDefinitionDifferentialComponent differential, int diffCursor, String profileName, 1563 List<ElementDefinition> diffMatches, ElementDefinition outcome, String webUrl, Resource srcSD) { 1564 if (outcome.getType().size() == 0) { 1565 if (outcome.hasContentReference()) { 1566 throw new Error(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_CONTENT_REFERENCE_IN_THIS_CONTEXT, outcome.getContentReference(), outcome.getId(), outcome.getPath())); 1567 } else { 1568 throw new DefinitionException(context.formatMessage(I18nConstants._HAS_NO_CHILDREN__AND_NO_TYPES_IN_PROFILE_, diffMatches.get(0).getPath(), differential.getElement().get(diffCursor).getPath(), profileName)); 1569 } 1570 } 1571 if (outcome.getType().size() > 1) { 1572 for (TypeRefComponent t : outcome.getType()) { 1573 if (!t.getWorkingCode().equals("Reference")) 1574 throw new DefinitionException(context.formatMessage(I18nConstants._HAS_CHILDREN__AND_MULTIPLE_TYPES__IN_PROFILE_, diffMatches.get(0).getPath(), differential.getElement().get(diffCursor).getPath(), typeCode(outcome.getType()), profileName)); 1575 } 1576 } 1577 StructureDefinition dt = getProfileForDataType(outcome.getType().get(0), webUrl, srcSD); 1578 if (dt == null) 1579 throw new DefinitionException(context.formatMessage(I18nConstants.UNKNOWN_TYPE__AT_, outcome.getType().get(0), diffMatches.get(0).getPath())); 1580 return dt; 1581 } 1582 1583 protected String sliceNames(List<ElementDefinition> diffMatches) { 1584 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1585 for (ElementDefinition ed : diffMatches) { 1586 if (ed.hasSliceName()) { 1587 b.append(ed.getSliceName()); 1588 } 1589 } 1590 return b.toString(); 1591 } 1592 1593 protected boolean isMatchingType(StructureDefinition sd, List<TypeRefComponent> types, String inner) { 1594 StructureDefinition tsd = sd; 1595 while (tsd != null) { 1596 for (TypeRefComponent tr : types) { 1597 if (tsd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition") && tsd.getType().equals(tr.getCode())) { 1598 return true; 1599 } 1600 if (inner == null && tsd.getUrl().equals(tr.getCode())) { 1601 return true; 1602 } 1603 if (inner != null) { 1604 ElementDefinition ed = null; 1605 for (ElementDefinition t : tsd.getSnapshot().getElement()) { 1606 if (inner.equals(t.getId())) { 1607 ed = t; 1608 } 1609 } 1610 if (ed != null) { 1611 return isMatchingType(ed.getType(), types); 1612 } 1613 } 1614 } 1615 tsd = context.fetchResource(StructureDefinition.class, tsd.getBaseDefinition(), tsd); 1616 } 1617 return false; 1618 } 1619 1620 private boolean isMatchingType(List<TypeRefComponent> test, List<TypeRefComponent> desired) { 1621 for (TypeRefComponent t : test) { 1622 for (TypeRefComponent d : desired) { 1623 if (t.getCode().equals(d.getCode())) { 1624 return true; 1625 } 1626 } 1627 } 1628 return false; 1629 } 1630 1631 protected boolean isValidType(TypeRefComponent t, ElementDefinition base) { 1632 for (TypeRefComponent tr : base.getType()) { 1633 if (tr.getCode().equals(t.getCode())) { 1634 return true; 1635 } 1636 if (tr.getWorkingCode().equals(t.getCode())) { 1637 log.error("Type error: use of a simple type \""+t.getCode()+"\" wrongly constraining "+base.getPath()); 1638 return true; 1639 } 1640 } 1641 return false; 1642 } 1643 1644 protected void checkNotGenerating(StructureDefinition sd, String role) { 1645 if (sd.isGeneratingSnapshot()) { 1646 throw new FHIRException(context.formatMessage(I18nConstants.ATTEMPT_TO_USE_A_SNAPSHOT_ON_PROFILE__AS__BEFORE_IT_IS_GENERATED, sd.getUrl(), role)); 1647 } 1648 } 1649 1650 protected boolean isBaseResource(List<TypeRefComponent> types) { 1651 if (types.isEmpty()) 1652 return false; 1653 for (TypeRefComponent type : types) { 1654 String t = type.getWorkingCode(); 1655 if ("Resource".equals(t)) 1656 return false; 1657 } 1658 return true; 1659 1660 } 1661 1662 String determineFixedType(List<ElementDefinition> diffMatches, String fixedType, int i) { 1663 if (diffMatches.get(i).getType().size() == 0 && diffMatches.get(i).hasSliceName()) { 1664 String n = tail(diffMatches.get(i).getPath()).replace("[x]", ""); 1665 String t = diffMatches.get(i).getSliceName().substring(n.length()); 1666 if (isDataType(t)) { 1667 fixedType = t; 1668 } else if (isPrimitive(Utilities.uncapitalize(t))) { 1669 fixedType = Utilities.uncapitalize(t); 1670 } else { 1671 throw new FHIRException(context.formatMessage(I18nConstants.UNEXPECTED_CONDITION_IN_DIFFERENTIAL_TYPESLICETYPELISTSIZE__10_AND_IMPLICIT_SLICE_NAME_DOES_NOT_CONTAIN_A_VALID_TYPE__AT_, t, diffMatches.get(i).getPath(), diffMatches.get(i).getSliceName())); 1672 } 1673 } else if (diffMatches.get(i).getType().size() == 1) { 1674 fixedType = diffMatches.get(i).getType().get(0).getCode(); 1675 } else { 1676 throw new FHIRException(context.formatMessage(I18nConstants.UNEXPECTED_CONDITION_IN_DIFFERENTIAL_TYPESLICETYPELISTSIZE__1_AT_, diffMatches.get(i).getPath(), diffMatches.get(i).getSliceName())); 1677 } 1678 return fixedType; 1679 } 1680 1681 1682 protected BaseTypeSlice chooseMatchingBaseSlice(List<BaseTypeSlice> baseSlices, String type) { 1683 for (BaseTypeSlice bs : baseSlices) { 1684 if (bs.getType().equals(type)) { 1685 return bs; 1686 } 1687 } 1688 return null; 1689 } 1690 1691 1692 protected List<BaseTypeSlice> findBaseSlices(StructureDefinitionSnapshotComponent list, int start) { 1693 List<BaseTypeSlice> res = new ArrayList<>(); 1694 ElementDefinition base = list.getElement().get(start); 1695 int i = start + 1; 1696 while (i < list.getElement().size() && list.getElement().get(i).getPath().startsWith(base.getPath()+".")) { 1697 i++; 1698 }; 1699 while (i < list.getElement().size() && list.getElement().get(i).getPath().equals(base.getPath()) && list.getElement().get(i).hasSliceName()) { 1700 int s = i; 1701 i++; 1702 while (i < list.getElement().size() && list.getElement().get(i).getPath().startsWith(base.getPath()+".")) { 1703 i++; 1704 }; 1705 res.add(new BaseTypeSlice(list.getElement().get(s), list.getElement().get(s).getTypeFirstRep().getCode(), s, i-1)); 1706 } 1707 return res; 1708 } 1709 1710 1711 protected String getWebUrl(StructureDefinition dt, String webUrl) { 1712 if (dt.hasWebPath()) { 1713 // this is a hack, but it works for now, since we don't have deep folders 1714 String url = dt.getWebPath(); 1715 int i = url.lastIndexOf("/"); 1716 if (i < 1) { 1717 return defWebRoot; 1718 } else { 1719 return url.substring(0, i+1); 1720 } 1721 } else { 1722 return webUrl; 1723 } 1724 } 1725 1726 protected String descED(List<ElementDefinition> list, int index) { 1727 return index >=0 && index < list.size() ? list.get(index).present() : "X"; 1728 } 1729 1730 1731 1732 protected String rootName(String cpath) { 1733 String t = tail(cpath); 1734 return t.replace("[x]", ""); 1735 } 1736 1737 1738 protected String determineTypeSlicePath(String path, String cpath) { 1739 String headP = path.substring(0, path.lastIndexOf(".")); 1740// String tailP = path.substring(path.lastIndexOf(".")+1); 1741 String tailC = cpath.substring(cpath.lastIndexOf(".")+1); 1742 return headP+"."+tailC; 1743 } 1744 1745 1746 protected boolean isImplicitSlicing(ElementDefinition ed, String path) { 1747 if (ed == null || ed.getPath() == null || path == null) 1748 return false; 1749 if (path.equals(ed.getPath())) 1750 return false; 1751 boolean ok = path.endsWith("[x]") && ed.getPath().startsWith(path.substring(0, path.length()-3)); 1752 return ok; 1753 } 1754 1755 1756 protected boolean diffsConstrainTypes(List<ElementDefinition> diffMatches, String cPath, List<TypeSlice> typeList) { 1757// if (diffMatches.size() < 2) 1758 // return false; 1759 String p = diffMatches.get(0).getPath(); 1760 if (!p.endsWith("[x]") && !cPath.endsWith("[x]")) 1761 return false; 1762 typeList.clear(); 1763 String rn = tail(cPath); 1764 rn = rn.substring(0, rn.length()-3); 1765 for (int i = 0; i < diffMatches.size(); i++) { 1766 ElementDefinition ed = diffMatches.get(i); 1767 String n = tail(ed.getPath()); 1768 if (!n.startsWith(rn)) 1769 return false; 1770 String s = n.substring(rn.length()); 1771 if (!s.contains(".")) { 1772 if (ed.hasSliceName() && ed.getType().size() == 1) { 1773 typeList.add(new TypeSlice(ed, ed.getTypeFirstRep().getWorkingCode())); 1774 } else if (ed.hasSliceName() && ed.getType().size() == 0) { 1775 if (isDataType(s)) { 1776 typeList.add(new TypeSlice(ed, s)); 1777 } else if (isPrimitive(Utilities.uncapitalize(s))) { 1778 typeList.add(new TypeSlice(ed, Utilities.uncapitalize(s))); 1779 } else { 1780 String tn = ed.getSliceName().substring(n.length()); 1781 if (isDataType(tn)) { 1782 typeList.add(new TypeSlice(ed, tn)); 1783 } else if (isPrimitive(Utilities.uncapitalize(tn))) { 1784 typeList.add(new TypeSlice(ed, Utilities.uncapitalize(tn))); 1785 } 1786 } 1787 } else if (!ed.hasSliceName() && !s.equals("[x]")) { 1788 if (isDataType(s)) 1789 typeList.add(new TypeSlice(ed, s)); 1790 else if (isConstrainedDataType(s)) 1791 typeList.add(new TypeSlice(ed, baseType(s))); 1792 else if (isPrimitive(Utilities.uncapitalize(s))) 1793 typeList.add(new TypeSlice(ed, Utilities.uncapitalize(s))); 1794 } else if (!ed.hasSliceName() && s.equals("[x]")) 1795 typeList.add(new TypeSlice(ed, null)); 1796 } 1797 } 1798 return true; 1799 } 1800 1801 1802 protected List<ElementRedirection> redirectorStack(List<ElementRedirection> redirector, ElementDefinition outcome, String path) { 1803 List<ElementRedirection> result = new ArrayList<ElementRedirection>(); 1804 result.addAll(redirector); 1805 result.add(new ElementRedirection(outcome, path)); 1806 return result; 1807 } 1808 1809 1810 protected List<TypeRefComponent> getByTypeName(List<TypeRefComponent> type, String t) { 1811 List<TypeRefComponent> res = new ArrayList<TypeRefComponent>(); 1812 for (TypeRefComponent tr : type) { 1813 if (t.equals(tr.getWorkingCode())) 1814 res.add(tr); 1815 } 1816 return res; 1817 } 1818 1819 1820 protected void replaceFromContentReference(ElementDefinition outcome, ElementDefinition tgt) { 1821 outcome.setContentReference(null); 1822 outcome.getType().clear(); // though it should be clear anyway 1823 outcome.getType().addAll(tgt.getType()); 1824 } 1825 1826 1827 protected boolean baseWalksInto(List<ElementDefinition> elements, int cursor) { 1828 if (cursor >= elements.size()) 1829 return false; 1830 String path = elements.get(cursor).getPath(); 1831 String prevPath = elements.get(cursor - 1).getPath(); 1832 return path.startsWith(prevPath + "."); 1833 } 1834 1835 1836 protected ElementDefinition fillOutFromBase(ElementDefinition profile, ElementDefinition usage) throws FHIRFormatError { 1837 ElementDefinition res = profile.copy(); 1838 if (!res.hasSliceName()) 1839 res.setSliceName(usage.getSliceName()); 1840 if (!res.hasLabel()) 1841 res.setLabel(usage.getLabel()); 1842 for (Coding c : usage.getCode()) 1843 if (!res.hasCode(c)) 1844 res.addCode(c); 1845 1846 if (!res.hasDefinition()) 1847 res.setDefinition(usage.getDefinition()); 1848 if (!res.hasShort() && usage.hasShort()) 1849 res.setShort(usage.getShort()); 1850 if (!res.hasComment() && usage.hasComment()) 1851 res.setComment(usage.getComment()); 1852 if (!res.hasRequirements() && usage.hasRequirements()) 1853 res.setRequirements(usage.getRequirements()); 1854 for (StringType c : usage.getAlias()) 1855 if (!res.hasAlias(c.getValue())) 1856 res.addAlias(c.getValue()); 1857 if (!res.hasMin() && usage.hasMin()) 1858 res.setMin(usage.getMin()); 1859 if (!res.hasMax() && usage.hasMax()) 1860 res.setMax(usage.getMax()); 1861 1862 if (!res.hasFixed() && usage.hasFixed()) 1863 res.setFixed(usage.getFixed()); 1864 if (!res.hasPattern() && usage.hasPattern()) 1865 res.setPattern(usage.getPattern()); 1866 if (!res.hasExample() && usage.hasExample()) 1867 res.setExample(usage.getExample()); 1868 if (!res.hasMinValue() && usage.hasMinValue()) 1869 res.setMinValue(usage.getMinValue()); 1870 if (!res.hasMaxValue() && usage.hasMaxValue()) 1871 res.setMaxValue(usage.getMaxValue()); 1872 if (!res.hasMaxLength() && usage.hasMaxLength()) 1873 res.setMaxLength(usage.getMaxLength()); 1874 if (!res.hasMustSupport() && usage.hasMustSupport()) 1875 res.setMustSupport(usage.getMustSupport()); 1876 if (!res.hasIsSummary() && usage.hasIsSummary()) 1877 res.setIsSummary(usage.getIsSummary()); 1878 if (!res.hasIsModifier() && usage.hasIsModifier()) 1879 res.setIsModifier(usage.getIsModifier()); 1880 if (!res.hasIsModifierReason() && usage.hasIsModifierReason()) 1881 res.setIsModifierReason(usage.getIsModifierReason()); 1882 if (!res.hasMustHaveValue() && usage.hasMustHaveValue()) 1883 res.setMustHaveValue(usage.getMustHaveValue()); 1884 if (!res.hasBinding() && usage.hasBinding()) 1885 res.setBinding(usage.getBinding().copy()); 1886 for (ElementDefinitionConstraintComponent c : usage.getConstraint()) 1887 if (!res.hasConstraint(c.getKey())) 1888 res.addConstraint(c); 1889 for (Extension e : usage.getExtension()) { 1890 if (!res.hasExtension(e.getUrl())) 1891 res.addExtension(e.copy()); 1892 } 1893 1894 return res; 1895 } 1896 1897 1898 protected boolean checkExtensionDoco(ElementDefinition base) { 1899 // see task 3970. For an extension, there's no point copying across all the underlying definitional stuff 1900 boolean isExtension = (base.getPath().equals("Extension") || base.getPath().endsWith(".extension") || base.getPath().endsWith(".modifierExtension")) && 1901 (!base.hasBase() || !"II.extension".equals(base.getBase().getPath())); 1902 if (isExtension) { 1903 base.setDefinition("An Extension"); 1904 base.setShort("Extension"); 1905 base.setCommentElement(null); 1906 base.setRequirementsElement(null); 1907 base.getAlias().clear(); 1908 base.getMapping().clear(); 1909 } 1910 return isExtension; 1911 } 1912 1913 1914 protected String pathTail(List<ElementDefinition> diffMatches, int i) { 1915 1916 ElementDefinition d = diffMatches.get(i); 1917 String s = d.getPath().contains(".") ? d.getPath().substring(d.getPath().lastIndexOf(".")+1) : d.getPath(); 1918 return "."+s + (d.hasType() && d.getType().get(0).hasProfile() ? "["+d.getType().get(0).getProfile()+"]" : ""); 1919 } 1920 1921 1922 protected void markDerived(ElementDefinition outcome) { 1923 for (ElementDefinitionConstraintComponent inv : outcome.getConstraint()) 1924 inv.setUserData(UserDataNames.SNAPSHOT_IS_DERIVED, true); 1925 } 1926 1927 1928 static String summarizeSlicing(ElementDefinitionSlicingComponent slice) { 1929 StringBuilder b = new StringBuilder(); 1930 boolean first = true; 1931 for (ElementDefinitionSlicingDiscriminatorComponent d : slice.getDiscriminator()) { 1932 if (first) 1933 first = false; 1934 else 1935 b.append(", "); 1936 b.append(d.getType().toCode()+":"+d.getPath()); 1937 } 1938 b.append(" ("); 1939 if (slice.hasOrdered()) 1940 b.append(slice.getOrdered() ? "ordered" : "unordered"); 1941 b.append("/"); 1942 if (slice.hasRules()) 1943 b.append(slice.getRules().toCode()); 1944 b.append(")"); 1945 if (slice.hasDescription()) { 1946 b.append(" \""); 1947 b.append(slice.getDescription()); 1948 b.append("\""); 1949 } 1950 return b.toString(); 1951 } 1952 1953 1954 protected void updateFromBase(ElementDefinition derived, ElementDefinition base, String baseProfileUrl) { 1955 derived.setUserData(UserDataNames.SNAPSHOT_BASE_MODEL, baseProfileUrl); 1956 derived.setUserData(UserDataNames.SNAPSHOT_BASE_PATH, base.getPath()); 1957 if (base.hasBase()) { 1958 if (!derived.hasBase()) 1959 derived.setBase(new ElementDefinitionBaseComponent()); 1960 derived.getBase().setPath(base.getBase().getPath()); 1961 derived.getBase().setMin(base.getBase().getMin()); 1962 derived.getBase().setMax(base.getBase().getMax()); 1963 } else { 1964 if (!derived.hasBase()) 1965 derived.setBase(new ElementDefinitionBaseComponent()); 1966 derived.getBase().setPath(base.getPath()); 1967 derived.getBase().setMin(base.getMin()); 1968 derived.getBase().setMax(base.getMax()); 1969 } 1970 } 1971 1972 1973 protected boolean pathStartsWith(String p1, String p2) { 1974 return p1.startsWith(p2) || (p2.endsWith("[x].") && p1.startsWith(p2.substring(0, p2.length()-4))); 1975 } 1976 1977 private boolean pathMatches(String p1, String p2) { 1978 return p1.equals(p2) || (p2.endsWith("[x]") && p1.startsWith(p2.substring(0, p2.length()-3)) && !p1.substring(p2.length()-3).contains(".")); 1979 } 1980 1981 1982 protected String fixedPathSource(String contextPath, String pathSimple, List<ElementRedirection> redirector) { 1983 if (contextPath == null) 1984 return pathSimple; 1985// String ptail = pathSimple.substring(contextPath.length() + 1); 1986 if (redirector != null && redirector.size() > 0) { 1987 String ptail = null; 1988 if (contextPath.length() >= pathSimple.length()) { 1989 ptail = pathSimple.substring(pathSimple.indexOf(".")+1); 1990 } else { 1991 ptail = pathSimple.substring(contextPath.length()+1); 1992 } 1993 return redirector.get(redirector.size()-1).getPath()+"."+ptail; 1994// return contextPath+"."+tail(redirector.getPath())+"."+ptail.substring(ptail.indexOf(".")+1); 1995 } else { 1996 String ptail = pathSimple.substring(pathSimple.indexOf(".")+1); 1997 return contextPath+"."+ptail; 1998 } 1999 } 2000 2001 protected String fixedPathDest(String contextPath, String pathSimple, List<ElementRedirection> redirector, String redirectSource) { 2002 String s; 2003 if (contextPath == null) 2004 s = pathSimple; 2005 else { 2006 if (redirector != null && redirector.size() > 0) { 2007 String ptail = null; 2008 if (redirectSource.length() >= pathSimple.length()) { 2009 ptail = pathSimple.substring(pathSimple.indexOf(".")+1); 2010 } else { 2011 ptail = pathSimple.substring(redirectSource.length()+1); 2012 } 2013 // ptail = ptail.substring(ptail.indexOf(".")+1); 2014 s = contextPath+"."+/*tail(redirector.getPath())+"."+*/ptail; 2015 } else { 2016 String ptail = pathSimple.substring(pathSimple.indexOf(".")+1); 2017 s = contextPath+"."+ptail; 2018 } 2019 } 2020 return s; 2021 } 2022 2023 protected StructureDefinition getProfileForDataType(TypeRefComponent type, String webUrl, Resource src) { 2024 StructureDefinition sd = null; 2025 if (type.hasProfile()) { 2026 sd = context.fetchResource(StructureDefinition.class, type.getProfile().get(0).getValue(), src); 2027 if (sd == null) { 2028 if (makeXVer().matchingUrl(type.getProfile().get(0).getValue()) && xver.status(type.getProfile().get(0).getValue()) == XVerExtensionStatus.Valid) { 2029 sd = xver.getDefinition(type.getProfile().get(0).getValue()); 2030 generateSnapshot(context.fetchTypeDefinition("Extension"), sd, sd.getUrl(), webUrl, sd.getName()); 2031 } 2032 } 2033 if (sd == null) { 2034 log.debug("Failed to find referenced profile: " + type.getProfile()); 2035 } 2036 2037 } 2038 if (sd == null) 2039 sd = context.fetchTypeDefinition(type.getWorkingCode()); 2040 if (sd == null) 2041 log.warn("XX: failed to find profle for type: " + type.getWorkingCode()); // debug GJM 2042 return sd; 2043 } 2044 2045 protected StructureDefinition getProfileForDataType(String type) { 2046 StructureDefinition sd = context.fetchTypeDefinition(type); 2047 if (sd == null) 2048 log.warn("XX: failed to find profle for type: " + type); // debug GJM 2049 return sd; 2050 } 2051 2052 static String typeCode(List<TypeRefComponent> types) { 2053 StringBuilder b = new StringBuilder(); 2054 boolean first = true; 2055 for (TypeRefComponent type : types) { 2056 if (first) first = false; else b.append(", "); 2057 b.append(type.getWorkingCode()); 2058 if (type.hasTargetProfile()) 2059 b.append("{"+type.getTargetProfile()+"}"); 2060 else if (type.hasProfile()) 2061 b.append("{"+type.getProfile()+"}"); 2062 } 2063 return b.toString(); 2064 } 2065 2066 2067 protected boolean isDataType(List<TypeRefComponent> types) { 2068 if (types.isEmpty()) 2069 return false; 2070 for (TypeRefComponent type : types) { 2071 String t = type.getWorkingCode(); 2072 if (!isDataType(t) && !isPrimitive(t)) 2073 return false; 2074 } 2075 return true; 2076 } 2077 2078 2079 /** 2080 * Finds internal references in an Element's Binding and StructureDefinition references (in TypeRef) and bases them on the given url 2081 * @param url - the base url to use to turn internal references into absolute references 2082 * @param element - the Element to update 2083 * @return - the updated Element 2084 */ 2085 public ElementDefinition updateURLs(String url, String webUrl, ElementDefinition element, boolean processRelatives) { 2086 if (element != null) { 2087 ElementDefinition defn = element; 2088 if (defn.hasBinding() && defn.getBinding().hasValueSet() && defn.getBinding().getValueSet().startsWith("#")) 2089 defn.getBinding().setValueSet(url+defn.getBinding().getValueSet()); 2090 for (TypeRefComponent t : defn.getType()) { 2091 for (UriType u : t.getProfile()) { 2092 if (u.getValue().startsWith("#")) 2093 u.setValue(url+t.getProfile()); 2094 } 2095 for (UriType u : t.getTargetProfile()) { 2096 if (u.getValue().startsWith("#")) 2097 u.setValue(url+t.getTargetProfile()); 2098 } 2099 } 2100 if (webUrl != null) { 2101 // also, must touch up the markdown 2102 if (element.hasDefinition()) { 2103 element.setDefinition(processRelativeUrls(element.getDefinition(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives)); 2104 } 2105 if (element.hasComment()) { 2106 element.setComment(processRelativeUrls(element.getComment(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives)); 2107 } 2108 if (element.hasRequirements()) { 2109 element.setRequirements(processRelativeUrls(element.getRequirements(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives)); 2110 } 2111 if (element.hasMeaningWhenMissing()) { 2112 element.setMeaningWhenMissing(processRelativeUrls(element.getMeaningWhenMissing(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives)); 2113 } 2114 if (element.hasBinding() && element.getBinding().hasDescription()) { 2115 element.getBinding().setDescription(processRelativeUrls(element.getBinding().getDescription(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives)); 2116 } 2117 for (Extension ext : element.getExtension()) { 2118 if (ext.hasValueMarkdownType()) { 2119 MarkdownType md = ext.getValueMarkdownType(); 2120 md.setValue(processRelativeUrls(md.getValue(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives)); 2121 } 2122 } 2123 } 2124 } 2125 return element; 2126 } 2127 2128 2129 public static String processRelativeUrls(String markdown, String webUrl, String basePath, List<String> resourceNames, Set<String> baseFilenames, Set<String> localFilenames, boolean processRelatives) { 2130 if (markdown == null) { 2131 return ""; 2132 } 2133 Set<String> anchorRefs = new HashSet<>(); 2134 markdown = markdown+" "; 2135 2136 StringBuilder b = new StringBuilder(); 2137 int i = 0; 2138 int left = -1; 2139 boolean processingLink = false; 2140 int linkLeft = -1; 2141 while (i < markdown.length()) { 2142 if (markdown.charAt(i) == '[') { 2143 if (left == -1) { 2144 left = i; 2145 } else { 2146 left = Integer.MAX_VALUE; 2147 } 2148 } 2149 if (markdown.charAt(i) == ']') { 2150 if (left != -1 && left != Integer.MAX_VALUE && markdown.length() > i && markdown.charAt(i+1) != '(') { 2151 String n = markdown.substring(left+1, i); 2152 if (anchorRefs.contains(n) && markdown.length() > i && markdown.charAt(i+1) == ':') { 2153 processingLink = true; 2154 } else { 2155 anchorRefs.add(n); 2156 } 2157 } 2158 left = -1; 2159 } 2160 if (processingLink) { 2161 char ch = markdown.charAt(i); 2162 if (linkLeft == -1) { 2163 if (ch != ']' && ch != ':' && !Character.isWhitespace(ch)) { 2164 linkLeft = i; 2165 } else { 2166 b.append(ch); 2167 } 2168 } else { 2169 if (Character.isWhitespace(ch)) { 2170 // found the end of the processible link: 2171 String url = markdown.substring(linkLeft, i); 2172 if (isLikelySourceURLReference(url, resourceNames, baseFilenames, localFilenames, webUrl)) { 2173 b.append(basePath); 2174 if (!Utilities.noString(basePath) && !basePath.endsWith("/")) { 2175 b.append("/"); 2176 } 2177 } 2178 b.append(url); 2179 b.append(ch); 2180 linkLeft = -1; 2181 } 2182 } 2183 } else { 2184 if (i < markdown.length()-3 && markdown.substring(i, i+2).equals("](")) { 2185 2186 int j = i + 2; 2187 while (j < markdown.length() && markdown.charAt(j) != ')') 2188 j++; 2189 if (j < markdown.length()) { 2190 String url = markdown.substring(i+2, j); 2191 if (!Utilities.isAbsoluteUrl(url) && !url.startsWith("..")) { 2192 // 2193 // In principle, relative URLs are supposed to be converted to absolute URLs in snapshots. 2194 // that's what this code is doing. 2195 // 2196 // But that hasn't always happened and there's packages out there where the snapshots 2197 // contain relative references that actually are references to the main specification 2198 // 2199 // This code is trying to guess which relative references are actually to the 2200 // base specification. 2201 // 2202 if (isLikelySourceURLReference(url, resourceNames, baseFilenames, localFilenames, webUrl)) { 2203 b.append("]("); 2204 b.append(basePath); 2205 if (!Utilities.noString(basePath) && !basePath.endsWith("/")) { 2206 b.append("/"); 2207 } 2208 i = i + 1; 2209 } else { 2210 b.append("]("); 2211 // disabled 7-Dec 2021 GDG - we don't want to fool with relative URLs at all? 2212 // re-enabled 11-Feb 2022 GDG - we do want to do this. At least, $assemble in davinci-dtr, where the markdown comes from the SDC IG, and an SDC local reference must be changed to point to SDC. in this case, it's called when generating snapshots 2213 // added processRelatives parameter to deal with this (well, to try) 2214 if (processRelatives && webUrl != null && !issLocalFileName(url, localFilenames)) { 2215 2216 b.append(webUrl); 2217 if (!Utilities.noString(webUrl) && !webUrl.endsWith("/")) { 2218 b.append("/"); 2219 } 2220 } else { 2221 //DO NOTHING 2222 } 2223 i = i + 1; 2224 } 2225 } else 2226 b.append(markdown.charAt(i)); 2227 } else 2228 b.append(markdown.charAt(i)); 2229 } else { 2230 b.append(markdown.charAt(i)); 2231 } 2232 } 2233 i++; 2234 } 2235 String s = b.toString(); 2236 return Utilities.rightTrim(s); 2237 } 2238 2239 private static boolean issLocalFileName(String url, Set<String> localFilenames) { 2240 if (localFilenames != null) { 2241 for (String n : localFilenames) { 2242 if (url.startsWith(n.toLowerCase())) { 2243 return true; 2244 } 2245 } 2246 } 2247 return false; 2248 } 2249 2250 2251 private static boolean isLikelySourceURLReference(String url, List<String> resourceNames, Set<String> baseFilenames, Set<String> localFilenames, String baseUrl) { 2252 if (url == null) { 2253 return false; 2254 } 2255 if (baseUrl != null && !baseUrl.startsWith("http://hl7.org/fhir/R")) { 2256 if (resourceNames != null) { 2257 for (String n : resourceNames) { 2258 if (n != null && url.startsWith(n.toLowerCase()+".html")) { 2259 return true; 2260 } 2261 if (n != null && url.startsWith(n.toLowerCase()+"-definitions.html")) { 2262 return true; 2263 } 2264 } 2265 } 2266 if (localFilenames != null) { 2267 for (String n : localFilenames) { 2268 if (n != null && url.startsWith(n.toLowerCase())) { 2269 return false; 2270 } 2271 } 2272 } 2273 if (baseFilenames != null) { 2274 for (String n : baseFilenames) { 2275 if (n != null && url.startsWith(n.toLowerCase())) { 2276 return true; 2277 } 2278 } 2279 } 2280 } 2281 return 2282 url.startsWith("extensibility.html") || 2283 url.startsWith("terminologies.html") || 2284 url.startsWith("observation.html") || 2285 url.startsWith("codesystem.html") || 2286 url.startsWith("fhirpath.html") || 2287 url.startsWith("datatypes.html") || 2288 url.startsWith("operations.html") || 2289 url.startsWith("resource.html") || 2290 url.startsWith("elementdefinition.html") || 2291 url.startsWith("element-definitions.html") || 2292 url.startsWith("snomedct.html") || 2293 url.startsWith("loinc.html") || 2294 url.startsWith("http.html") || 2295 url.startsWith("references") || 2296 url.startsWith("license.html") || 2297 url.startsWith("narrative.html") || 2298 url.startsWith("search.html") || 2299 url.startsWith("security.html") || 2300 url.startsWith("versions.html") || 2301 url.startsWith("patient-operation-match.html") || 2302 (url.startsWith("extension-") && url.contains(".html")) || 2303 url.startsWith("resource-definitions.html"); 2304 } 2305 2306 protected List<ElementDefinition> getSiblings(List<ElementDefinition> list, ElementDefinition current) { 2307 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 2308 String path = current.getPath(); 2309 int cursor = list.indexOf(current)+1; 2310 while (cursor < list.size() && list.get(cursor).getPath().length() >= path.length()) { 2311 if (pathMatches(list.get(cursor).getPath(), path)) 2312 result.add(list.get(cursor)); 2313 cursor++; 2314 } 2315 return result; 2316 } 2317 2318 protected void updateFromSlicing(ElementDefinitionSlicingComponent dst, ElementDefinitionSlicingComponent src) { 2319 if (src.hasOrderedElement()) 2320 dst.setOrderedElement(src.getOrderedElement().copy()); 2321 if (src.hasDiscriminator()) { 2322 // dst.getDiscriminator().addAll(src.getDiscriminator()); Can't use addAll because it uses object equality, not string equality 2323 for (ElementDefinitionSlicingDiscriminatorComponent s : src.getDiscriminator()) { 2324 boolean found = false; 2325 for (ElementDefinitionSlicingDiscriminatorComponent d : dst.getDiscriminator()) { 2326 if (matches(d, s)) { 2327 found = true; 2328 break; 2329 } 2330 } 2331 if (!found) 2332 dst.getDiscriminator().add(s); 2333 } 2334 } 2335 if (src.hasRulesElement()) 2336 dst.setRulesElement(src.getRulesElement().copy()); 2337 } 2338 2339 protected boolean orderMatches(BooleanType diff, BooleanType base) { 2340 return (diff == null) || (base == null) || (diff.getValue() == base.getValue()); 2341 } 2342 2343 protected boolean discriminatorMatches(List<ElementDefinitionSlicingDiscriminatorComponent> diff, List<ElementDefinitionSlicingDiscriminatorComponent> base) { 2344 if (diff.isEmpty() || base.isEmpty()) 2345 return true; 2346 if (diff.size() < base.size()) 2347 return false; 2348 for (int i = 0; i < base.size(); i++) 2349 if (!matches(diff.get(i), base.get(i))) 2350 return false; 2351 return true; 2352 } 2353 2354 private boolean matches(ElementDefinitionSlicingDiscriminatorComponent c1, ElementDefinitionSlicingDiscriminatorComponent c2) { 2355 return c1.getType().equals(c2.getType()) && c1.getPath().equals(c2.getPath()); 2356 } 2357 2358 2359 protected boolean ruleMatches(SlicingRules diff, SlicingRules base) { 2360 return (diff == null) || (base == null) || (diff == base) || (base == SlicingRules.OPEN) || 2361 ((diff == SlicingRules.OPENATEND && base == SlicingRules.CLOSED)); 2362 } 2363 2364 protected boolean isSlicedToOneOnly(ElementDefinition e) { 2365 return (e.hasSlicing() && e.hasMaxElement() && e.getMax().equals("1")); 2366 } 2367 2368 protected boolean isTypeSlicing(ElementDefinition e) { 2369 return (e.hasSlicing() && e.getSlicing().getDiscriminator().size() == 1 && 2370 e.getSlicing().getDiscriminatorFirstRep().getType() == DiscriminatorType.TYPE && 2371 "$this".equals(e.getSlicing().getDiscriminatorFirstRep().getPath())); 2372 } 2373 2374 2375 protected ElementDefinitionSlicingComponent makeExtensionSlicing() { 2376 ElementDefinitionSlicingComponent slice = new ElementDefinitionSlicingComponent(); 2377 slice.addDiscriminator().setPath("url").setType(DiscriminatorType.VALUE); 2378 slice.setOrdered(false); 2379 slice.setRules(SlicingRules.OPEN); 2380 return slice; 2381 } 2382 2383 protected boolean isExtension(ElementDefinition currentBase) { 2384 return currentBase.getPath().endsWith(".extension") || currentBase.getPath().endsWith(".modifierExtension"); 2385 } 2386 2387 boolean hasInnerDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, List<ElementDefinition> base, boolean allowSlices) throws DefinitionException { 2388 end = Math.min(context.getElement().size(), end); 2389 start = Math.max(0, start); 2390 2391 for (int i = start; i <= end; i++) { 2392 ElementDefinition ed = context.getElement().get(i); 2393 String statedPath = ed.getPath(); 2394 if (!allowSlices && statedPath.equals(path) && ed.hasSliceName()) { 2395 return false; 2396 } else if (statedPath.startsWith(path+".")) { 2397 return true; 2398 } else if (path.endsWith("[x]") && statedPath.startsWith(path.substring(0, path.length() -3))) { 2399 return true; 2400 } else if (i != start && !allowSlices && !statedPath.startsWith(path+".")) { 2401 return false; 2402 } else if (i != start && allowSlices && !statedPath.startsWith(path)) { 2403 return false; 2404 } else { 2405 // not sure why we get here, but returning false at this point makes a bunch of tests fail 2406 } 2407 } 2408 return false; 2409 } 2410 2411 protected List<ElementDefinition> getDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, String profileName) throws DefinitionException { 2412 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 2413 String[] p = path.split("\\."); 2414 for (int i = start; i <= end; i++) { 2415 String statedPath = context.getElement().get(i).getPath(); 2416 String[] sp = statedPath.split("\\."); 2417 boolean ok = sp.length == p.length; 2418 for (int j = 0; j < p.length; j++) { 2419 ok = ok && sp.length > j && (p[j].equals(sp[j]) || isSameBase(p[j], sp[j])); 2420 } 2421// don't need this debug check - everything is ok 2422// if (ok != (statedPath.equals(path) || (path.endsWith("[x]") && statedPath.length() > path.length() - 2 && 2423// statedPath.substring(0, path.length()-3).equals(path.substring(0, path.length()-3)) && 2424// (statedPath.length() < path.length() || !statedPath.substring(path.length()).contains("."))))) { 2425// 2426// } 2427 if (ok) { 2428 /* 2429 * Commenting this out because it raises warnings when profiling inherited elements. For example, 2430 * Error: unknown element 'Bundle.meta.profile' (or it is out of order) in profile ... (looking for 'Bundle.entry') 2431 * Not sure we have enough information here to do the check properly. Might be better done when we're sorting the profile? 2432 2433 if (i != start && result.isEmpty() && !path.startsWith(context.getElement().get(start).getPath())) 2434 addMessage(new ValidationMessage(Source.ProfileValidator, IssueType.VALUE, "StructureDefinition.differential.element["+Integer.toString(start)+"]", "Error: unknown element '"+context.getElement().get(start).getPath()+"' (or it is out of order) in profile '"+url+"' (looking for '"+path+"')", IssueSeverity.WARNING)); 2435 2436 */ 2437 result.add(context.getElement().get(i)); 2438 } 2439 } 2440 if (debug) { 2441 Set<String> ids = new HashSet<>(); 2442 for (ElementDefinition ed : result) { 2443 ids.add(ed.getIdOrPath()); 2444 } 2445 } 2446 return result; 2447 } 2448 2449 2450 private boolean isSameBase(String p, String sp) { 2451 return (p.endsWith("[x]") && sp.startsWith(p.substring(0, p.length()-3))) || (sp.endsWith("[x]") && p.startsWith(sp.substring(0, sp.length()-3))) ; 2452 } 2453 2454 protected int findEndOfElement(StructureDefinitionDifferentialComponent context, int cursor) { 2455 int result = cursor; 2456 if (cursor >= context.getElement().size()) 2457 return result; 2458 String path = context.getElement().get(cursor).getPath()+"."; 2459 while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path)) 2460 result++; 2461 return result; 2462 } 2463 2464 protected int findEndOfElement(StructureDefinitionSnapshotComponent context, int cursor) { 2465 int result = cursor; 2466 String path = context.getElement().get(cursor).getPath()+"."; 2467 while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path)) 2468 result++; 2469 return result; 2470 } 2471 2472 protected int findEndOfElementNoSlices(StructureDefinitionSnapshotComponent context, int cursor) { 2473 int result = cursor; 2474 String path = context.getElement().get(cursor).getPath()+"."; 2475 while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path) && !context.getElement().get(result+1).hasSliceName()) 2476 result++; 2477 return result; 2478 } 2479 2480 protected boolean unbounded(ElementDefinition definition) { 2481 StringType max = definition.getMaxElement(); 2482 if (max == null) 2483 return false; // this is not valid 2484 if (max.getValue().equals("1")) 2485 return false; 2486 if (max.getValue().equals("0")) 2487 return false; 2488 return true; 2489 } 2490 2491 2492 public void updateFromObligationProfiles(ElementDefinition base) { 2493 List<ElementDefinition> obligationProfileElements = new ArrayList<>(); 2494 for (StructureDefinition sd : obligationProfiles) { 2495 ElementDefinition ed = sd.getSnapshot().getElementById(base.getId()); 2496 if (ed != null) { 2497 obligationProfileElements.add(ed); 2498 } 2499 } 2500 for (ElementDefinition ed : obligationProfileElements) { 2501 for (Extension ext : ed.getExtension()) { 2502 if (Utilities.existsInList(ext.getUrl(), ExtensionDefinitions.EXT_OBLIGATION_CORE, ExtensionDefinitions.EXT_OBLIGATION_TOOLS)) { 2503 base.getExtension().add(ext.copy()); 2504 } 2505 } 2506 } 2507 boolean hasMustSupport = false; 2508 for (ElementDefinition ed : obligationProfileElements) { 2509 hasMustSupport = hasMustSupport || ed.hasMustSupportElement(); 2510 } 2511 if (hasMustSupport) { 2512 for (ElementDefinition ed : obligationProfileElements) { 2513 mergeExtensions(base.getMustSupportElement(), ed.getMustSupportElement()); 2514 if (ed.getMustSupport()) { 2515 base.setMustSupport(true); 2516 } 2517 } 2518 } 2519 boolean hasBinding = false; 2520 for (ElementDefinition ed : obligationProfileElements) { 2521 hasBinding = hasBinding || ed.hasBinding(); 2522 } 2523 if (hasBinding) { 2524 ElementDefinitionBindingComponent binding = base.getBinding(); 2525 for (ElementDefinition ed : obligationProfileElements) { 2526 for (Extension ext : ed.getBinding().getExtension()) { 2527 if (ExtensionDefinitions.EXT_BINDING_ADDITIONAL.equals(ext.getUrl())) { 2528 String p = ext.getExtensionString("purpose"); 2529 if (!Utilities.existsInList(p, "maximum", "required", "extensible")) { 2530 if (!binding.hasExtension(ext)) { 2531 binding.getExtension().add(ext.copy()); 2532 } 2533 } 2534 } 2535 } 2536 for (ElementDefinitionBindingAdditionalComponent ab : ed.getBinding().getAdditional()) { 2537 if (!Utilities.existsInList(ab.getPurpose().toCode(), "maximum", "required", "extensible")) { 2538 if (!binding.hasAdditional(ab)) { 2539 binding.getAdditional().add(ab.copy()); 2540 } 2541 } 2542 } 2543 } 2544 } 2545 } 2546 2547 2548 protected void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn, boolean trimDifferential, String purl, StructureDefinition srcSD, StructureDefinition derivedSrc, String path, MappingAssistant mappings, boolean fromSlicer) throws DefinitionException, FHIRException { 2549 source.setUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT, dest); 2550 // we start with a clone of the base profile ('dest') and we copy from the profile ('source') 2551 // over the top for anything the source has 2552 ElementDefinition base = dest; 2553 ElementDefinition derived = source; 2554 derived.setUserData(UserDataNames.SNAPSHOT_DERIVATION_POINTER, base); 2555 boolean isExtension = checkExtensionDoco(base); 2556 List<ElementDefinition> obligationProfileElements = new ArrayList<>(); 2557 for (StructureDefinition sd : obligationProfiles) { 2558 ElementDefinition ed = sd.getSnapshot().getElementById(base.getId()); 2559 if (ed != null) { 2560 obligationProfileElements.add(ed); 2561 } 2562 } 2563 2564 // hack workaround for problem in R5 snapshots 2565 List<Extension> elist = dest.getExtensionsByUrl(ExtensionDefinitions.EXT_TRANSLATABLE); 2566 if (elist.size() == 2) { 2567 dest.getExtension().remove(elist.get(1)); 2568 } 2569 updateExtensionsFromDefinition(dest, source, derivedSrc, srcSD); 2570 2571 for (ElementDefinition ed : obligationProfileElements) { 2572 for (Extension ext : ed.getExtension()) { 2573 if (Utilities.existsInList(ext.getUrl(), ExtensionDefinitions.EXT_OBLIGATION_CORE, ExtensionDefinitions.EXT_OBLIGATION_TOOLS)) { 2574 dest.getExtension().add(new Extension(ExtensionDefinitions.EXT_OBLIGATION_CORE, ext.getValue().copy())); 2575 } 2576 } 2577 } 2578 2579 // Before applying changes, apply them to what's in the profile 2580 // but only if it's an extension or a resource 2581 2582 StructureDefinition profile = null; 2583 boolean msg = true; 2584 if (base.hasSliceName()) { 2585 profile = base.getType().size() == 1 && base.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, base.getTypeFirstRep().getProfile().get(0).getValue(), srcSD) : null; 2586 } 2587 if (profile == null && source.getTypeFirstRep().hasProfile()) { 2588 String pu = source.getTypeFirstRep().getProfile().get(0).getValue(); 2589 profile = context.fetchResource(StructureDefinition.class, pu, derivedSrc); 2590 if (profile == null) { 2591 if (makeXVer().matchingUrl(pu)) { 2592 switch (xver.status(pu)) { 2593 case BadVersion: 2594 throw new FHIRException("Reference to invalid version in extension url " + pu); 2595 case Invalid: 2596 throw new FHIRException("Reference to invalid extension " + pu); 2597 case Unknown: 2598 throw new FHIRException("Reference to unknown extension " + pu); 2599 case Valid: 2600 profile = xver.getDefinition(pu); 2601 generateSnapshot(context.fetchTypeDefinition("Extension"), profile, profile.getUrl(), context.getSpecUrl(), profile.getName()); 2602 } 2603 } 2604 2605 } 2606 if (profile != null && !"Extension".equals(profile.getType()) && profile.getKind() != StructureDefinitionKind.RESOURCE && profile.getKind() != StructureDefinitionKind.LOGICAL) { 2607 // this is a problem - we're kind of hacking things here. The problem is that we sometimes want the details from the profile to override the 2608 // inherited attributes, and sometimes not 2609 profile = null; 2610 msg = false; 2611 } 2612 } 2613 if (profile != null && (profile.getKind() == StructureDefinitionKind.RESOURCE || "Extension".equals(profile.getType()))) { 2614 if (profile.getSnapshot().getElement().isEmpty()) { 2615 throw new DefinitionException(context.formatMessage(I18nConstants.SNAPSHOT_IS_EMPTY, profile.getVersionedUrl())); 2616 } 2617 ElementDefinition e = profile.getSnapshot().getElement().get(0); 2618 String webroot = profile.getUserString(UserDataNames.render_webroot); 2619 2620 if (e.hasDefinition()) { 2621 base.setDefinition(processRelativeUrls(e.getDefinition(), webroot, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, true)); 2622 } 2623 if (e.getBinding().hasDescription()) { 2624 base.getBinding().setDescription(processRelativeUrls(e.getBinding().getDescription(), webroot, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, true)); 2625 } 2626 base.setShort(e.getShort()); 2627 if (e.hasCommentElement()) 2628 base.setCommentElement(e.getCommentElement()); 2629 if (e.hasRequirementsElement()) 2630 base.setRequirementsElement(e.getRequirementsElement()); 2631 base.getAlias().clear(); 2632 base.getAlias().addAll(e.getAlias()); 2633 base.getMapping().clear(); 2634 base.getMapping().addAll(e.getMapping()); 2635 } else if (source.getType().size() == 1 && source.getTypeFirstRep().hasProfile() && !source.getTypeFirstRep().getProfile().get(0).hasExtension(ExtensionDefinitions.EXT_PROFILE_ELEMENT)) { 2636 // todo: should we change down the profile_element if there's one? 2637 String type = source.getTypeFirstRep().getWorkingCode(); 2638 if (msg) { 2639 if ("Extension".equals(type)) { 2640 log.warn("Can't find Extension definition for "+source.getTypeFirstRep().getProfile().get(0).asStringValue()+" but trying to go on"); 2641 if (allowUnknownProfile != AllowUnknownProfile.ALL_TYPES) { 2642 throw new DefinitionException("Unable to find Extension definition for "+source.getTypeFirstRep().getProfile().get(0).asStringValue()); 2643 } 2644 } else { 2645 log.warn("Can't find "+type+" profile "+source.getTypeFirstRep().getProfile().get(0).asStringValue()+" but trying to go on"); 2646 if (allowUnknownProfile == AllowUnknownProfile.NONE) { 2647 throw new DefinitionException("Unable to find "+type+" profile "+source.getTypeFirstRep().getProfile().get(0).asStringValue()); 2648 } 2649 } 2650 } 2651 } 2652 if (derived != null) { 2653 if (derived.hasSliceName()) { 2654 base.setSliceName(derived.getSliceName()); 2655 } 2656 2657 if (derived.hasShortElement()) { 2658 if (!Base.compareDeep(derived.getShortElement(), base.getShortElement(), false)) 2659 base.setShortElement(derived.getShortElement().copy()); 2660 else if (trimDifferential) 2661 derived.setShortElement(null); 2662 else if (derived.hasShortElement()) 2663 derived.getShortElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2664 } 2665 2666 if (derived.hasDefinitionElement()) { 2667 if (!Base.compareDeep(derived.getDefinitionElement(), base.getDefinitionElement(), false)) { 2668 base.setDefinitionElement(mergeMarkdown(derived.getDefinitionElement(), base.getDefinitionElement())); 2669 } else if (trimDifferential) 2670 derived.setDefinitionElement(null); 2671 else if (derived.hasDefinitionElement()) 2672 derived.getDefinitionElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2673 } 2674 2675 if (derived.hasCommentElement()) { 2676 if (!Base.compareDeep(derived.getCommentElement(), base.getCommentElement(), false)) 2677 base.setCommentElement(mergeMarkdown(derived.getCommentElement(), base.getCommentElement())); 2678 else if (trimDifferential) 2679 base.setCommentElement(derived.getCommentElement().copy()); 2680 else if (derived.hasCommentElement()) 2681 derived.getCommentElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2682 } 2683 2684 if (derived.hasLabelElement()) { 2685 if (!base.hasLabelElement() || !Base.compareDeep(derived.getLabelElement(), base.getLabelElement(), false)) 2686 base.setLabelElement(mergeStrings(derived.getLabelElement(), base.getLabelElement())); 2687 else if (trimDifferential) 2688 base.setLabelElement(derived.getLabelElement().copy()); 2689 else if (derived.hasLabelElement()) 2690 derived.getLabelElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2691 } 2692 2693 if (derived.hasRequirementsElement()) { 2694 if (!base.hasRequirementsElement() || !Base.compareDeep(derived.getRequirementsElement(), base.getRequirementsElement(), false)) 2695 base.setRequirementsElement(mergeMarkdown(derived.getRequirementsElement(), base.getRequirementsElement())); 2696 else if (trimDifferential) 2697 base.setRequirementsElement(derived.getRequirementsElement().copy()); 2698 else if (derived.hasRequirementsElement()) 2699 derived.getRequirementsElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2700 } 2701 // sdf-9 2702 if (derived.hasRequirements() && !base.getPath().contains(".")) 2703 derived.setRequirements(null); 2704 if (base.hasRequirements() && !base.getPath().contains(".")) 2705 base.setRequirements(null); 2706 2707 if (derived.hasAlias()) { 2708 if (!Base.compareDeep(derived.getAlias(), base.getAlias(), false)) 2709 for (StringType s : derived.getAlias()) { 2710 if (!base.hasAlias(s.getValue())) 2711 base.getAlias().add(s.copy()); 2712 } 2713 else if (trimDifferential) 2714 derived.getAlias().clear(); 2715 else 2716 for (StringType t : derived.getAlias()) 2717 t.setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2718 } 2719 2720 if (derived.hasMinElement()) { 2721 if (!Base.compareDeep(derived.getMinElement(), base.getMinElement(), false)) { 2722 if (derived.getMin() < base.getMin() && !derived.hasSliceName()) // in a slice, minimum cardinality rules do not apply 2723 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived min ("+Integer.toString(derived.getMin())+") cannot be less than the base min ("+Integer.toString(base.getMin())+") in "+srcSD.getVersionedUrl(), ValidationMessage.IssueSeverity.ERROR)); 2724 base.setMinElement(derived.getMinElement().copy()); 2725 } else if (trimDifferential) 2726 derived.setMinElement(null); 2727 else 2728 derived.getMinElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2729 } 2730 2731 if (derived.hasMaxElement()) { 2732 if (!Base.compareDeep(derived.getMaxElement(), base.getMaxElement(), false)) { 2733 if (isLargerMax(derived.getMax(), base.getMax())) 2734 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived max ("+derived.getMax()+") cannot be greater than the base max ("+base.getMax()+")", ValidationMessage.IssueSeverity.ERROR)); 2735 base.setMaxElement(derived.getMaxElement().copy()); 2736 } else if (trimDifferential) 2737 derived.setMaxElement(null); 2738 else 2739 derived.getMaxElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2740 } 2741 2742 if (derived.hasFixed()) { 2743 if (!Base.compareDeep(derived.getFixed(), base.getFixed(), true)) { 2744 base.setFixed(derived.getFixed().copy()); 2745 } else if (trimDifferential) 2746 derived.setFixed(null); 2747 else 2748 derived.getFixed().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2749 } 2750 2751 if (derived.hasPattern()) { 2752 if (!Base.compareDeep(derived.getPattern(), base.getPattern(), false)) { 2753 base.setPattern(derived.getPattern().copy()); 2754 } else 2755 if (trimDifferential) 2756 derived.setPattern(null); 2757 else 2758 derived.getPattern().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2759 } 2760 2761 List<ElementDefinitionExampleComponent> toDelB = new ArrayList<>(); 2762 List<ElementDefinitionExampleComponent> toDelD = new ArrayList<>(); 2763 for (ElementDefinitionExampleComponent ex : derived.getExample()) { 2764 boolean delete = ex.hasExtension(ExtensionDefinitions.EXT_ED_SUPPRESS); 2765 if (delete && "$all".equals(ex.getLabel())) { 2766 toDelB.addAll(base.getExample()); 2767 } else { 2768 boolean found = false; 2769 for (ElementDefinitionExampleComponent exS : base.getExample()) { 2770 if (Base.compareDeep(ex.getLabel(), exS.getLabel(), false) && Base.compareDeep(ex.getValue(), exS.getValue(), false)) { 2771 if (delete) { 2772 toDelB.add(exS); 2773 } else { 2774 found = true; 2775 } 2776 } 2777 } 2778 if (delete) { 2779 toDelD.add(ex); 2780 } else if (!found) { 2781 base.addExample(ex.copy()); 2782 } else if (trimDifferential) { 2783 derived.getExample().remove(ex); 2784 } else { 2785 ex.setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2786 } 2787 } 2788 } 2789 base.getExample().removeAll(toDelB); 2790 derived.getExample().removeAll(toDelD); 2791 2792 if (derived.hasMaxLengthElement()) { 2793 if (!Base.compareDeep(derived.getMaxLengthElement(), base.getMaxLengthElement(), false)) 2794 base.setMaxLengthElement(derived.getMaxLengthElement().copy()); 2795 else if (trimDifferential) 2796 derived.setMaxLengthElement(null); 2797 else 2798 derived.getMaxLengthElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2799 } 2800 2801 if (derived.hasMaxValue()) { 2802 if (!Base.compareDeep(derived.getMaxValue(), base.getMaxValue(), false)) 2803 base.setMaxValue(derived.getMaxValue().copy()); 2804 else if (trimDifferential) 2805 derived.setMaxValue(null); 2806 else 2807 derived.getMaxValue().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2808 } 2809 2810 if (derived.hasMinValue()) { 2811 if (!Base.compareDeep(derived.getMinValue(), base.getMinValue(), false)) 2812 base.setMinValue(derived.getMinValue().copy()); 2813 else if (trimDifferential) 2814 derived.setMinValue(null); 2815 else 2816 derived.getMinValue().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2817 } 2818 2819 // todo: what to do about conditions? 2820 // condition : id 0..* 2821 2822 boolean hasMustSupport = derived.hasMustSupportElement(); 2823 for (ElementDefinition ed : obligationProfileElements) { 2824 hasMustSupport = hasMustSupport || ed.hasMustSupportElement(); 2825 } 2826 if (hasMustSupport) { 2827 BooleanType mse = derived.getMustSupportElement().copy(); 2828 for (ElementDefinition ed : obligationProfileElements) { 2829 mergeExtensions(mse, ed.getMustSupportElement()); 2830 if (ed.getMustSupport()) { 2831 mse.setValue(true); 2832 } 2833 } 2834 if (!(base.hasMustSupportElement() && Base.compareDeep(base.getMustSupportElement(), mse, false))) { 2835 if (base.hasMustSupport() && base.getMustSupport() && !derived.getMustSupport() && !fromSlicer) { 2836 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Illegal constraint [must-support = false] when [must-support = true] in the base profile", ValidationMessage.IssueSeverity.ERROR)); 2837 } 2838 base.setMustSupportElement(mse); 2839 } else if (trimDifferential) 2840 derived.setMustSupportElement(null); 2841 else 2842 derived.getMustSupportElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2843 } 2844 2845 if (derived.hasMustHaveValueElement()) { 2846 if (!(base.hasMustHaveValueElement() && Base.compareDeep(derived.getMustHaveValueElement(), base.getMustHaveValueElement(), false))) { 2847 if (base.hasMustHaveValue() && base.getMustHaveValue() && !derived.getMustHaveValue() && !fromSlicer) { 2848 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Illegal constraint [must-have-value = false] when [must-have-value = true] in the base profile", ValidationMessage.IssueSeverity.ERROR)); 2849 } 2850 base.setMustHaveValueElement(derived.getMustHaveValueElement().copy()); 2851 } else if (trimDifferential) 2852 derived.setMustHaveValueElement(null); 2853 else 2854 derived.getMustHaveValueElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2855 } 2856 if (derived.hasValueAlternatives()) { 2857 if (!Base.compareDeep(derived.getValueAlternatives(), base.getValueAlternatives(), false)) 2858 for (CanonicalType s : derived.getValueAlternatives()) { 2859 if (!base.hasValueAlternatives(s.getValue())) 2860 base.getValueAlternatives().add(s.copy()); 2861 } 2862 else if (trimDifferential) 2863 derived.getValueAlternatives().clear(); 2864 else 2865 for (CanonicalType t : derived.getValueAlternatives()) 2866 t.setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2867 } 2868 2869 // profiles cannot change : isModifier, defaultValue, meaningWhenMissing 2870 // but extensions can change isModifier 2871 if (isExtension) { 2872 if (derived.hasIsModifierElement() && !(base.hasIsModifierElement() && Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false))) { 2873 base.setIsModifierElement(derived.getIsModifierElement().copy()); 2874 } else if (trimDifferential) { 2875 derived.setIsModifierElement(null); 2876 } else if (derived.hasIsModifierElement()) { 2877 derived.getIsModifierElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2878 } 2879 if (derived.hasIsModifierReasonElement() && !(base.hasIsModifierReasonElement() && Base.compareDeep(derived.getIsModifierReasonElement(), base.getIsModifierReasonElement(), false))) { 2880 base.setIsModifierReasonElement(derived.getIsModifierReasonElement().copy()); 2881 } else if (trimDifferential) { 2882 derived.setIsModifierReasonElement(null); 2883 } else if (derived.hasIsModifierReasonElement()) { 2884 derived.getIsModifierReasonElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2885 } 2886 if (base.getIsModifier() && !base.hasIsModifierReason()) { 2887 // we get here because modifier extensions don't get a modifier reason from the type 2888 base.setIsModifierReason("Modifier extensions are labelled as such because they modify the meaning or interpretation of the resource or element that contains them"); 2889 } 2890 } 2891 2892 boolean hasBinding = derived.hasBinding(); 2893 for (ElementDefinition ed : obligationProfileElements) { 2894 hasBinding = hasBinding || ed.hasBinding(); 2895 } 2896 if (hasBinding) { 2897 updateExtensionsFromDefinition(dest.getBinding(), source.getBinding(), derivedSrc, srcSD); 2898 ElementDefinitionBindingComponent binding = derived.getBinding(); 2899 for (ElementDefinition ed : obligationProfileElements) { 2900 for (Extension ext : ed.getBinding().getExtension()) { 2901 if (ExtensionDefinitions.EXT_BINDING_ADDITIONAL.equals(ext.getUrl())) { 2902 String p = ext.getExtensionString("purpose"); 2903 if (!Utilities.existsInList(p, "maximum", "required", "extensible")) { 2904 if (!binding.hasExtension(ext)) { 2905 binding.getExtension().add(ext.copy()); 2906 } 2907 } 2908 } 2909 } 2910 for (ElementDefinitionBindingAdditionalComponent ab : ed.getBinding().getAdditional()) { 2911 if (!Utilities.existsInList(ab.getPurpose().toCode(), "maximum", "required", "extensible")) { 2912 if (binding.hasAdditional(ab)) { 2913 binding.getAdditional().add(ab.copy()); 2914 } 2915 } 2916 } 2917 } 2918 2919 if (!base.hasBinding() || !Base.compareDeep(derived.getBinding(), base.getBinding(), false)) { 2920 if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && derived.getBinding().getStrength() != BindingStrength.REQUIRED) 2921 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "illegal attempt to change the binding on "+derived.getPath()+" from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode(), ValidationMessage.IssueSeverity.ERROR)); 2922// throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode()); 2923 else if (base.hasBinding() && derived.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && base.getBinding().hasValueSet() && derived.getBinding().hasValueSet()) { 2924 ValueSet baseVs = context.findTxResource(ValueSet.class, base.getBinding().getValueSet(), srcSD); 2925 ValueSet contextVs = context.findTxResource(ValueSet.class, derived.getBinding().getValueSet(), derivedSrc); 2926 if (baseVs == null) { 2927 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING)); 2928 } else if (contextVs == null) { 2929 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING)); 2930 } else { 2931 ValueSetExpansionOutcome expBase = context.expandVS(baseVs, true, false); 2932 ValueSetExpansionOutcome expDerived = context.expandVS(contextVs, true, false); 2933 if (expBase.getValueset() == null) 2934 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING)); 2935 else if (expDerived.getValueset() == null) 2936 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING)); 2937 else if (ExtensionUtilities.hasExtension(expBase.getValueset().getExpansion(), ExtensionDefinitions.EXT_EXP_TOOCOSTLY)) { 2938 if (ExtensionUtilities.hasExtension(expDerived.getValueset().getExpansion(), ExtensionDefinitions.EXT_EXP_TOOCOSTLY) || expDerived.getValueset().getExpansion().getContains().size() > 100) { 2939 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Unable to check if "+derived.getBinding().getValueSet()+" is a proper subset of " +base.getBinding().getValueSet()+" - base value set is too large to check", ValidationMessage.IssueSeverity.WARNING)); 2940 } else { 2941 boolean ok = true; 2942 for (ValueSetExpansionContainsComponent cc : expDerived.getValueset().getExpansion().getContains()) { 2943 ValidationResult vr = context.validateCode(new ValidationOptions(), cc.getSystem(), cc.getVersion(), cc.getCode(), null, baseVs); 2944 if (!vr.isOk()) { 2945 ok = false; 2946 break; 2947 } 2948 } 2949 if (!ok) { 2950 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" is not a subset of binding "+base.getBinding().getValueSet(), ValidationMessage.IssueSeverity.ERROR)); 2951 } 2952 } 2953 } else if (expBase.getValueset().getExpansion().getContains().size() == 1000 || 2954 expDerived.getValueset().getExpansion().getContains().size() == 1000) { 2955 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Unable to check if "+derived.getBinding().getValueSet()+" is a proper subset of " +base.getBinding().getValueSet()+" - value set is too large to check", ValidationMessage.IssueSeverity.WARNING)); 2956 } else { 2957 String msgs = checkSubset(expBase.getValueset(), expDerived.getValueset()); 2958 if (msgs != null) { 2959 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" is not a subset of binding "+base.getBinding().getValueSet()+" because "+msgs, ValidationMessage.IssueSeverity.ERROR)); 2960 } 2961 } 2962 } 2963 } 2964 ElementDefinitionBindingComponent d = derived.getBinding(); 2965 ElementDefinitionBindingComponent nb = base.getBinding().copy(); 2966 if (!COPY_BINDING_EXTENSIONS) { 2967 nb.getExtension().clear(); 2968 } 2969 nb.setDescription(null); 2970 for (Extension dex : d.getExtension()) { 2971 nb.getExtension().add(markExtensionSource(dex.copy(), false, srcSD)); 2972 } 2973 if (d.hasStrength()) { 2974 nb.setStrength(d.getStrength()); 2975 } 2976 if (d.hasDescription()) { 2977 nb.setDescription(d.getDescription()); 2978 } 2979 if (d.hasValueSet()) { 2980 nb.setValueSet(d.getValueSet()); 2981 } 2982 for (ElementDefinitionBindingAdditionalComponent ab : d.getAdditional()) { 2983 ElementDefinitionBindingAdditionalComponent eab = getMatchingAdditionalBinding(nb, ab); 2984 if (eab != null) { 2985 mergeAdditionalBinding(eab, ab); 2986 } else { 2987 nb.getAdditional().add(ab); 2988 } 2989 } 2990 base.setBinding(nb); 2991 } else if (trimDifferential) 2992 derived.setBinding(null); 2993 else 2994 derived.getBinding().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2995 } else if (base.hasBinding()) { 2996 base.getBinding().getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), ProfileUtilities.NON_INHERITED_ED_URLS)); 2997 for (Extension ex : base.getBinding().getExtension()) { 2998 markExtensionSource(ex, false, srcSD); 2999 } 3000 } 3001 3002 if (derived.hasIsSummaryElement()) { 3003 if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false)) { 3004 if (base.hasIsSummary() && !context.getVersion().equals("1.4.0")) // work around a known issue with some 1.4.0 cosntraints 3005 throw new Error(context.formatMessage(I18nConstants.ERROR_IN_PROFILE__AT__BASE_ISSUMMARY___DERIVED_ISSUMMARY__, purl, derived.getPath(), base.getIsSummaryElement().asStringValue(), derived.getIsSummaryElement().asStringValue())); 3006 base.setIsSummaryElement(derived.getIsSummaryElement().copy()); 3007 } else if (trimDifferential) 3008 derived.setIsSummaryElement(null); 3009 else 3010 derived.getIsSummaryElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 3011 } 3012 3013 // this would make sense but blows up the process later, so we let it happen anyway, and sort out the business rule elsewhere 3014 //if (!derived.hasContentReference() && !base.hasContentReference()) { 3015 3016 if (derived.hasType()) { 3017 if (!Base.compareDeep(derived.getType(), base.getType(), false)) { 3018 if (base.hasType()) { 3019 for (TypeRefComponent ts : derived.getType()) { 3020 checkTypeDerivation(purl, derivedSrc, base, derived, ts, path, derivedSrc.getDerivation() == TypeDerivationRule.SPECIALIZATION); 3021 } 3022 } 3023 base.getType().clear(); 3024 for (TypeRefComponent t : derived.getType()) { 3025 TypeRefComponent tt = t.copy(); 3026 // tt.setUserData(DERIVATION_EQUALS, true); 3027 base.getType().add(tt); 3028 for (Extension ex : tt.getExtension()) { 3029 markExtensionSource(ex, false, srcSD); 3030 } 3031 } 3032 } 3033 else if (trimDifferential) 3034 derived.getType().clear(); 3035 else { 3036 for (TypeRefComponent t : derived.getType()) { 3037 t.setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 3038 for (Extension ex : t.getExtension()) { 3039 markExtensionSource(ex, true, derivedSrc); 3040 } 3041 } 3042 } 3043 } 3044 3045 mappings.merge(derived, base); // note reversal of names to be correct in .merge() 3046 3047 // todo: constraints are cumulative. there is no replacing 3048 for (ElementDefinitionConstraintComponent s : base.getConstraint()) { 3049 s.setUserData(UserDataNames.SNAPSHOT_IS_DERIVED, true); 3050 if (!s.hasSource()) { 3051 s.setSource(srcSD.getUrl()); 3052 } 3053 } 3054 if (derived.hasConstraint()) { 3055 for (ElementDefinitionConstraintComponent s : derived.getConstraint()) { 3056 if (!base.hasConstraint(s.getKey())) { 3057 ElementDefinitionConstraintComponent inv = s.copy(); 3058 base.getConstraint().add(inv); 3059 } 3060 } 3061 } 3062 for (IdType id : derived.getCondition()) { 3063 if (!base.hasCondition(id)) { 3064 base.getCondition().add(id); 3065 } 3066 } 3067 3068 // now, check that we still have a bindable type; if not, delete the binding - see task 8477 3069 if (dest.hasBinding() && !hasBindableType(dest)) { 3070 dest.setBinding(null); 3071 } 3072 3073// // finally, we copy any extensions from source to dest 3074 //no, we already did. 3075// for (Extension ex : derived.getExtension()) { 3076// ! 3077// StructureDefinition sd = context.fetchResource(StructureDefinition.class, ex.getUrl(), derivedSrc); 3078// if (sd == null || sd.getSnapshot() == null || sd.getSnapshot().getElementFirstRep().getMax().equals("1")) { 3079// ToolingExtensions.removeExtension(dest, ex.getUrl()); 3080// } 3081// dest.addExtension(ex.copy()); 3082// } 3083 } 3084 if (dest.hasFixed()) { 3085 checkTypeOk(dest, dest.getFixed().fhirType(), srcSD, "fixed"); 3086 } 3087 if (dest.hasPattern()) { 3088 checkTypeOk(dest, dest.getPattern().fhirType(), srcSD, "pattern"); 3089 } 3090 //updateURLs(url, webUrl, dest); 3091 } 3092 3093 private MarkdownType mergeMarkdown(MarkdownType dest, MarkdownType source) { 3094 MarkdownType mergedMarkdown = dest.copy(); 3095 if (!mergedMarkdown.hasValue() && source.hasValue()) { 3096 mergedMarkdown.setValue(source.getValue()); 3097 } else if (mergedMarkdown.hasValue() && source.hasValue() && mergedMarkdown.getValue().startsWith("...")) { 3098 mergedMarkdown.setValue(Utilities.appendDerivedTextToBase(source.getValue(), mergedMarkdown.getValue())); 3099 } 3100 for (Extension sourceExtension : source.getExtension()) { 3101 Extension matchingExtension = findMatchingExtension(mergedMarkdown, sourceExtension); 3102 if (matchingExtension == null) { 3103 mergedMarkdown.addExtension(sourceExtension.copy()); 3104 } else { 3105 matchingExtension.setValue(sourceExtension.getValue()); 3106 } 3107 } 3108 return mergedMarkdown; 3109 } 3110 3111 private StringType mergeStrings(StringType dest, StringType source) { 3112 StringType res = dest.copy(); 3113 if (!res.hasValue() && source.hasValue()) { 3114 res.setValue(source.getValue()); 3115 } else if (res.hasValue() && source.hasValue() && res.getValue().startsWith("...")) { 3116 res.setValue(Utilities.appendDerivedTextToBase(res.getValue(), source.getValue())); 3117 } 3118 for (Extension sourceExtension : source.getExtension()) { 3119 Extension matchingExtension = findMatchingExtension(res, sourceExtension); 3120 if (matchingExtension == null) { 3121 res.addExtension(sourceExtension.copy()); 3122 } else { 3123 matchingExtension.setValue(sourceExtension.getValue()); 3124 } 3125 } 3126 return res; 3127 } 3128 3129 private Extension findMatchingExtension(Element res, Extension extensionToMatch) { 3130 for (Extension elementExtension : res.getExtensionsByUrl(extensionToMatch.getUrl())) { 3131 if (ExtensionDefinitions.EXT_TRANSLATION.equals(elementExtension.getUrl())) { 3132 String slang = extensionToMatch.getExtensionString("lang"); 3133 String dlang = elementExtension.getExtensionString("lang"); 3134 if (Utilities.stringsEqual(slang, dlang)) { 3135 return elementExtension; 3136 } 3137 } else { 3138 return elementExtension; 3139 } 3140 3141 } 3142 return null; 3143 } 3144 3145 private static Extension markExtensionSource(Extension extension, boolean overrideSource, StructureDefinition srcSD) { 3146 if (overrideSource || !extension.hasUserData(UserDataNames.SNAPSHOT_EXTENSION_SOURCE)) { 3147 extension.setUserData(UserDataNames.SNAPSHOT_EXTENSION_SOURCE, srcSD); 3148 } 3149 if (Utilities.existsInList(extension.getUrl(), ExtensionDefinitions.EXT_OBLIGATION_CORE, ExtensionDefinitions.EXT_OBLIGATION_TOOLS)) { 3150 Extension sub = extension.getExtensionByUrl(ExtensionDefinitions.EXT_OBLIGATION_SOURCE, ExtensionDefinitions.EXT_OBLIGATION_SOURCE_SHORT); 3151 if (sub == null || overrideSource) { 3152 ExtensionUtilities.setCanonicalExtension(extension, ExtensionDefinitions.EXT_OBLIGATION_SOURCE, srcSD.getVersionedUrl()); 3153 } 3154 } 3155 return extension; 3156 } 3157 3158 private void updateExtensionsFromDefinition(Element dest, Element source, StructureDefinition destSD, StructureDefinition srcSD) { 3159 dest.getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), NON_INHERITED_ED_URLS) || (Utilities.existsInList(ext.getUrl(), DEFAULT_INHERITED_ED_URLS) && source.hasExtension(ext.getUrl()))); 3160 3161 for (Extension ext : source.getExtension()) { 3162 if (!dest.hasExtension(ext.getUrl())) { 3163 dest.getExtension().add(markExtensionSource(ext.copy(), false, srcSD)); 3164 } else if (Utilities.existsInList(ext.getUrl(), NON_OVERRIDING_ED_URLS)) { 3165 // do nothing 3166 for (Extension ex2 : dest.getExtensionsByUrl(ext.getUrl())) { 3167 markExtensionSource(ex2, true, destSD); 3168 } 3169 } else if (Utilities.existsInList(ext.getUrl(), OVERRIDING_ED_URLS)) { 3170 dest.getExtensionByUrl(ext.getUrl()).setValue(ext.getValue()); 3171 markExtensionSource(dest.getExtensionByUrl(ext.getUrl()), false, srcSD); 3172 } else { 3173 dest.getExtension().add(markExtensionSource(ext.copy(), false, srcSD)); 3174 } 3175 } 3176 } 3177 3178 private void mergeAdditionalBinding(ElementDefinitionBindingAdditionalComponent dest, ElementDefinitionBindingAdditionalComponent source) { 3179 for (UsageContext t : source.getUsage()) { 3180 if (!hasUsage(dest, t)) { 3181 dest.addUsage(t); 3182 } 3183 } 3184 if (source.getAny()) { 3185 source.setAny(true); 3186 } 3187 if (source.hasShortDoco()) { 3188 dest.setShortDoco(source.getShortDoco()); 3189 } 3190 if (source.hasDocumentation()) { 3191 dest.setDocumentation(source.getDocumentation()); 3192 } 3193 3194 } 3195 3196 private boolean hasUsage(ElementDefinitionBindingAdditionalComponent dest, UsageContext tgt) { 3197 for (UsageContext t : dest.getUsage()) { 3198 if (t.getCode() != null && t.getCode().matches(tgt.getCode()) && t.getValue() != null && t.getValue().equals(tgt.getValue())) { 3199 return true; 3200 } 3201 } 3202 return false; 3203 } 3204 3205 private ElementDefinitionBindingAdditionalComponent getMatchingAdditionalBinding(ElementDefinitionBindingComponent nb,ElementDefinitionBindingAdditionalComponent ab) { 3206 for (ElementDefinitionBindingAdditionalComponent t : nb.getAdditional()) { 3207 if (t.getValueSet() != null && t.getValueSet().equals(ab.getValueSet()) && t.getPurpose() == ab.getPurpose() && !ab.hasUsage()) { 3208 return t; 3209 } 3210 } 3211 return null; 3212 } 3213 3214 private void mergeExtensions(Element tgt, Element src) { 3215 tgt.getExtension().addAll(src.getExtension()); 3216 } 3217 3218 private void checkTypeDerivation(String purl, StructureDefinition srcSD, ElementDefinition base, ElementDefinition derived, TypeRefComponent ts, String path, boolean specialising) { 3219 boolean ok = false; 3220 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 3221 String t = ts.getWorkingCode(); 3222 String tDesc = ts.toString(); 3223 for (TypeRefComponent td : base.getType()) {; 3224 boolean matchType = false; 3225 String tt = td.getWorkingCode(); 3226 b.append(td.toString()); 3227 if (td.hasCode() && (tt.equals(t))) { 3228 matchType = true; 3229 } 3230 if (!matchType) { 3231 StructureDefinition sdt = context.fetchTypeDefinition(tt); 3232 if (sdt != null && (sdt.getAbstract() || sdt.getKind() == StructureDefinitionKind.LOGICAL)) { 3233 StructureDefinition sdb = context.fetchTypeDefinition(t); 3234 while (sdb != null && !matchType) { 3235 matchType = sdb.getType().equals(sdt.getType()); 3236 sdb = context.fetchResource(StructureDefinition.class, sdb.getBaseDefinition(), sdb); 3237 } 3238 } 3239 } 3240 // work around for old badly generated SDs 3241// if (DONT_DO_THIS && Utilities.existsInList(tt, "Extension", "uri", "string", "Element")) { 3242// matchType = true; 3243// } 3244// if (DONT_DO_THIS && Utilities.existsInList(tt, "Resource","DomainResource") && pkp.isResource(t)) { 3245// matchType = true; 3246// } 3247 if (matchType) { 3248 ts.copyExtensions(td, "http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support", "http://hl7.org/fhir/StructureDefinition/elementdefinition-pattern", "http://hl7.org/fhir/StructureDefinition/obligation", "http://hl7.org/fhir/tools/StructureDefinition/obligation"); 3249 if (ts.hasTargetProfile()) { 3250 // check that any derived target has a reference chain back to one of the base target profiles 3251 for (UriType u : ts.getTargetProfile()) { 3252 String url = u.getValue(); 3253 boolean tgtOk = !td.hasTargetProfile() || sdConformsToTargets(path, derived.getPath(), url, td); 3254 if (tgtOk) { 3255 ok = true; 3256 } else if (specialising) { 3257 ok = true; 3258 } else { 3259 addMessage(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, derived.getPath(), context.formatMessage(I18nConstants.ERROR_AT__THE_TARGET_PROFILE__IS_NOT__VALID_CONSTRAINT_ON_THE_BASE_, purl, derived.getPath(), url, td.getTargetProfile()), IssueSeverity.ERROR)); 3260 } 3261 } 3262 } else { 3263 ok = true; 3264 } 3265 } 3266 } 3267 if (!ok && !isSuppressIgnorableExceptions()) { 3268 throw new DefinitionException(context.formatMessage(I18nConstants.STRUCTUREDEFINITION__AT__ILLEGAL_CONSTRAINED_TYPE__FROM__IN_, purl, derived.getPath(), tDesc, b.toString(), srcSD.getUrl())); 3269 } 3270 } 3271 3272 3273 private boolean sdConformsToTargets(String path, String dPath, String url, TypeRefComponent td) { 3274 if (td.hasTargetProfile(url)) { 3275 return true; 3276 } 3277 if (url != null && url.contains("|") && td.hasTargetProfile(url.substring(0, url.indexOf("|")))) { 3278 return true; 3279 } 3280 StructureDefinition sd = context.fetchResourceRaw(StructureDefinition.class, url); 3281 if (sd == null) { 3282 addMessage(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, "Cannot check whether the target profile " + url + " on "+dPath+" is valid constraint on the base because it is not known", IssueSeverity.WARNING)); 3283 return true; 3284 } else { 3285 if (sd.hasBaseDefinition() && sdConformsToTargets(path, dPath, sd.getBaseDefinition(), td)) { 3286 return true; 3287 } 3288 for (Extension ext : sd.getExtensionsByUrl(ExtensionDefinitions.EXT_SD_IMPOSE_PROFILE)) { 3289 if (sdConformsToTargets(path, dPath, ext.getValueCanonicalType().asStringValue(), td)) { 3290 return true; 3291 } 3292 } 3293 } 3294 return false; 3295 } 3296 3297 private void checkTypeOk(ElementDefinition dest, String ft, StructureDefinition sd, String fieldName) { 3298 boolean ok = false; 3299 Set<String> types = new HashSet<>(); 3300 if (dest.getPath().contains(".")) { 3301 for (TypeRefComponent t : dest.getType()) { 3302 if (t.hasCode()) { 3303 types.add(t.getWorkingCode()); 3304 } 3305 ok = ok || ft.equals(t.getWorkingCode()); 3306 } 3307 } else { 3308 types.add(sd.getType()); 3309 ok = ok || ft.equals(sd.getType()); 3310 3311 } 3312 if (!ok) { 3313 addMessage(new ValidationMessage(Source.InstanceValidator, IssueType.CONFLICT, dest.getId(), "The "+fieldName+" value has type '"+ft+"' which is not valid (valid "+Utilities.pluralize("type", dest.getType().size())+": "+types.toString()+")", IssueSeverity.ERROR)); 3314 } 3315 } 3316 3317 private boolean hasBindableType(ElementDefinition ed) { 3318 for (TypeRefComponent tr : ed.getType()) { 3319 if (Utilities.existsInList(tr.getWorkingCode(), "Coding", "CodeableConcept", "Quantity", "uri", "string", "code", "CodeableReference")) { 3320 return true; 3321 } 3322 StructureDefinition sd = context.fetchTypeDefinition(tr.getCode()); 3323 if (sd != null && sd.hasExtension(ExtensionDefinitions.EXT_BINDING_STYLE)) { 3324 return true; 3325 } 3326 if (sd != null && sd.hasExtension(ExtensionDefinitions.EXT_TYPE_CHARACTERISTICS) && 3327 "can-bind".equals(ExtensionUtilities.readStringExtension(sd, ExtensionDefinitions.EXT_TYPE_CHARACTERISTICS))) { 3328 return true; 3329 } 3330 } 3331 return false; 3332 } 3333 3334 3335 private boolean isLargerMax(String derived, String base) { 3336 if ("*".equals(base)) { 3337 return false; 3338 } 3339 if ("*".equals(derived)) { 3340 return true; 3341 } 3342 return Integer.parseInt(derived) > Integer.parseInt(base); 3343 } 3344 3345 3346 private String checkSubset(ValueSet expBase, ValueSet expDerived) { 3347 Set<String> codes = new HashSet<>(); 3348 checkCodesInExpansion(codes, expDerived.getExpansion().getContains(), expBase.getExpansion()); 3349 if (codes.isEmpty()) { 3350 return null; 3351 } else { 3352 return "The codes '"+CommaSeparatedStringBuilder.join(",", codes)+"' are not in the base valueset"; 3353 } 3354 } 3355 3356 3357 private void checkCodesInExpansion(Set<String> codes, List<ValueSetExpansionContainsComponent> contains, ValueSetExpansionComponent expansion) { 3358 for (ValueSetExpansionContainsComponent cc : contains) { 3359 if (!inExpansion(cc, expansion.getContains())) { 3360 codes.add(cc.getCode()); 3361 } 3362 checkCodesInExpansion(codes, cc.getContains(), expansion); 3363 } 3364 } 3365 3366 3367 private boolean inExpansion(ValueSetExpansionContainsComponent cc, List<ValueSetExpansionContainsComponent> contains) { 3368 for (ValueSetExpansionContainsComponent cc1 : contains) { 3369 if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode())) { 3370 return true; 3371 } 3372 if (inExpansion(cc, cc1.getContains())) { 3373 return true; 3374 } 3375 } 3376 return false; 3377 } 3378 3379 public void closeDifferential(StructureDefinition base, StructureDefinition derived) throws FHIRException { 3380 for (ElementDefinition edb : base.getSnapshot().getElement()) { 3381 if (isImmediateChild(edb) && !edb.getPath().endsWith(".id")) { 3382 ElementDefinition edm = getMatchInDerived(edb, derived.getDifferential().getElement()); 3383 if (edm == null) { 3384 ElementDefinition edd = derived.getDifferential().addElement(); 3385 edd.setPath(edb.getPath()); 3386 edd.setMax("0"); 3387 } else if (edb.hasSlicing()) { 3388 closeChildren(base, edb, derived, edm); 3389 } 3390 } 3391 } 3392 sortDifferential(base, derived, derived.getName(), new ArrayList<String>(), false); 3393 } 3394 3395 private void closeChildren(StructureDefinition base, ElementDefinition edb, StructureDefinition derived, ElementDefinition edm) { 3396// String path = edb.getPath()+"."; 3397 int baseStart = base.getSnapshot().getElement().indexOf(edb); 3398 int baseEnd = findEnd(base.getSnapshot().getElement(), edb, baseStart+1); 3399 int diffStart = derived.getDifferential().getElement().indexOf(edm); 3400 int diffEnd = findEnd(derived.getDifferential().getElement(), edm, diffStart+1); 3401 3402 for (int cBase = baseStart; cBase < baseEnd; cBase++) { 3403 ElementDefinition edBase = base.getSnapshot().getElement().get(cBase); 3404 if (isImmediateChild(edBase, edb)) { 3405 ElementDefinition edMatch = getMatchInDerived(edBase, derived.getDifferential().getElement(), diffStart, diffEnd); 3406 if (edMatch == null) { 3407 ElementDefinition edd = derived.getDifferential().addElement(); 3408 edd.setPath(edBase.getPath()); 3409 edd.setMax("0"); 3410 } else { 3411 closeChildren(base, edBase, derived, edMatch); 3412 } 3413 } 3414 } 3415 } 3416 3417 private int findEnd(List<ElementDefinition> list, ElementDefinition ed, int cursor) { 3418 String path = ed.getPath()+"."; 3419 while (cursor < list.size() && list.get(cursor).getPath().startsWith(path)) { 3420 cursor++; 3421 } 3422 return cursor; 3423 } 3424 3425 3426 private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list) { 3427 for (ElementDefinition t : list) { 3428 if (t.getPath().equals(ed.getPath())) { 3429 return t; 3430 } 3431 } 3432 return null; 3433 } 3434 3435 private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list, int start, int end) { 3436 for (int i = start; i < end; i++) { 3437 ElementDefinition t = list.get(i); 3438 if (t.getPath().equals(ed.getPath())) { 3439 return t; 3440 } 3441 } 3442 return null; 3443 } 3444 3445 3446 private boolean isImmediateChild(ElementDefinition ed) { 3447 String p = ed.getPath(); 3448 if (!p.contains(".")) { 3449 return false; 3450 } 3451 p = p.substring(p.indexOf(".")+1); 3452 return !p.contains("."); 3453 } 3454 3455 private boolean isImmediateChild(ElementDefinition candidate, ElementDefinition base) { 3456 String p = candidate.getPath(); 3457 if (!p.contains(".")) 3458 return false; 3459 if (!p.startsWith(base.getPath()+".")) 3460 return false; 3461 p = p.substring(base.getPath().length()+1); 3462 return !p.contains("."); 3463 } 3464 3465 3466 3467 private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) { 3468 int i = ed.getSnapshot().getElement().indexOf(c) + 1; 3469 while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) { 3470 if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath()+".url")) 3471 return ed.getSnapshot().getElement().get(i); 3472 i++; 3473 } 3474 return null; 3475 } 3476 3477 3478 3479 protected ElementDefinitionResolution getElementById(StructureDefinition source, List<ElementDefinition> elements, String contentReference) { 3480 if (!contentReference.startsWith("#") && contentReference.contains("#")) { 3481 String url = contentReference.substring(0, contentReference.indexOf("#")); 3482 contentReference = contentReference.substring(contentReference.indexOf("#")); 3483 if (!url.equals(source.getUrl())){ 3484 source = context.fetchResource(StructureDefinition.class, url, source); 3485 if (source == null) { 3486 return null; 3487 } 3488 elements = source.getSnapshot().getElement(); 3489 } 3490 } 3491 for (ElementDefinition ed : elements) 3492 if (ed.hasId() && ("#"+ed.getId()).equals(contentReference)) 3493 return new ElementDefinitionResolution(source, ed); 3494 return null; 3495 } 3496 3497 3498 public static String describeExtensionContext(StructureDefinition ext) { 3499 StringBuilder b = new StringBuilder(); 3500 b.append("Use on "); 3501 for (int i = 0; i < ext.getContext().size(); i++) { 3502 StructureDefinitionContextComponent ec = ext.getContext().get(i); 3503 if (i > 0) 3504 b.append(i < ext.getContext().size() - 1 ? ", " : " or "); 3505 b.append(ec.getType().getDisplay()); 3506 b.append(" "); 3507 b.append(ec.getExpression()); 3508 } 3509 if (ext.hasContextInvariant()) { 3510 b.append(", with <a href=\"structuredefinition-definitions.html#StructureDefinition.contextInvariant\">Context Invariant</a> = "); 3511 boolean first = true; 3512 for (StringType s : ext.getContextInvariant()) { 3513 if (first) 3514 first = false; 3515 else 3516 b.append(", "); 3517 b.append("<code>"+s.getValue()+"</code>"); 3518 } 3519 } 3520 return b.toString(); 3521 } 3522 3523 3524 3525 3526// public XhtmlNode generateTable(String defFile, StructureDefinition profile, boolean diff, String imageFolder, boolean inlineGraphics, String profileBaseFileName, boolean snapshot, String corePath, String imagePath, 3527// boolean logicalModel, boolean allInvariants, Set<String> outputTracker, boolean mustSupport, RenderingContext rc) throws IOException, FHIRException { 3528// return generateTable(defFile, profile, diff, imageFolder, inlineGraphics, profileBaseFileName, snapshot, corePath, imagePath, logicalModel, allInvariants, outputTracker, mustSupport, rc, ""); 3529// } 3530 3531 3532 3533 3534 3535 protected String tail(String path) { 3536 if (path == null) { 3537 return ""; 3538 } else if (path.contains(".")) 3539 return path.substring(path.lastIndexOf('.')+1); 3540 else 3541 return path; 3542 } 3543 3544 private boolean isDataType(String value) { 3545 StructureDefinition sd = context.fetchTypeDefinition(value); 3546 if (sd == null) // might be running before all SDs are available 3547 return Utilities.existsInList(value, "Address", "Age", "Annotation", "Attachment", "CodeableConcept", "Coding", "ContactPoint", "Count", "Distance", "Duration", "HumanName", "Identifier", "Money", "Period", "Quantity", "Range", "Ratio", "Reference", "SampledData", "Signature", "Timing", 3548 "ContactDetail", "Contributor", "DataRequirement", "Expression", "ParameterDefinition", "RelatedArtifact", "TriggerDefinition", "UsageContext"); 3549 else 3550 return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION; 3551 } 3552 3553 private boolean isConstrainedDataType(String value) { 3554 StructureDefinition sd = context.fetchTypeDefinition(value); 3555 if (sd == null) // might be running before all SDs are available 3556 return Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity"); 3557 else 3558 return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.CONSTRAINT; 3559 } 3560 3561 private String baseType(String value) { 3562 StructureDefinition sd = context.fetchTypeDefinition(value); 3563 if (sd != null) // might be running before all SDs are available 3564 return sd.getTypeName(); 3565 if (Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity")) 3566 return "Quantity"; 3567 throw new Error(context.formatMessage(I18nConstants.INTERNAL_ERROR___TYPE_NOT_KNOWN_, value)); 3568 } 3569 3570 3571 protected boolean isPrimitive(String value) { 3572 StructureDefinition sd = context.fetchTypeDefinition(value); 3573 if (sd == null) // might be running before all SDs are available 3574 return Utilities.existsInList(value, "base64Binary", "boolean", "canonical", "code", "date", "dateTime", "decimal", "id", "instant", "integer", "integer64", "markdown", "oid", "positiveInt", "string", "time", "unsignedInt", "uri", "url", "uuid"); 3575 else 3576 return sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 3577 } 3578 3579// private static String listStructures(StructureDefinition p) { 3580// StringBuilder b = new StringBuilder(); 3581// boolean first = true; 3582// for (ProfileStructureComponent s : p.getStructure()) { 3583// if (first) 3584// first = false; 3585// else 3586// b.append(", "); 3587// if (pkp != null && pkp.hasLinkFor(s.getType())) 3588// b.append("<a href=\""+pkp.getLinkFor(s.getType())+"\">"+s.getType()+"</a>"); 3589// else 3590// b.append(s.getType()); 3591// } 3592// return b.toString(); 3593// } 3594 3595 3596 public StructureDefinition getProfile(StructureDefinition source, String url) { 3597 StructureDefinition profile = null; 3598 String code = null; 3599 if (url.startsWith("#")) { 3600 profile = source; 3601 code = url.substring(1); 3602 } else if (context != null) { 3603 String[] parts = url.split("\\#"); 3604 profile = context.fetchResource(StructureDefinition.class, parts[0], source); 3605 code = parts.length == 1 ? null : parts[1]; 3606 } 3607 if (profile == null) 3608 return null; 3609 if (code == null) 3610 return profile; 3611 for (Resource r : profile.getContained()) { 3612 if (r instanceof StructureDefinition && r.getId().equals(code)) 3613 return (StructureDefinition) r; 3614 } 3615 return null; 3616 } 3617 3618 3619 3620 private static class ElementDefinitionHolder { 3621 private String name; 3622 private ElementDefinition self; 3623 private int baseIndex = 0; 3624 private List<ElementDefinitionHolder> children; 3625 private boolean placeHolder = false; 3626 3627 public ElementDefinitionHolder(ElementDefinition self, boolean isPlaceholder) { 3628 super(); 3629 this.self = self; 3630 this.name = self.getPath(); 3631 this.placeHolder = isPlaceholder; 3632 children = new ArrayList<ElementDefinitionHolder>(); 3633 } 3634 3635 public ElementDefinitionHolder(ElementDefinition self) { 3636 this(self, false); 3637 } 3638 3639 public ElementDefinition getSelf() { 3640 return self; 3641 } 3642 3643 public List<ElementDefinitionHolder> getChildren() { 3644 return children; 3645 } 3646 3647 public int getBaseIndex() { 3648 return baseIndex; 3649 } 3650 3651 public void setBaseIndex(int baseIndex) { 3652 this.baseIndex = baseIndex; 3653 } 3654 3655 public boolean isPlaceHolder() { 3656 return this.placeHolder; 3657 } 3658 3659 @Override 3660 public String toString() { 3661 if (self.hasSliceName()) 3662 return self.getPath()+"("+self.getSliceName()+")"; 3663 else 3664 return self.getPath(); 3665 } 3666 } 3667 3668 private static class ElementDefinitionComparer implements Comparator<ElementDefinitionHolder> { 3669 3670 private boolean inExtension; 3671 private List<ElementDefinition> snapshot; 3672 private int prefixLength; 3673 private String base; 3674 private String name; 3675 private String baseName; 3676 private Set<String> errors = new HashSet<String>(); 3677 3678 public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base, int prefixLength, String name, String baseName) { 3679 this.inExtension = inExtension; 3680 this.snapshot = snapshot; 3681 this.prefixLength = prefixLength; 3682 this.base = base; 3683 if (Utilities.isAbsoluteUrl(base)) { 3684 this.base = urlTail(base); 3685 } 3686 this.name = name; 3687 this.baseName = baseName; 3688 } 3689 3690 @Override 3691 public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) { 3692 if (o1.getBaseIndex() == 0) { 3693 o1.setBaseIndex(find(o1.getSelf().getPath(), true)); 3694 } 3695 if (o2.getBaseIndex() == 0) { 3696 o2.setBaseIndex(find(o2.getSelf().getPath(), true)); 3697 } 3698 return o1.getBaseIndex() - o2.getBaseIndex(); 3699 } 3700 3701 private int find(String path, boolean mandatory) { 3702 String op = path; 3703 int lc = 0; 3704 String actual = base+path.substring(prefixLength); 3705 for (int i = 0; i < snapshot.size(); i++) { 3706 String p = snapshot.get(i).getPath(); 3707 if (p.equals(actual)) { 3708 return i; 3709 } 3710 if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length()-3)) && !(actual.endsWith("[x]")) && !actual.substring(p.length()-3).contains(".")) { 3711 return i; 3712 } 3713 if (actual.endsWith("[x]") && p.startsWith(actual.substring(0, actual.length()-3)) && !p.substring(actual.length()-3).contains(".")) { 3714 return i; 3715 } 3716 if (path.startsWith(p+".") && snapshot.get(i).hasContentReference()) { 3717 String ref = snapshot.get(i).getContentReference(); 3718 if (ref.substring(1, 2).toUpperCase().equals(ref.substring(1,2))) { 3719 actual = base+(ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength); 3720 path = actual; 3721 } else if (ref.startsWith("http:")) { 3722 actual = base+(ref.substring(ref.indexOf("#")+1)+"."+path.substring(p.length()+1)).substring(prefixLength); 3723 path = actual; 3724 } else { 3725 // Older versions of FHIR (e.g. 2016May) had reference of the style #parameter instead of #Parameters.parameter, so we have to handle that 3726 actual = base+(path.substring(0, path.indexOf(".")+1) + ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength); 3727 path = actual; 3728 } 3729 3730 i = 0; 3731 lc++; 3732 if (lc > MAX_RECURSION_LIMIT) 3733 throw new Error("Internal recursion detection: find() loop path recursion > "+MAX_RECURSION_LIMIT+" - check paths are valid (for path "+path+"/"+op+")"); 3734 } 3735 } 3736 if (mandatory) { 3737 if (prefixLength == 0) 3738 errors.add("Differential contains path "+path+" which is not found in the base "+baseName); 3739 else 3740 errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the in base "+ baseName); 3741 } 3742 return 0; 3743 } 3744 3745 public void checkForErrors(List<String> errorList) { 3746 if (errors.size() > 0) { 3747// CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 3748// for (String s : errors) 3749// b.append("StructureDefinition "+name+": "+s); 3750// throw new DefinitionException(b.toString()); 3751 for (String s : errors) 3752 if (s.startsWith("!")) 3753 errorList.add("!StructureDefinition "+name+": "+s.substring(1)); 3754 else 3755 errorList.add("StructureDefinition "+name+": "+s); 3756 } 3757 } 3758 } 3759 3760 3761 public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List<String> errors, boolean errorIfChanges) throws FHIRException { 3762 int index = 0; 3763 for (ElementDefinition ed : diff.getDifferential().getElement()) { 3764 ed.setUserData(UserDataNames.SNAPSHOT_SORT_ed_index, Integer.toString(index)); 3765 index++; 3766 } 3767 List<ElementDefinition> original = new ArrayList<>(); 3768 original.addAll(diff.getDifferential().getElement()); 3769 final List<ElementDefinition> diffList = diff.getDifferential().getElement(); 3770 int lastCount = diffList.size(); 3771 // first, we move the differential elements into a tree 3772 if (diffList.isEmpty()) 3773 return; 3774 3775 ElementDefinitionHolder edh = null; 3776 int i = 0; 3777 if (diffList.get(0).getPath().contains(".")) { 3778 String newPath = diffList.get(0).getPath().split("\\.")[0]; 3779 ElementDefinition e = new ElementDefinition(newPath); 3780 edh = new ElementDefinitionHolder(e, true); 3781 } else { 3782 edh = new ElementDefinitionHolder(diffList.get(0)); 3783 i = 1; 3784 } 3785 3786 boolean hasSlicing = false; 3787 List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly 3788 for(ElementDefinition elt : diffList) { 3789 if (elt.hasSlicing() || paths.contains(elt.getPath())) { 3790 hasSlicing = true; 3791 break; 3792 } 3793 paths.add(elt.getPath()); 3794 } 3795 3796 processElementsIntoTree(edh, i, diff.getDifferential().getElement()); 3797 3798 // now, we sort the siblings throughout the tree 3799 ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name, base.getType()); 3800 sortElements(edh, cmp, errors); 3801 3802 // now, we serialise them back to a list 3803 List<ElementDefinition> newDiff = new ArrayList<>(); 3804 writeElements(edh, newDiff); 3805 if (errorIfChanges) { 3806 compareDiffs(original, newDiff, errors); 3807 } 3808 diffList.clear(); 3809 diffList.addAll(newDiff); 3810 3811 if (lastCount != diffList.size()) 3812 errors.add("Sort failed: counts differ; at least one of the paths in the differential is illegal"); 3813 } 3814 3815 private void compareDiffs(List<ElementDefinition> diffList, List<ElementDefinition> newDiff, List<String> errors) { 3816 if (diffList.size() != newDiff.size()) { 3817 errors.add("The diff list size changed when sorting - was "+diffList.size()+" is now "+newDiff.size()+ 3818 " ["+CommaSeparatedStringBuilder.buildObjects(diffList)+"]/["+CommaSeparatedStringBuilder.buildObjects(newDiff)+"]"); 3819 } else { 3820 for (int i = 0; i < Integer.min(diffList.size(), newDiff.size()); i++) { 3821 ElementDefinition e = diffList.get(i); 3822 ElementDefinition n = newDiff.get(i); 3823 if (!n.getPath().equals(e.getPath())) { 3824 errors.add("The element "+(e.hasId() ? e.getId() : e.getPath())+" @diff["+e.getUserString(UserDataNames.SNAPSHOT_SORT_ed_index)+"] is out of order (and maybe others after it)"); 3825 return; 3826 } 3827 } 3828 } 3829 } 3830 3831 3832 private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) { 3833 String path = edh.getSelf().getPath(); 3834 final String prefix = path + "."; 3835 while (i < list.size() && list.get(i).getPath().startsWith(prefix)) { 3836 if (list.get(i).getPath().substring(prefix.length()+1).contains(".")) { 3837 String newPath = prefix + list.get(i).getPath().substring(prefix.length()).split("\\.")[0]; 3838 ElementDefinition e = new ElementDefinition(newPath); 3839 ElementDefinitionHolder child = new ElementDefinitionHolder(e, true); 3840 edh.getChildren().add(child); 3841 i = processElementsIntoTree(child, i, list); 3842 3843 } else { 3844 ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i)); 3845 edh.getChildren().add(child); 3846 i = processElementsIntoTree(child, i+1, list); 3847 } 3848 } 3849 return i; 3850 } 3851 3852 private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) throws FHIRException { 3853 if (edh.getChildren().size() == 1) 3854 // special case - sort needsto allocate base numbers, but there'll be no sort if there's only 1 child. So in that case, we just go ahead and allocated base number directly 3855 edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath(), false); 3856 else 3857 Collections.sort(edh.getChildren(), cmp); 3858 if (debug) { 3859 cmp.checkForErrors(errors); 3860 } 3861 3862 for (ElementDefinitionHolder child : edh.getChildren()) { 3863 if (child.getChildren().size() > 0) { 3864 ElementDefinitionComparer ccmp = getComparer(cmp, child); 3865 if (ccmp != null) { 3866 sortElements(child, ccmp, errors); 3867 } 3868 } 3869 } 3870 } 3871 3872 public ElementDefinitionComparer getComparer(ElementDefinitionComparer cmp, ElementDefinitionHolder child) throws FHIRException, Error { 3873 // what we have to check for here is running off the base profile into a data type profile 3874 ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex()); 3875 ElementDefinitionComparer ccmp; 3876 if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getWorkingCode()) || ed.getType().get(0).getWorkingCode().equals(ed.getPath())) { 3877 if (ed.hasType() && "Resource".equals(ed.getType().get(0).getWorkingCode()) && (child.getSelf().hasType() && child.getSelf().getType().get(0).hasProfile())) { 3878 if (child.getSelf().getType().get(0).getProfile().size() > 1) { 3879 throw new FHIRException(context.formatMessage(I18nConstants.UNHANDLED_SITUATION_RESOURCE_IS_PROFILED_TO_MORE_THAN_ONE_OPTION__CANNOT_SORT_PROFILE)); 3880 } 3881 StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue()); 3882 while (profile != null && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) { 3883 profile = context.fetchResource(StructureDefinition.class, profile.getBaseDefinition()); 3884 } 3885 if (profile==null) { 3886 ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case 3887 } else { 3888 ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), profile.getType(), child.getSelf().getPath().length(), cmp.name, profile.present()); 3889 } 3890 } else { 3891 ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name, cmp.name); 3892 } 3893 } else if (ed.getType().get(0).getWorkingCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) { 3894 StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue()); 3895 if (profile==null) 3896 ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case 3897 else 3898 ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), resolveType(ed.getType().get(0).getWorkingCode()), child.getSelf().getPath().length(), cmp.name, profile.present()); 3899 } else if (ed.getType().size() == 1 && !ed.getType().get(0).getWorkingCode().equals("*")) { 3900 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode())); 3901 if (profile==null) 3902 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath())); 3903 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), resolveType(ed.getType().get(0).getWorkingCode()), child.getSelf().getPath().length(), cmp.name, profile.present()); 3904 } else if (child.getSelf().getType().size() == 1) { 3905 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(child.getSelf().getType().get(0).getWorkingCode())); 3906 if (profile==null) 3907 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath())); 3908 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), child.getSelf().getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present()); 3909 } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) { 3910 String edLastNode = ed.getPath().replaceAll("(.*\\.)*(.*)", "$2"); 3911 String childLastNode = child.getSelf().getPath().replaceAll("(.*\\.)*(.*)", "$2"); 3912 String p = childLastNode.substring(edLastNode.length()-3); 3913 if (isPrimitive(Utilities.uncapitalize(p))) 3914 p = Utilities.uncapitalize(p); 3915 StructureDefinition sd = context.fetchResource(StructureDefinition.class, sdNs(p)); 3916 if (sd == null) 3917 throw new Error(context.formatMessage(I18nConstants.UNABLE_TO_FIND_PROFILE__AT_, p, ed.getId())); 3918 ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), cmp.name, sd.present()); 3919 } else if (child.getSelf().hasType() && child.getSelf().getType().get(0).getWorkingCode().equals("Reference")) { 3920 for (TypeRefComponent t: child.getSelf().getType()) { 3921 if (!t.getWorkingCode().equals("Reference")) { 3922 throw new Error(context.formatMessage(I18nConstants.CANT_HAVE_CHILDREN_ON_AN_ELEMENT_WITH_A_POLYMORPHIC_TYPE__YOU_MUST_SLICE_AND_CONSTRAIN_THE_TYPES_FIRST_SORTELEMENTS_, ed.getPath(), typeCode(ed.getType()))); 3923 } 3924 } 3925 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode())); 3926 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present()); 3927 } else if (!child.getSelf().hasType() && ed.getType().get(0).getWorkingCode().equals("Reference")) { 3928 for (TypeRefComponent t: ed.getType()) { 3929 if (!t.getWorkingCode().equals("Reference")) { 3930 throw new Error(context.formatMessage(I18nConstants.NOT_HANDLED_YET_SORTELEMENTS_, ed.getPath(), typeCode(ed.getType()))); 3931 } 3932 } 3933 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode())); 3934 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present()); 3935 } else { 3936 // this is allowed if we only profile the extensions 3937 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs("Element")); 3938 if (profile==null) 3939 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath())); 3940 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), "Element", child.getSelf().getPath().length(), cmp.name, profile.present()); 3941// throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")"); 3942 } 3943 return ccmp; 3944 } 3945 3946 private String resolveType(String code) { 3947 if (Utilities.isAbsoluteUrl(code)) { 3948 StructureDefinition sd = context.fetchResource(StructureDefinition.class, code); 3949 if (sd != null) { 3950 return sd.getType(); 3951 } 3952 } 3953 return code; 3954 } 3955 3956 private static String sdNs(String type) { 3957 return sdNs(type, null); 3958 } 3959 3960 public static String sdNs(String type, String overrideVersionNs) { 3961 if (Utilities.isAbsoluteUrl(type)) 3962 return type; 3963 else if (overrideVersionNs != null) 3964 return Utilities.pathURL(overrideVersionNs, type); 3965 else 3966 return "http://hl7.org/fhir/StructureDefinition/"+type; 3967 } 3968 3969 3970 private boolean isAbstract(String code) { 3971 return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") || code.equals("DomainResource"); 3972 } 3973 3974 3975 private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) { 3976 if (!edh.isPlaceHolder()) 3977 list.add(edh.getSelf()); 3978 for (ElementDefinitionHolder child : edh.getChildren()) { 3979 writeElements(child, list); 3980 } 3981 } 3982 3983 /** 3984 * First compare element by path then by name if same 3985 */ 3986 private static class ElementNameCompare implements Comparator<ElementDefinition> { 3987 3988 @Override 3989 public int compare(ElementDefinition o1, ElementDefinition o2) { 3990 String path1 = normalizePath(o1); 3991 String path2 = normalizePath(o2); 3992 int cmp = path1.compareTo(path2); 3993 if (cmp == 0) { 3994 String name1 = o1.hasSliceName() ? o1.getSliceName() : ""; 3995 String name2 = o2.hasSliceName() ? o2.getSliceName() : ""; 3996 cmp = name1.compareTo(name2); 3997 } 3998 return cmp; 3999 } 4000 4001 private static String normalizePath(ElementDefinition e) { 4002 if (!e.hasPath()) return ""; 4003 String path = e.getPath(); 4004 // if sorting element names make sure onset[x] appears before onsetAge, onsetDate, etc. 4005 // so strip off the [x] suffix when comparing the path names. 4006 if (path.endsWith("[x]")) { 4007 path = path.substring(0, path.length()-3); 4008 } 4009 return path; 4010 } 4011 4012 } 4013 4014 4015 // generate schematrons for the rules in a structure definition 4016 public void generateSchematrons(OutputStream dest, StructureDefinition structure) throws IOException, DefinitionException { 4017 if (structure.getDerivation() != TypeDerivationRule.CONSTRAINT) 4018 throw new DefinitionException(context.formatMessage(I18nConstants.NOT_THE_RIGHT_KIND_OF_STRUCTURE_TO_GENERATE_SCHEMATRONS_FOR)); 4019 if (!structure.hasSnapshot()) 4020 throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT)); 4021 4022 StructureDefinition base = context.fetchResource(StructureDefinition.class, structure.getBaseDefinition(), structure); 4023 4024 if (base != null) { 4025 SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName()); 4026 4027 ElementDefinition ed = structure.getSnapshot().getElement().get(0); 4028 generateForChildren(sch, "f:"+ed.getPath(), ed, structure, base); 4029 sch.dump(); 4030 } 4031 } 4032 4033 // generate a CSV representation of the structure definition 4034 public void generateCsv(OutputStream dest, StructureDefinition structure, boolean asXml) throws IOException, DefinitionException, Exception { 4035 if (!structure.hasSnapshot()) 4036 throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT)); 4037 4038 CSVWriter csv = new CSVWriter(dest, structure, asXml); 4039 4040 for (ElementDefinition child : structure.getSnapshot().getElement()) { 4041 csv.processElement(null, child); 4042 } 4043 csv.dump(); 4044 } 4045 4046 // generate a CSV representation of the structure definition 4047 public void addToCSV(CSVWriter csv, StructureDefinition structure) throws IOException, DefinitionException, Exception { 4048 if (!structure.hasSnapshot()) 4049 throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT)); 4050 4051 for (ElementDefinition child : structure.getSnapshot().getElement()) { 4052 csv.processElement(structure, child); 4053 } 4054 } 4055 4056 4057 private class Slicer extends ElementDefinitionSlicingComponent { 4058 String criteria = ""; 4059 String name = ""; 4060 boolean check; 4061 public Slicer(boolean cantCheck) { 4062 super(); 4063 this.check = cantCheck; 4064 } 4065 } 4066 4067 private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, StructureDefinition structure) { 4068 // given a child in a structure, it's sliced. figure out the slicing xpath 4069 if (child.getPath().endsWith(".extension")) { 4070 ElementDefinition ued = getUrlFor(structure, child); 4071 if ((ued == null || !ued.hasFixed()) && !(child.hasType() && (child.getType().get(0).hasProfile()))) 4072 return new Slicer(false); 4073 else { 4074 Slicer s = new Slicer(true); 4075 String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile().get(0).getValue() : ((UriType) ued.getFixed()).asStringValue(); 4076 s.name = " with URL = '"+url+"'"; 4077 s.criteria = "[@url = '"+url+"']"; 4078 return s; 4079 } 4080 } else 4081 return new Slicer(false); 4082 } 4083 4084 private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed, StructureDefinition structure, StructureDefinition base) throws IOException { 4085 // generateForChild(txt, structure, child); 4086 List<ElementDefinition> children = getChildList(structure, ed); 4087 String sliceName = null; 4088 ElementDefinitionSlicingComponent slicing = null; 4089 for (ElementDefinition child : children) { 4090 String name = tail(child.getPath()); 4091 if (child.hasSlicing()) { 4092 sliceName = name; 4093 slicing = child.getSlicing(); 4094 } else if (!name.equals(sliceName)) 4095 slicing = null; 4096 4097 ElementDefinition based = getByPath(base, child.getPath()); 4098 boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin())); 4099 boolean doMax = child.hasMax() && !child.getMax().equals("*") && (based == null || (!child.getMax().equals(based.getMax()))); 4100 Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure); 4101 if (slicer.check) { 4102 if (doMin || doMax) { 4103 Section s = sch.section(xpath); 4104 Rule r = s.rule(xpath); 4105 if (doMin) 4106 r.assrt("count(f:"+name+slicer.criteria+") >= "+Integer.toString(child.getMin()), name+slicer.name+": minimum cardinality of '"+name+"' is "+Integer.toString(child.getMin())); 4107 if (doMax) 4108 r.assrt("count(f:"+name+slicer.criteria+") <= "+child.getMax(), name+slicer.name+": maximum cardinality of '"+name+"' is "+child.getMax()); 4109 } 4110 } 4111 } 4112/// xpath has been removed 4113// for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) { 4114// if (inv.hasXpath()) { 4115// Section s = sch.section(ed.getPath()); 4116// Rule r = s.rule(xpath); 4117// r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId()+": " : "")+inv.getHuman()+(inv.hasUserData(IS_DERIVED) ? " (inherited)" : "")); 4118// } 4119// } 4120 if (!ed.hasContentReference()) { 4121 for (ElementDefinition child : children) { 4122 String name = tail(child.getPath()); 4123 generateForChildren(sch, xpath+"/f:"+name, child, structure, base); 4124 } 4125 } 4126 } 4127 4128 4129 4130 4131 private ElementDefinition getByPath(StructureDefinition base, String path) { 4132 for (ElementDefinition ed : base.getSnapshot().getElement()) { 4133 if (ed.getPath().equals(path)) 4134 return ed; 4135 if (ed.getPath().endsWith("[x]") && ed.getPath().length() <= path.length()-3 && ed.getPath().substring(0, ed.getPath().length()-3).equals(path.substring(0, ed.getPath().length()-3))) 4136 return ed; 4137 } 4138 return null; 4139 } 4140 4141 4142 public void setIds(StructureDefinition sd, boolean checkFirst) throws DefinitionException { 4143 if (!checkFirst || !sd.hasDifferential() || hasMissingIds(sd.getDifferential().getElement())) { 4144 if (!sd.hasDifferential()) 4145 sd.setDifferential(new StructureDefinitionDifferentialComponent()); 4146 generateIds(sd.getDifferential().getElement(), sd.getUrl(), sd.getType(), sd); 4147 } 4148 if (!checkFirst || !sd.hasSnapshot() || hasMissingIds(sd.getSnapshot().getElement())) { 4149 if (!sd.hasSnapshot()) 4150 sd.setSnapshot(new StructureDefinitionSnapshotComponent()); 4151 generateIds(sd.getSnapshot().getElement(), sd.getUrl(), sd.getType(), sd); 4152 } 4153 } 4154 4155 4156 private boolean hasMissingIds(List<ElementDefinition> list) { 4157 for (ElementDefinition ed : list) { 4158 if (!ed.hasId()) 4159 return true; 4160 } 4161 return false; 4162 } 4163 4164 private class SliceList { 4165 4166 private Map<String, String> slices = new HashMap<>(); 4167 4168 public void seeElement(ElementDefinition ed) { 4169 Iterator<Map.Entry<String,String>> iter = slices.entrySet().iterator(); 4170 while (iter.hasNext()) { 4171 Map.Entry<String,String> entry = iter.next(); 4172 if (entry.getKey().length() > ed.getPath().length() || entry.getKey().equals(ed.getPath())) 4173 iter.remove(); 4174 } 4175 4176 if (ed.hasSliceName()) 4177 slices.put(ed.getPath(), ed.getSliceName()); 4178 } 4179 4180 public String[] analyse(List<String> paths) { 4181 String s = paths.get(0); 4182 String[] res = new String[paths.size()]; 4183 res[0] = null; 4184 for (int i = 1; i < paths.size(); i++) { 4185 s = s + "."+paths.get(i); 4186 if (slices.containsKey(s)) 4187 res[i] = slices.get(s); 4188 else 4189 res[i] = null; 4190 } 4191 return res; 4192 } 4193 4194 } 4195 4196 protected void generateIds(List<ElementDefinition> list, String name, String type, StructureDefinition srcSD) throws DefinitionException { 4197 if (list.isEmpty()) 4198 return; 4199 4200 Map<String, String> idList = new HashMap<String, String>(); 4201 Map<String, String> replacedIds = new HashMap<String, String>(); 4202 4203 SliceList sliceInfo = new SliceList(); 4204 // first pass, update the element ids 4205 for (ElementDefinition ed : list) { 4206 generateIdForElement(list, name, type, srcSD, ed, sliceInfo, replacedIds, idList); 4207 } 4208 // second path - fix up any broken path based id references 4209 4210 } 4211 4212 private void generateIdForElement(List<ElementDefinition> list, String name, String type, StructureDefinition srcSD, ElementDefinition ed, SliceList sliceInfo, Map<String, String> replacedIds, Map<String, String> idList) { 4213 List<String> paths = new ArrayList<String>(); 4214 if (!ed.hasPath()) 4215 throw new DefinitionException(context.formatMessage(I18nConstants.NO_PATH_ON_ELEMENT_DEFINITION__IN_, Integer.toString(list.indexOf(ed)), name)); 4216 sliceInfo.seeElement(ed); 4217 String[] pl = ed.getPath().split("\\."); 4218 for (int i = paths.size(); i < pl.length; i++) // -1 because the last path is in focus 4219 paths.add(pl[i]); 4220 String slices[] = sliceInfo.analyse(paths); 4221 4222 StringBuilder b = new StringBuilder(); 4223 b.append(paths.get(0)); 4224 for (int i = 1; i < paths.size(); i++) { 4225 b.append("."); 4226 String s = paths.get(i); 4227 String p = slices[i]; 4228 b.append(fixChars(s)); 4229 if (p != null) { 4230 b.append(":"); 4231 b.append(p); 4232 } 4233 } 4234 String bs = b.toString(); 4235 if (ed.hasId()) { 4236 replacedIds.put(ed.getId(), ed.getPath()); 4237 } 4238 ed.setId(bs); 4239 if (idList.containsKey(bs)) { 4240 addMessage(new ValidationMessage(Source.ProfileValidator, IssueType.BUSINESSRULE, name +"."+bs, context.formatMessage(I18nConstants.SAME_ID_ON_MULTIPLE_ELEMENTS__IN_, bs, idList.get(bs), ed.getPath(), name), IssueSeverity.ERROR)); 4241 } 4242 idList.put(bs, ed.getPath()); 4243 if (ed.hasContentReference() && ed.getContentReference().startsWith("#")) { 4244 String s = ed.getContentReference(); 4245 String typeURL = getUrlForSource(type, srcSD); 4246 if (replacedIds.containsKey(s.substring(1))) { 4247 ed.setContentReference(typeURL+"#"+ replacedIds.get(s.substring(1))); 4248 } else { 4249 ed.setContentReference(typeURL+s); 4250 } 4251 } 4252 } 4253 4254 4255 private String getUrlForSource(String type, StructureDefinition srcSD) { 4256 if (srcSD.getKind() == StructureDefinitionKind.LOGICAL) { 4257 return srcSD.getUrl(); 4258 } else { 4259 return "http://hl7.org/fhir/StructureDefinition/"+type; 4260 } 4261 } 4262 4263 private Object fixChars(String s) { 4264 return s.replace("_", "-"); 4265 } 4266 4267 4268// private String describeExtension(ElementDefinition ed) { 4269// if (!ed.hasType() || !ed.getTypeFirstRep().hasProfile()) 4270// return ""; 4271// return "$"+urlTail(ed.getTypeFirstRep().getProfile()); 4272// } 4273// 4274 4275 private static String urlTail(String profile) { 4276 return profile.contains("/") ? profile.substring(profile.lastIndexOf("/")+1) : profile; 4277 } 4278// 4279// 4280// private String checkName(String name) { 4281//// if (name.contains(".")) 4282////// throw new Exception("Illegal name "+name+": no '.'"); 4283//// if (name.contains(" ")) 4284//// throw new Exception("Illegal name "+name+": no spaces"); 4285// StringBuilder b = new StringBuilder(); 4286// for (char c : name.toCharArray()) { 4287// if (!Utilities.existsInList(c, '.', ' ', ':', '"', '\'', '(', ')', '&', '[', ']')) 4288// b.append(c); 4289// } 4290// return b.toString().toLowerCase(); 4291// } 4292// 4293// 4294// private int charCount(String path, char t) { 4295// int res = 0; 4296// for (char ch : path.toCharArray()) { 4297// if (ch == t) 4298// res++; 4299// } 4300// return res; 4301// } 4302 4303// 4304//private void generateForChild(TextStreamWriter txt, 4305// StructureDefinition structure, ElementDefinition child) { 4306// // TODO Auto-generated method stub 4307// 4308//} 4309 4310 private interface ExampleValueAccessor { 4311 DataType getExampleValue(ElementDefinition ed); 4312 String getId(); 4313 } 4314 4315 private class BaseExampleValueAccessor implements ExampleValueAccessor { 4316 @Override 4317 public DataType getExampleValue(ElementDefinition ed) { 4318 if (ed.hasFixed()) 4319 return ed.getFixed(); 4320 if (ed.hasExample()) 4321 return ed.getExample().get(0).getValue(); 4322 else 4323 return null; 4324 } 4325 4326 @Override 4327 public String getId() { 4328 return "-genexample"; 4329 } 4330 } 4331 4332 private class ExtendedExampleValueAccessor implements ExampleValueAccessor { 4333 private String index; 4334 4335 public ExtendedExampleValueAccessor(String index) { 4336 this.index = index; 4337 } 4338 @Override 4339 public DataType getExampleValue(ElementDefinition ed) { 4340 if (ed.hasFixed()) 4341 return ed.getFixed(); 4342 for (Extension ex : ed.getExtension()) { 4343 String ndx = ExtensionUtilities.readStringExtension(ex, "index"); 4344 DataType value = ExtensionUtilities.getExtension(ex, "exValue").getValue(); 4345 if (index.equals(ndx) && value != null) 4346 return value; 4347 } 4348 return null; 4349 } 4350 @Override 4351 public String getId() { 4352 return "-genexample-"+index; 4353 } 4354 } 4355 4356 public List<org.hl7.fhir.r5.elementmodel.Element> generateExamples(StructureDefinition sd, boolean evenWhenNoExamples) throws FHIRException { 4357 List<org.hl7.fhir.r5.elementmodel.Element> examples = new ArrayList<org.hl7.fhir.r5.elementmodel.Element>(); 4358 if (sd.hasSnapshot()) { 4359 if (evenWhenNoExamples || hasAnyExampleValues(sd)) 4360 examples.add(generateExample(sd, new BaseExampleValueAccessor())); 4361 for (int i = 1; i <= 50; i++) { 4362 if (hasAnyExampleValues(sd, Integer.toString(i))) 4363 examples.add(generateExample(sd, new ExtendedExampleValueAccessor(Integer.toString(i)))); 4364 } 4365 } 4366 return examples; 4367 } 4368 4369 private org.hl7.fhir.r5.elementmodel.Element generateExample(StructureDefinition profile, ExampleValueAccessor accessor) throws FHIRException { 4370 ElementDefinition ed = profile.getSnapshot().getElementFirstRep(); 4371 org.hl7.fhir.r5.elementmodel.Element r = new org.hl7.fhir.r5.elementmodel.Element(ed.getPath(), new Property(context, ed, profile)); 4372 SourcedChildDefinitions children = getChildMap(profile, ed, true); 4373 for (ElementDefinition child : children.getList()) { 4374 if (child.getPath().endsWith(".id")) { 4375 org.hl7.fhir.r5.elementmodel.Element id = new org.hl7.fhir.r5.elementmodel.Element("id", new Property(context, child, profile)); 4376 id.setValue(profile.getId()+accessor.getId()); 4377 r.getChildren().add(id); 4378 } else { 4379 org.hl7.fhir.r5.elementmodel.Element e = createExampleElement(profile, child, accessor); 4380 if (e != null) 4381 r.getChildren().add(e); 4382 } 4383 } 4384 return r; 4385 } 4386 4387 private org.hl7.fhir.r5.elementmodel.Element createExampleElement(StructureDefinition profile, ElementDefinition ed, ExampleValueAccessor accessor) throws FHIRException { 4388 DataType v = accessor.getExampleValue(ed); 4389 if (v != null) { 4390 return new ObjectConverter(context).convert(new Property(context, ed, profile), v); 4391 } else { 4392 org.hl7.fhir.r5.elementmodel.Element res = new org.hl7.fhir.r5.elementmodel.Element(tail(ed.getPath()), new Property(context, ed, profile)); 4393 boolean hasValue = false; 4394 SourcedChildDefinitions children = getChildMap(profile, ed, true); 4395 for (ElementDefinition child : children.getList()) { 4396 if (!child.hasContentReference()) { 4397 org.hl7.fhir.r5.elementmodel.Element e = createExampleElement(profile, child, accessor); 4398 if (e != null) { 4399 hasValue = true; 4400 res.getChildren().add(e); 4401 } 4402 } 4403 } 4404 if (hasValue) 4405 return res; 4406 else 4407 return null; 4408 } 4409 } 4410 4411 private boolean hasAnyExampleValues(StructureDefinition sd, String index) { 4412 for (ElementDefinition ed : sd.getSnapshot().getElement()) 4413 for (Extension ex : ed.getExtension()) { 4414 String ndx = ExtensionUtilities.readStringExtension(ex, "index"); 4415 Extension exv = ExtensionUtilities.getExtension(ex, "exValue"); 4416 if (exv != null) { 4417 DataType value = exv.getValue(); 4418 if (index.equals(ndx) && value != null) 4419 return true; 4420 } 4421 } 4422 return false; 4423 } 4424 4425 4426 private boolean hasAnyExampleValues(StructureDefinition sd) { 4427 for (ElementDefinition ed : sd.getSnapshot().getElement()) 4428 if (ed.hasExample()) 4429 return true; 4430 return false; 4431 } 4432 4433 4434 public void populateLogicalSnapshot(StructureDefinition sd) throws FHIRException { 4435 sd.getSnapshot().getElement().add(sd.getDifferential().getElementFirstRep().copy()); 4436 4437 if (sd.hasBaseDefinition()) { 4438 StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); 4439 if (base == null) 4440 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_BASE_DEFINITION_FOR_LOGICAL_MODEL__FROM_, sd.getBaseDefinition(), sd.getUrl())); 4441 copyElements(sd, base.getSnapshot().getElement()); 4442 } 4443 copyElements(sd, sd.getDifferential().getElement()); 4444 } 4445 4446 4447 private void copyElements(StructureDefinition sd, List<ElementDefinition> list) { 4448 for (ElementDefinition ed : list) { 4449 if (ed.getPath().contains(".")) { 4450 ElementDefinition n = ed.copy(); 4451 n.setPath(sd.getSnapshot().getElementFirstRep().getPath()+"."+ed.getPath().substring(ed.getPath().indexOf(".")+1)); 4452 sd.getSnapshot().addElement(n); 4453 } 4454 } 4455 } 4456 4457 4458 public void cleanUpDifferential(StructureDefinition sd) { 4459 if (sd.getDifferential().getElement().size() > 1) 4460 cleanUpDifferential(sd, 1); 4461 } 4462 4463 private void cleanUpDifferential(StructureDefinition sd, int start) { 4464 int level = Utilities.charCount(sd.getDifferential().getElement().get(start).getPath(), '.'); 4465 int c = start; 4466 int len = sd.getDifferential().getElement().size(); 4467 HashSet<String> paths = new HashSet<String>(); 4468 while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') == level) { 4469 ElementDefinition ed = sd.getDifferential().getElement().get(c); 4470 if (!paths.contains(ed.getPath())) { 4471 paths.add(ed.getPath()); 4472 int ic = c+1; 4473 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 4474 ic++; 4475 ElementDefinition slicer = null; 4476 List<ElementDefinition> slices = new ArrayList<ElementDefinition>(); 4477 slices.add(ed); 4478 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') == level) { 4479 ElementDefinition edi = sd.getDifferential().getElement().get(ic); 4480 if (ed.getPath().equals(edi.getPath())) { 4481 if (slicer == null) { 4482 slicer = new ElementDefinition(); 4483 slicer.setPath(edi.getPath()); 4484 slicer.getSlicing().setRules(SlicingRules.OPEN); 4485 sd.getDifferential().getElement().add(c, slicer); 4486 c++; 4487 ic++; 4488 } 4489 slices.add(edi); 4490 } 4491 ic++; 4492 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 4493 ic++; 4494 } 4495 // now we're at the end, we're going to figure out the slicing discriminator 4496 if (slicer != null) 4497 determineSlicing(slicer, slices); 4498 } 4499 c++; 4500 if (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) { 4501 cleanUpDifferential(sd, c); 4502 c++; 4503 while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) 4504 c++; 4505 } 4506 } 4507 } 4508 4509 4510 private void determineSlicing(ElementDefinition slicer, List<ElementDefinition> slices) { 4511 // first, name them 4512 int i = 0; 4513 for (ElementDefinition ed : slices) { 4514 if (ed.hasUserData(UserDataNames.SNAPSHOT_slice_name)) { 4515 ed.setSliceName(ed.getUserString(UserDataNames.SNAPSHOT_slice_name)); 4516 } else { 4517 i++; 4518 ed.setSliceName("slice-"+Integer.toString(i)); 4519 } 4520 } 4521 // now, the hard bit, how are they differentiated? 4522 // right now, we hard code this... 4523 if (slicer.getPath().endsWith(".extension") || slicer.getPath().endsWith(".modifierExtension")) 4524 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url"); 4525 else if (slicer.getPath().equals("DiagnosticReport.result")) 4526 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("reference.code"); 4527 else if (slicer.getPath().equals("Observation.related")) 4528 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("target.reference.code"); 4529 else if (slicer.getPath().equals("Bundle.entry")) 4530 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("resource.@profile"); 4531 else 4532 throw new Error("No slicing for "+slicer.getPath()); 4533 } 4534 4535 4536 public static ElementDefinitionSlicingDiscriminatorComponent interpretR2Discriminator(String discriminator, boolean isExists) { 4537 if (discriminator.endsWith("@pattern")) 4538 return makeDiscriminator(DiscriminatorType.PATTERN, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 4539 if (discriminator.endsWith("@profile")) 4540 return makeDiscriminator(DiscriminatorType.PROFILE, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 4541 if (discriminator.endsWith("@type")) 4542 return makeDiscriminator(DiscriminatorType.TYPE, discriminator.length() == 5 ? "" : discriminator.substring(0,discriminator.length()-6)); 4543 if (discriminator.endsWith("@exists")) 4544 return makeDiscriminator(DiscriminatorType.EXISTS, discriminator.length() == 7 ? "" : discriminator.substring(0,discriminator.length()-8)); 4545 if (isExists) 4546 return makeDiscriminator(DiscriminatorType.EXISTS, discriminator); 4547 return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(discriminator); 4548 } 4549 4550 4551 private static ElementDefinitionSlicingDiscriminatorComponent makeDiscriminator(DiscriminatorType dType, String str) { 4552 return new ElementDefinitionSlicingDiscriminatorComponent().setType(dType).setPath(Utilities.noString(str)? "$this" : str); 4553 } 4554 4555 4556 public static String buildR2Discriminator(ElementDefinitionSlicingDiscriminatorComponent t) throws FHIRException { 4557 switch (t.getType()) { 4558 case PROFILE: return t.getPath()+"/@profile"; 4559 case PATTERN: return t.getPath()+"/@pattern"; 4560 case TYPE: return t.getPath()+"/@type"; 4561 case VALUE: return t.getPath(); 4562 case EXISTS: return t.getPath(); // determination of value vs. exists is based on whether there's only 2 slices - one with minOccurs=1 and other with maxOccur=0 4563 default: throw new FHIRException("Unable to represent "+t.getType().toCode()+":"+t.getPath()+" in R2"); 4564 } 4565 } 4566 4567 4568 public static StructureDefinition makeExtensionForVersionedURL(IWorkerContext context, String url) { 4569 String epath = url.substring(54); 4570 if (!epath.contains(".")) 4571 return null; 4572 String type = epath.substring(0, epath.indexOf(".")); 4573 StructureDefinition sd = context.fetchTypeDefinition(type); 4574 if (sd == null) 4575 return null; 4576 ElementDefinition ed = null; 4577 for (ElementDefinition t : sd.getSnapshot().getElement()) { 4578 if (t.getPath().equals(epath)) { 4579 ed = t; 4580 break; 4581 } 4582 } 4583 if (ed == null) 4584 return null; 4585 if ("Element".equals(ed.typeSummary()) || "BackboneElement".equals(ed.typeSummary())) { 4586 return null; 4587 } else { 4588 StructureDefinition template = context.fetchResource(StructureDefinition.class, "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities"); 4589 StructureDefinition ext = template.copy(); 4590 ext.setUrl(url); 4591 ext.setId("extension-"+epath); 4592 ext.setName("Extension-"+epath); 4593 ext.setTitle("Extension for r4 "+epath); 4594 ext.setStatus(sd.getStatus()); 4595 ext.setDate(sd.getDate()); 4596 ext.getContact().clear(); 4597 ext.getContact().addAll(sd.getContact()); 4598 ext.setFhirVersion(sd.getFhirVersion()); 4599 ext.setDescription(ed.getDefinition()); 4600 ext.getContext().clear(); 4601 ext.addContext().setType(ExtensionContextType.ELEMENT).setExpression(epath.substring(0, epath.lastIndexOf("."))); 4602 ext.getDifferential().getElement().clear(); 4603 ext.getSnapshot().getElement().get(3).setFixed(new UriType(url)); 4604 ext.getSnapshot().getElement().set(4, ed.copy()); 4605 ext.getSnapshot().getElement().get(4).setPath("Extension.value"+Utilities.capitalize(ed.typeSummary())); 4606 return ext; 4607 } 4608 4609 } 4610 4611 4612 public boolean isThrowException() { 4613 return wantThrowExceptions; 4614 } 4615 4616 4617 public void setThrowException(boolean exception) { 4618 this.wantThrowExceptions = exception; 4619 } 4620 4621 4622 public ValidationOptions getTerminologyServiceOptions() { 4623 return terminologyServiceOptions; 4624 } 4625 4626 4627 public void setTerminologyServiceOptions(ValidationOptions terminologyServiceOptions) { 4628 this.terminologyServiceOptions = terminologyServiceOptions; 4629 } 4630 4631 4632 public boolean isNewSlicingProcessing() { 4633 return newSlicingProcessing; 4634 } 4635 4636 4637 public ProfileUtilities setNewSlicingProcessing(boolean newSlicingProcessing) { 4638 this.newSlicingProcessing = newSlicingProcessing; 4639 return this; 4640 } 4641 4642 4643 public boolean isDebug() { 4644 return debug; 4645 } 4646 4647 4648 public void setDebug(boolean debug) { 4649 this.debug = debug; 4650 } 4651 4652 4653 public String getDefWebRoot() { 4654 return defWebRoot; 4655 } 4656 4657 4658 public void setDefWebRoot(String defWebRoot) { 4659 this.defWebRoot = defWebRoot; 4660 if (!this.defWebRoot.endsWith("/")) 4661 this.defWebRoot = this.defWebRoot + '/'; 4662 } 4663 4664 4665 public static StructureDefinition makeBaseDefinition(FHIRVersion fhirVersion) { 4666 return makeBaseDefinition(fhirVersion.toCode()); 4667 } 4668 public static StructureDefinition makeBaseDefinition(String fhirVersion) { 4669 StructureDefinition base = new StructureDefinition(); 4670 base.setId("Base"); 4671 base.setUrl("http://hl7.org/fhir/StructureDefinition/Base"); 4672 base.setVersion(fhirVersion); 4673 base.setName("Base"); 4674 base.setStatus(PublicationStatus.ACTIVE); 4675 base.setDate(new Date()); 4676 base.setFhirVersion(FHIRVersion.fromCode(fhirVersion)); 4677 base.setKind(StructureDefinitionKind.COMPLEXTYPE); 4678 base.setAbstract(true); 4679 base.setType("Base"); 4680 base.setWebPath("http://build.fhir.org/types.html#Base"); 4681 ElementDefinition e = base.getSnapshot().getElementFirstRep(); 4682 e.setId("Base"); 4683 e.setPath("Base"); 4684 e.setMin(0); 4685 e.setMax("*"); 4686 e.getBase().setPath("Base"); 4687 e.getBase().setMin(0); 4688 e.getBase().setMax("*"); 4689 e.setIsModifier(false); 4690 e = base.getDifferential().getElementFirstRep(); 4691 e.setId("Base"); 4692 e.setPath("Base"); 4693 e.setMin(0); 4694 e.setMax("*"); 4695 return base; 4696 } 4697 4698 public XVerExtensionManager getXver() { 4699 return xver; 4700 } 4701 4702 public ProfileUtilities setXver(XVerExtensionManager xver) { 4703 this.xver = xver; 4704 return this; 4705 } 4706 4707 4708 private List<ElementChoiceGroup> readChoices(ElementDefinition ed, List<ElementDefinition> children) { 4709 List<ElementChoiceGroup> result = new ArrayList<>(); 4710 for (ElementDefinitionConstraintComponent c : ed.getConstraint()) { 4711 ElementChoiceGroup grp = processConstraint(children, c); 4712 if (grp != null) { 4713 result.add(grp); 4714 } 4715 } 4716 return result; 4717 } 4718 4719 public ElementChoiceGroup processConstraint(List<ElementDefinition> children, ElementDefinitionConstraintComponent c) { 4720 if (!c.hasExpression()) { 4721 return null; 4722 } 4723 ExpressionNode expr = null; 4724 try { 4725 expr = fpe.parse(c.getExpression()); 4726 } catch (Exception e) { 4727 return null; 4728 } 4729 if (expr.getKind() != Kind.Group || expr.getOpNext() == null || !(expr.getOperation() == Operation.Equals || expr.getOperation() == Operation.LessOrEqual)) { 4730 return null; 4731 } 4732 ExpressionNode n1 = expr.getGroup(); 4733 ExpressionNode n2 = expr.getOpNext(); 4734 if (n2.getKind() != Kind.Constant || n2.getInner() != null || n2.getOpNext() != null || !"1".equals(n2.getConstant().primitiveValue())) { 4735 return null; 4736 } 4737 ElementChoiceGroup grp = new ElementChoiceGroup(c.getKey(), expr.getOperation() == Operation.Equals); 4738 while (n1 != null) { 4739 if (n1.getKind() != Kind.Name || n1.getInner() != null) { 4740 return null; 4741 } 4742 grp.elements.add(n1.getName()); 4743 if (n1.getOperation() == null || n1.getOperation() == Operation.Union) { 4744 n1 = n1.getOpNext(); 4745 } else { 4746 return null; 4747 } 4748 } 4749 int total = 0; 4750 for (String n : grp.elements) { 4751 boolean found = false; 4752 for (ElementDefinition child : children) { 4753 String name = tail(child.getPath()); 4754 if (n.equals(name)) { 4755 found = true; 4756 if (!"0".equals(child.getMax())) { 4757 total++; 4758 } 4759 } 4760 } 4761 if (!found) { 4762 return null; 4763 } 4764 } 4765 if (total <= 1) { 4766 return null; 4767 } 4768 return grp; 4769 } 4770 4771 public Set<String> getMasterSourceFileNames() { 4772 return masterSourceFileNames; 4773 } 4774 4775 public void setMasterSourceFileNames(Set<String> masterSourceFileNames) { 4776 this.masterSourceFileNames = masterSourceFileNames; 4777 } 4778 4779 4780 public Set<String> getLocalFileNames() { 4781 return localFileNames; 4782 } 4783 4784 public void setLocalFileNames(Set<String> localFileNames) { 4785 this.localFileNames = localFileNames; 4786 } 4787 4788 public ProfileKnowledgeProvider getPkp() { 4789 return pkp; 4790 } 4791 4792 4793 public static final String UD_ERROR_STATUS = "error-status"; 4794 public static final int STATUS_OK = 0; 4795 public static final int STATUS_HINT = 1; 4796 public static final int STATUS_WARNING = 2; 4797 public static final int STATUS_ERROR = 3; 4798 public static final int STATUS_FATAL = 4; 4799 private static final String ROW_COLOR_ERROR = "#ffcccc"; 4800 private static final String ROW_COLOR_FATAL = "#ff9999"; 4801 private static final String ROW_COLOR_WARNING = "#ffebcc"; 4802 private static final String ROW_COLOR_HINT = "#ebf5ff"; 4803 private static final String ROW_COLOR_NOT_MUST_SUPPORT = "#d6eaf8"; 4804 4805 public String getRowColor(ElementDefinition element, boolean isConstraintMode) { 4806 switch (element.getUserInt(UD_ERROR_STATUS)) { 4807 case STATUS_HINT: return ROW_COLOR_HINT; 4808 case STATUS_WARNING: return ROW_COLOR_WARNING; 4809 case STATUS_ERROR: return ROW_COLOR_ERROR; 4810 case STATUS_FATAL: return ROW_COLOR_FATAL; 4811 } 4812 if (isConstraintMode && !element.getMustSupport() && !element.getIsModifier() && element.getPath().contains(".")) 4813 return null; // ROW_COLOR_NOT_MUST_SUPPORT; 4814 else 4815 return null; 4816 } 4817 4818 public static boolean isExtensionDefinition(StructureDefinition sd) { 4819 return sd.getDerivation() == TypeDerivationRule.CONSTRAINT && sd.getType().equals("Extension"); 4820 } 4821 4822 public AllowUnknownProfile getAllowUnknownProfile() { 4823 return allowUnknownProfile; 4824 } 4825 4826 public void setAllowUnknownProfile(AllowUnknownProfile allowUnknownProfile) { 4827 this.allowUnknownProfile = allowUnknownProfile; 4828 } 4829 4830 public static boolean isSimpleExtension(StructureDefinition sd) { 4831 if (!isExtensionDefinition(sd)) { 4832 return false; 4833 } 4834 ElementDefinition value = sd.getSnapshot().getElementByPath("Extension.value"); 4835 return value != null && !value.isProhibited(); 4836 } 4837 4838 public static boolean isComplexExtension(StructureDefinition sd) { 4839 if (!isExtensionDefinition(sd)) { 4840 return false; 4841 } 4842 ElementDefinition value = sd.getSnapshot().getElementByPath("Extension.value"); 4843 return value == null || value.isProhibited(); 4844 } 4845 4846 public static boolean isModifierExtension(StructureDefinition sd) { 4847 ElementDefinition defn = sd.getSnapshot().hasElement() ? sd.getSnapshot().getElementByPath("Extension") : sd.getDifferential().getElementByPath("Extension"); 4848 return defn != null && defn.getIsModifier(); 4849 } 4850 4851 public boolean isForPublication() { 4852 return forPublication; 4853 } 4854 4855 public void setForPublication(boolean forPublication) { 4856 this.forPublication = forPublication; 4857 } 4858 4859 public List<ValidationMessage> getMessages() { 4860 return messages; 4861 } 4862 4863 public static boolean isResourceBoundary(ElementDefinition ed) { 4864 return ed.getType().size() == 1 && "Resource".equals(ed.getTypeFirstRep().getCode()); 4865 } 4866 4867 public static boolean isSuppressIgnorableExceptions() { 4868 return suppressIgnorableExceptions; 4869 } 4870 4871 public static void setSuppressIgnorableExceptions(boolean suppressIgnorableExceptions) { 4872 ProfileUtilities.suppressIgnorableExceptions = suppressIgnorableExceptions; 4873 } 4874 4875 public void setMessages(List<ValidationMessage> messages) { 4876 if (messages != null) { 4877 this.messages = messages; 4878 wantThrowExceptions = false; 4879 } 4880 } 4881 4882 private Map<String, List<Property>> propertyCache = new HashMap<>(); 4883 4884 public Map<String, List<Property>> getCachedPropertyList() { 4885 return propertyCache; 4886 } 4887 4888 public void checkExtensions(ElementDefinition outcome) { 4889 outcome.getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), ProfileUtilities.NON_INHERITED_ED_URLS)); 4890 if (outcome.hasBinding()) { 4891 outcome.getBinding().getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), ProfileUtilities.NON_INHERITED_ED_URLS)); 4892 } 4893 4894 } 4895 4896 public static void markExtensions(ElementDefinition ed, boolean overrideSource, StructureDefinition src) { 4897 for (Extension ex : ed.getExtension()) { 4898 markExtensionSource(ex, overrideSource, src); 4899 } 4900 for (Extension ex : ed.getBinding().getExtension()) { 4901 markExtensionSource(ex, overrideSource, src); 4902 } 4903 for (TypeRefComponent t : ed.getType()) { 4904 for (Extension ex : t.getExtension()) { 4905 markExtensionSource(ex, overrideSource, src); 4906 } 4907 } 4908 } 4909 4910 public static boolean hasObligations(StructureDefinition sd) { 4911 if (sd.hasExtension(ExtensionDefinitions.EXT_OBLIGATION_CORE)) { 4912 return true; 4913 } 4914 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 4915 if (ed.hasExtension(ExtensionDefinitions.EXT_OBLIGATION_CORE)) { 4916 return true; 4917 } 4918 for (TypeRefComponent tr : ed.getType()) { 4919 if (tr.hasExtension(ExtensionDefinitions.EXT_OBLIGATION_CORE)) { 4920 return true; 4921 } 4922 } 4923 } 4924 return false; 4925 } 4926 4927 public List<String> getSuppressedMappings() { 4928 return suppressedMappings; 4929 } 4930 4931 public void setSuppressedMappings(List<String> suppressedMappings) { 4932 this.suppressedMappings = suppressedMappings; 4933 } 4934 4935 public static String getCSUrl(StructureDefinition profile) { 4936 if (profile.hasExtension(ExtensionDefinitions.EXT_SD_CS_URL)) { 4937 return ExtensionUtilities.readStringExtension(profile, ExtensionDefinitions.EXT_SD_CS_URL); 4938 } else { 4939 return profile.getUrl()+"?codesystem"; 4940 } 4941 } 4942 4943 public static String getUrlFromCSUrl(String url) { 4944 if (url == null) { 4945 return null; 4946 } 4947 if (url.endsWith("?codesystem")) { 4948 return url.replace("?codesystem", ""); 4949 } else { 4950 return null; 4951 } 4952 } 4953 4954 public FHIRPathEngine getFpe() { 4955 return fpe; 4956 } 4957 4958}