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