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