
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 } else { 2207 //DO NOTHING 2208 } 2209 i = i + 1; 2210 } 2211 } else 2212 b.append(markdown.charAt(i)); 2213 } else 2214 b.append(markdown.charAt(i)); 2215 } else { 2216 b.append(markdown.charAt(i)); 2217 } 2218 } 2219 i++; 2220 } 2221 String s = b.toString(); 2222 return Utilities.rightTrim(s); 2223 } 2224 2225 private static boolean issLocalFileName(String url, Set<String> localFilenames) { 2226 if (localFilenames != null) { 2227 for (String n : localFilenames) { 2228 if (url.startsWith(n.toLowerCase())) { 2229 return true; 2230 } 2231 } 2232 } 2233 return false; 2234 } 2235 2236 2237 private static boolean isLikelySourceURLReference(String url, List<String> resourceNames, Set<String> baseFilenames, Set<String> localFilenames, String baseUrl) { 2238 if (url == null) { 2239 return false; 2240 } 2241 if (baseUrl != null && !baseUrl.startsWith("http://hl7.org/fhir/R")) { 2242 if (resourceNames != null) { 2243 for (String n : resourceNames) { 2244 if (n != null && url.startsWith(n.toLowerCase()+".html")) { 2245 return true; 2246 } 2247 if (n != null && url.startsWith(n.toLowerCase()+"-definitions.html")) { 2248 return true; 2249 } 2250 } 2251 } 2252 if (localFilenames != null) { 2253 for (String n : localFilenames) { 2254 if (n != null && url.startsWith(n.toLowerCase())) { 2255 return false; 2256 } 2257 } 2258 } 2259 if (baseFilenames != null) { 2260 for (String n : baseFilenames) { 2261 if (n != null && url.startsWith(n.toLowerCase())) { 2262 return true; 2263 } 2264 } 2265 } 2266 } 2267 return 2268 url.startsWith("extensibility.html") || 2269 url.startsWith("terminologies.html") || 2270 url.startsWith("observation.html") || 2271 url.startsWith("codesystem.html") || 2272 url.startsWith("fhirpath.html") || 2273 url.startsWith("datatypes.html") || 2274 url.startsWith("operations.html") || 2275 url.startsWith("resource.html") || 2276 url.startsWith("elementdefinition.html") || 2277 url.startsWith("element-definitions.html") || 2278 url.startsWith("snomedct.html") || 2279 url.startsWith("loinc.html") || 2280 url.startsWith("http.html") || 2281 url.startsWith("references") || 2282 url.startsWith("license.html") || 2283 url.startsWith("narrative.html") || 2284 url.startsWith("search.html") || 2285 url.startsWith("security.html") || 2286 url.startsWith("versions.html") || 2287 url.startsWith("patient-operation-match.html") || 2288 (url.startsWith("extension-") && url.contains(".html")) || 2289 url.startsWith("resource-definitions.html"); 2290 } 2291 2292 protected List<ElementDefinition> getSiblings(List<ElementDefinition> list, ElementDefinition current) { 2293 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 2294 String path = current.getPath(); 2295 int cursor = list.indexOf(current)+1; 2296 while (cursor < list.size() && list.get(cursor).getPath().length() >= path.length()) { 2297 if (pathMatches(list.get(cursor).getPath(), path)) 2298 result.add(list.get(cursor)); 2299 cursor++; 2300 } 2301 return result; 2302 } 2303 2304 protected void updateFromSlicing(ElementDefinitionSlicingComponent dst, ElementDefinitionSlicingComponent src) { 2305 if (src.hasOrderedElement()) 2306 dst.setOrderedElement(src.getOrderedElement().copy()); 2307 if (src.hasDiscriminator()) { 2308 // dst.getDiscriminator().addAll(src.getDiscriminator()); Can't use addAll because it uses object equality, not string equality 2309 for (ElementDefinitionSlicingDiscriminatorComponent s : src.getDiscriminator()) { 2310 boolean found = false; 2311 for (ElementDefinitionSlicingDiscriminatorComponent d : dst.getDiscriminator()) { 2312 if (matches(d, s)) { 2313 found = true; 2314 break; 2315 } 2316 } 2317 if (!found) 2318 dst.getDiscriminator().add(s); 2319 } 2320 } 2321 if (src.hasRulesElement()) 2322 dst.setRulesElement(src.getRulesElement().copy()); 2323 } 2324 2325 protected boolean orderMatches(BooleanType diff, BooleanType base) { 2326 return (diff == null) || (base == null) || (diff.getValue() == base.getValue()); 2327 } 2328 2329 protected boolean discriminatorMatches(List<ElementDefinitionSlicingDiscriminatorComponent> diff, List<ElementDefinitionSlicingDiscriminatorComponent> base) { 2330 if (diff.isEmpty() || base.isEmpty()) 2331 return true; 2332 if (diff.size() < base.size()) 2333 return false; 2334 for (int i = 0; i < base.size(); i++) 2335 if (!matches(diff.get(i), base.get(i))) 2336 return false; 2337 return true; 2338 } 2339 2340 private boolean matches(ElementDefinitionSlicingDiscriminatorComponent c1, ElementDefinitionSlicingDiscriminatorComponent c2) { 2341 return c1.getType().equals(c2.getType()) && c1.getPath().equals(c2.getPath()); 2342 } 2343 2344 2345 protected boolean ruleMatches(SlicingRules diff, SlicingRules base) { 2346 return (diff == null) || (base == null) || (diff == base) || (base == SlicingRules.OPEN) || 2347 ((diff == SlicingRules.OPENATEND && base == SlicingRules.CLOSED)); 2348 } 2349 2350 protected boolean isSlicedToOneOnly(ElementDefinition e) { 2351 return (e.hasSlicing() && e.hasMaxElement() && e.getMax().equals("1")); 2352 } 2353 2354 protected boolean isTypeSlicing(ElementDefinition e) { 2355 return (e.hasSlicing() && e.getSlicing().getDiscriminator().size() == 1 && 2356 e.getSlicing().getDiscriminatorFirstRep().getType() == DiscriminatorType.TYPE && 2357 "$this".equals(e.getSlicing().getDiscriminatorFirstRep().getPath())); 2358 } 2359 2360 2361 protected ElementDefinitionSlicingComponent makeExtensionSlicing() { 2362 ElementDefinitionSlicingComponent slice = new ElementDefinitionSlicingComponent(); 2363 slice.addDiscriminator().setPath("url").setType(DiscriminatorType.VALUE); 2364 slice.setOrdered(false); 2365 slice.setRules(SlicingRules.OPEN); 2366 return slice; 2367 } 2368 2369 protected boolean isExtension(ElementDefinition currentBase) { 2370 return currentBase.getPath().endsWith(".extension") || currentBase.getPath().endsWith(".modifierExtension"); 2371 } 2372 2373 boolean hasInnerDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, List<ElementDefinition> base, boolean allowSlices) throws DefinitionException { 2374 end = Math.min(context.getElement().size(), end); 2375 start = Math.max(0, start); 2376 2377 for (int i = start; i <= end; i++) { 2378 ElementDefinition ed = context.getElement().get(i); 2379 String statedPath = ed.getPath(); 2380 if (!allowSlices && statedPath.equals(path) && ed.hasSliceName()) { 2381 return false; 2382 } else if (statedPath.startsWith(path+".")) { 2383 return true; 2384 } else if (path.endsWith("[x]") && statedPath.startsWith(path.substring(0, path.length() -3))) { 2385 return true; 2386 } else if (i != start && !allowSlices && !statedPath.startsWith(path+".")) { 2387 return false; 2388 } else if (i != start && allowSlices && !statedPath.startsWith(path)) { 2389 return false; 2390 } else { 2391 // not sure why we get here, but returning false at this point makes a bunch of tests fail 2392 } 2393 } 2394 return false; 2395 } 2396 2397 protected List<ElementDefinition> getDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, String profileName) throws DefinitionException { 2398 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 2399 String[] p = path.split("\\."); 2400 for (int i = start; i <= end; i++) { 2401 String statedPath = context.getElement().get(i).getPath(); 2402 String[] sp = statedPath.split("\\."); 2403 boolean ok = sp.length == p.length; 2404 for (int j = 0; j < p.length; j++) { 2405 ok = ok && sp.length > j && (p[j].equals(sp[j]) || isSameBase(p[j], sp[j])); 2406 } 2407// don't need this debug check - everything is ok 2408// if (ok != (statedPath.equals(path) || (path.endsWith("[x]") && statedPath.length() > path.length() - 2 && 2409// statedPath.substring(0, path.length()-3).equals(path.substring(0, path.length()-3)) && 2410// (statedPath.length() < path.length() || !statedPath.substring(path.length()).contains("."))))) { 2411// 2412// } 2413 if (ok) { 2414 /* 2415 * Commenting this out because it raises warnings when profiling inherited elements. For example, 2416 * Error: unknown element 'Bundle.meta.profile' (or it is out of order) in profile ... (looking for 'Bundle.entry') 2417 * Not sure we have enough information here to do the check properly. Might be better done when we're sorting the profile? 2418 2419 if (i != start && result.isEmpty() && !path.startsWith(context.getElement().get(start).getPath())) 2420 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)); 2421 2422 */ 2423 result.add(context.getElement().get(i)); 2424 } 2425 } 2426 if (debug) { 2427 Set<String> ids = new HashSet<>(); 2428 for (ElementDefinition ed : result) { 2429 ids.add(ed.getIdOrPath()); 2430 } 2431 } 2432 return result; 2433 } 2434 2435 2436 private boolean isSameBase(String p, String sp) { 2437 return (p.endsWith("[x]") && sp.startsWith(p.substring(0, p.length()-3))) || (sp.endsWith("[x]") && p.startsWith(sp.substring(0, sp.length()-3))) ; 2438 } 2439 2440 protected int findEndOfElement(StructureDefinitionDifferentialComponent context, int cursor) { 2441 int result = cursor; 2442 if (cursor >= context.getElement().size()) 2443 return result; 2444 String path = context.getElement().get(cursor).getPath()+"."; 2445 while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path)) 2446 result++; 2447 return result; 2448 } 2449 2450 protected int findEndOfElement(StructureDefinitionSnapshotComponent context, int cursor) { 2451 int result = cursor; 2452 String path = context.getElement().get(cursor).getPath()+"."; 2453 while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path)) 2454 result++; 2455 return result; 2456 } 2457 2458 protected int findEndOfElementNoSlices(StructureDefinitionSnapshotComponent context, int cursor) { 2459 int result = cursor; 2460 String path = context.getElement().get(cursor).getPath()+"."; 2461 while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path) && !context.getElement().get(result+1).hasSliceName()) 2462 result++; 2463 return result; 2464 } 2465 2466 protected boolean unbounded(ElementDefinition definition) { 2467 StringType max = definition.getMaxElement(); 2468 if (max == null) 2469 return false; // this is not valid 2470 if (max.getValue().equals("1")) 2471 return false; 2472 if (max.getValue().equals("0")) 2473 return false; 2474 return true; 2475 } 2476 2477 2478 public void updateFromObligationProfiles(ElementDefinition base) { 2479 List<ElementDefinition> obligationProfileElements = new ArrayList<>(); 2480 for (StructureDefinition sd : obligationProfiles) { 2481 ElementDefinition ed = sd.getSnapshot().getElementById(base.getId()); 2482 if (ed != null) { 2483 obligationProfileElements.add(ed); 2484 } 2485 } 2486 for (ElementDefinition ed : obligationProfileElements) { 2487 for (Extension ext : ed.getExtension()) { 2488 if (Utilities.existsInList(ext.getUrl(), ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS)) { 2489 base.getExtension().add(ext.copy()); 2490 } 2491 } 2492 } 2493 boolean hasMustSupport = false; 2494 for (ElementDefinition ed : obligationProfileElements) { 2495 hasMustSupport = hasMustSupport || ed.hasMustSupportElement(); 2496 } 2497 if (hasMustSupport) { 2498 for (ElementDefinition ed : obligationProfileElements) { 2499 mergeExtensions(base.getMustSupportElement(), ed.getMustSupportElement()); 2500 if (ed.getMustSupport()) { 2501 base.setMustSupport(true); 2502 } 2503 } 2504 } 2505 boolean hasBinding = false; 2506 for (ElementDefinition ed : obligationProfileElements) { 2507 hasBinding = hasBinding || ed.hasBinding(); 2508 } 2509 if (hasBinding) { 2510 ElementDefinitionBindingComponent binding = base.getBinding(); 2511 for (ElementDefinition ed : obligationProfileElements) { 2512 for (Extension ext : ed.getBinding().getExtension()) { 2513 if (ToolingExtensions.EXT_BINDING_ADDITIONAL.equals(ext.getUrl())) { 2514 String p = ext.getExtensionString("purpose"); 2515 if (!Utilities.existsInList(p, "maximum", "required", "extensible")) { 2516 if (!binding.hasExtension(ext)) { 2517 binding.getExtension().add(ext.copy()); 2518 } 2519 } 2520 } 2521 } 2522 for (ElementDefinitionBindingAdditionalComponent ab : ed.getBinding().getAdditional()) { 2523 if (!Utilities.existsInList(ab.getPurpose().toCode(), "maximum", "required", "extensible")) { 2524 if (!binding.hasAdditional(ab)) { 2525 binding.getAdditional().add(ab.copy()); 2526 } 2527 } 2528 } 2529 } 2530 } 2531 } 2532 2533 2534 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 { 2535 source.setUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT, dest); 2536 // we start with a clone of the base profile ('dest') and we copy from the profile ('source') 2537 // over the top for anything the source has 2538 ElementDefinition base = dest; 2539 ElementDefinition derived = source; 2540 derived.setUserData(UserDataNames.SNAPSHOT_DERIVATION_POINTER, base); 2541 boolean isExtension = checkExtensionDoco(base); 2542 List<ElementDefinition> obligationProfileElements = new ArrayList<>(); 2543 for (StructureDefinition sd : obligationProfiles) { 2544 ElementDefinition ed = sd.getSnapshot().getElementById(base.getId()); 2545 if (ed != null) { 2546 obligationProfileElements.add(ed); 2547 } 2548 } 2549 2550 // hack workaround for problem in R5 snapshots 2551 List<Extension> elist = dest.getExtensionsByUrl(ToolingExtensions.EXT_TRANSLATABLE); 2552 if (elist.size() == 2) { 2553 dest.getExtension().remove(elist.get(1)); 2554 } 2555 updateExtensionsFromDefinition(dest, source, derivedSrc, srcSD); 2556 2557 for (ElementDefinition ed : obligationProfileElements) { 2558 for (Extension ext : ed.getExtension()) { 2559 if (Utilities.existsInList(ext.getUrl(), ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS)) { 2560 dest.getExtension().add(new Extension(ToolingExtensions.EXT_OBLIGATION_CORE, ext.getValue().copy())); 2561 } 2562 } 2563 } 2564 2565 // Before applying changes, apply them to what's in the profile 2566 // but only if it's an extension or a resource 2567 2568 StructureDefinition profile = null; 2569 boolean msg = true; 2570 if (base.hasSliceName()) { 2571 profile = base.getType().size() == 1 && base.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, base.getTypeFirstRep().getProfile().get(0).getValue(), srcSD) : null; 2572 } 2573 if (profile == null && source.getTypeFirstRep().hasProfile()) { 2574 String pu = source.getTypeFirstRep().getProfile().get(0).getValue(); 2575 profile = context.fetchResource(StructureDefinition.class, pu, derivedSrc); 2576 if (profile == null) { 2577 if (makeXVer().matchingUrl(pu)) { 2578 switch (xver.status(pu)) { 2579 case BadVersion: 2580 throw new FHIRException("Reference to invalid version in extension url " + pu); 2581 case Invalid: 2582 throw new FHIRException("Reference to invalid extension " + pu); 2583 case Unknown: 2584 throw new FHIRException("Reference to unknown extension " + pu); 2585 case Valid: 2586 profile = xver.makeDefinition(pu); 2587 generateSnapshot(context.fetchTypeDefinition("Extension"), profile, profile.getUrl(), context.getSpecUrl(), profile.getName()); 2588 } 2589 } 2590 2591 } 2592 if (profile != null && !"Extension".equals(profile.getType()) && profile.getKind() != StructureDefinitionKind.RESOURCE && profile.getKind() != StructureDefinitionKind.LOGICAL) { 2593 // 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 2594 // inherited attributes, and sometimes not 2595 profile = null; 2596 msg = false; 2597 } 2598 } 2599 if (profile != null && (profile.getKind() == StructureDefinitionKind.RESOURCE || "Extension".equals(profile.getType()))) { 2600 if (profile.getSnapshot().getElement().isEmpty()) { 2601 throw new DefinitionException(context.formatMessage(I18nConstants.SNAPSHOT_IS_EMPTY, profile.getVersionedUrl())); 2602 } 2603 ElementDefinition e = profile.getSnapshot().getElement().get(0); 2604 String webroot = profile.getUserString(UserDataNames.render_webroot); 2605 2606 if (e.hasDefinition()) { 2607 base.setDefinition(processRelativeUrls(e.getDefinition(), webroot, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, true)); 2608 } 2609 if (e.getBinding().hasDescription()) { 2610 base.getBinding().setDescription(processRelativeUrls(e.getBinding().getDescription(), webroot, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, true)); 2611 } 2612 base.setShort(e.getShort()); 2613 if (e.hasCommentElement()) 2614 base.setCommentElement(e.getCommentElement()); 2615 if (e.hasRequirementsElement()) 2616 base.setRequirementsElement(e.getRequirementsElement()); 2617 base.getAlias().clear(); 2618 base.getAlias().addAll(e.getAlias()); 2619 base.getMapping().clear(); 2620 base.getMapping().addAll(e.getMapping()); 2621 } else if (source.getType().size() == 1 && source.getTypeFirstRep().hasProfile() && !source.getTypeFirstRep().getProfile().get(0).hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT)) { 2622 // todo: should we change down the profile_element if there's one? 2623 String type = source.getTypeFirstRep().getWorkingCode(); 2624 if (msg) { 2625 if ("Extension".equals(type)) { 2626 log.warn("Can't find Extension definition for "+source.getTypeFirstRep().getProfile().get(0).asStringValue()+" but trying to go on"); 2627 if (allowUnknownProfile != AllowUnknownProfile.ALL_TYPES) { 2628 throw new DefinitionException("Unable to find Extension definition for "+source.getTypeFirstRep().getProfile().get(0).asStringValue()); 2629 } 2630 } else { 2631 log.warn("Can't find "+type+" profile "+source.getTypeFirstRep().getProfile().get(0).asStringValue()+" but trying to go on"); 2632 if (allowUnknownProfile == AllowUnknownProfile.NONE) { 2633 throw new DefinitionException("Unable to find "+type+" profile "+source.getTypeFirstRep().getProfile().get(0).asStringValue()); 2634 } 2635 } 2636 } 2637 } 2638 if (derived != null) { 2639 if (derived.hasSliceName()) { 2640 base.setSliceName(derived.getSliceName()); 2641 } 2642 2643 if (derived.hasShortElement()) { 2644 if (!Base.compareDeep(derived.getShortElement(), base.getShortElement(), false)) 2645 base.setShortElement(derived.getShortElement().copy()); 2646 else if (trimDifferential) 2647 derived.setShortElement(null); 2648 else if (derived.hasShortElement()) 2649 derived.getShortElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2650 } 2651 2652 if (derived.hasDefinitionElement()) { 2653 if (derived.getDefinition() != null && derived.getDefinition().startsWith("...")) 2654 base.setDefinition(Utilities.appendDerivedTextToBase(base.getDefinition(), derived.getDefinition())); 2655 else if (!Base.compareDeep(derived.getDefinitionElement(), base.getDefinitionElement(), false)) { 2656 base.setDefinitionElement(derived.getDefinitionElement().copy()); 2657 } else if (trimDifferential) 2658 derived.setDefinitionElement(null); 2659 else if (derived.hasDefinitionElement()) 2660 derived.getDefinitionElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2661 } 2662 2663 if (derived.hasCommentElement()) { 2664 if (derived.getComment().startsWith("...")) 2665 base.setComment(Utilities.appendDerivedTextToBase(base.getComment(), derived.getComment())); 2666 else if (derived.hasCommentElement()!= base.hasCommentElement() || !Base.compareDeep(derived.getCommentElement(), base.getCommentElement(), false)) 2667 base.setCommentElement(derived.getCommentElement().copy()); 2668 else if (trimDifferential) 2669 base.setCommentElement(derived.getCommentElement().copy()); 2670 else if (derived.hasCommentElement()) 2671 derived.getCommentElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2672 } 2673 2674 if (derived.hasLabelElement()) { 2675 if (derived.getLabel().startsWith("...")) 2676 base.setLabel(Utilities.appendDerivedTextToBase(base.getLabel(), derived.getLabel())); 2677 else if (!base.hasLabelElement() || !Base.compareDeep(derived.getLabelElement(), base.getLabelElement(), false)) 2678 base.setLabelElement(derived.getLabelElement().copy()); 2679 else if (trimDifferential) 2680 base.setLabelElement(derived.getLabelElement().copy()); 2681 else if (derived.hasLabelElement()) 2682 derived.getLabelElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2683 } 2684 2685 if (derived.hasRequirementsElement()) { 2686 if (derived.getRequirements().startsWith("...")) 2687 base.setRequirements(Utilities.appendDerivedTextToBase(base.getRequirements(), derived.getRequirements())); 2688 else if (!base.hasRequirementsElement() || !Base.compareDeep(derived.getRequirementsElement(), base.getRequirementsElement(), false)) 2689 base.setRequirementsElement(derived.getRequirementsElement().copy()); 2690 else if (trimDifferential) 2691 base.setRequirementsElement(derived.getRequirementsElement().copy()); 2692 else if (derived.hasRequirementsElement()) 2693 derived.getRequirementsElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2694 } 2695 // sdf-9 2696 if (derived.hasRequirements() && !base.getPath().contains(".")) 2697 derived.setRequirements(null); 2698 if (base.hasRequirements() && !base.getPath().contains(".")) 2699 base.setRequirements(null); 2700 2701 if (derived.hasAlias()) { 2702 if (!Base.compareDeep(derived.getAlias(), base.getAlias(), false)) 2703 for (StringType s : derived.getAlias()) { 2704 if (!base.hasAlias(s.getValue())) 2705 base.getAlias().add(s.copy()); 2706 } 2707 else if (trimDifferential) 2708 derived.getAlias().clear(); 2709 else 2710 for (StringType t : derived.getAlias()) 2711 t.setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2712 } 2713 2714 if (derived.hasMinElement()) { 2715 if (!Base.compareDeep(derived.getMinElement(), base.getMinElement(), false)) { 2716 if (derived.getMin() < base.getMin() && !derived.hasSliceName()) // in a slice, minimum cardinality rules do not apply 2717 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)); 2718 base.setMinElement(derived.getMinElement().copy()); 2719 } else if (trimDifferential) 2720 derived.setMinElement(null); 2721 else 2722 derived.getMinElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2723 } 2724 2725 if (derived.hasMaxElement()) { 2726 if (!Base.compareDeep(derived.getMaxElement(), base.getMaxElement(), false)) { 2727 if (isLargerMax(derived.getMax(), base.getMax())) 2728 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)); 2729 base.setMaxElement(derived.getMaxElement().copy()); 2730 } else if (trimDifferential) 2731 derived.setMaxElement(null); 2732 else 2733 derived.getMaxElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2734 } 2735 2736 if (derived.hasFixed()) { 2737 if (!Base.compareDeep(derived.getFixed(), base.getFixed(), true)) { 2738 base.setFixed(derived.getFixed().copy()); 2739 } else if (trimDifferential) 2740 derived.setFixed(null); 2741 else 2742 derived.getFixed().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2743 } 2744 2745 if (derived.hasPattern()) { 2746 if (!Base.compareDeep(derived.getPattern(), base.getPattern(), false)) { 2747 base.setPattern(derived.getPattern().copy()); 2748 } else 2749 if (trimDifferential) 2750 derived.setPattern(null); 2751 else 2752 derived.getPattern().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2753 } 2754 2755 List<ElementDefinitionExampleComponent> toDelB = new ArrayList<>(); 2756 List<ElementDefinitionExampleComponent> toDelD = new ArrayList<>(); 2757 for (ElementDefinitionExampleComponent ex : derived.getExample()) { 2758 boolean delete = ex.hasExtension(ToolingExtensions.EXT_ED_SUPPRESS); 2759 if (delete && "$all".equals(ex.getLabel())) { 2760 toDelB.addAll(base.getExample()); 2761 } else { 2762 boolean found = false; 2763 for (ElementDefinitionExampleComponent exS : base.getExample()) { 2764 if (Base.compareDeep(ex.getLabel(), exS.getLabel(), false) && Base.compareDeep(ex.getValue(), exS.getValue(), false)) { 2765 if (delete) { 2766 toDelB.add(exS); 2767 } else { 2768 found = true; 2769 } 2770 } 2771 } 2772 if (delete) { 2773 toDelD.add(ex); 2774 } else if (!found) { 2775 base.addExample(ex.copy()); 2776 } else if (trimDifferential) { 2777 derived.getExample().remove(ex); 2778 } else { 2779 ex.setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2780 } 2781 } 2782 } 2783 base.getExample().removeAll(toDelB); 2784 derived.getExample().removeAll(toDelD); 2785 2786 if (derived.hasMaxLengthElement()) { 2787 if (!Base.compareDeep(derived.getMaxLengthElement(), base.getMaxLengthElement(), false)) 2788 base.setMaxLengthElement(derived.getMaxLengthElement().copy()); 2789 else if (trimDifferential) 2790 derived.setMaxLengthElement(null); 2791 else 2792 derived.getMaxLengthElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2793 } 2794 2795 if (derived.hasMaxValue()) { 2796 if (!Base.compareDeep(derived.getMaxValue(), base.getMaxValue(), false)) 2797 base.setMaxValue(derived.getMaxValue().copy()); 2798 else if (trimDifferential) 2799 derived.setMaxValue(null); 2800 else 2801 derived.getMaxValue().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2802 } 2803 2804 if (derived.hasMinValue()) { 2805 if (!Base.compareDeep(derived.getMinValue(), base.getMinValue(), false)) 2806 base.setMinValue(derived.getMinValue().copy()); 2807 else if (trimDifferential) 2808 derived.setMinValue(null); 2809 else 2810 derived.getMinValue().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2811 } 2812 2813 // todo: what to do about conditions? 2814 // condition : id 0..* 2815 2816 boolean hasMustSupport = derived.hasMustSupportElement(); 2817 for (ElementDefinition ed : obligationProfileElements) { 2818 hasMustSupport = hasMustSupport || ed.hasMustSupportElement(); 2819 } 2820 if (hasMustSupport) { 2821 BooleanType mse = derived.getMustSupportElement().copy(); 2822 for (ElementDefinition ed : obligationProfileElements) { 2823 mergeExtensions(mse, ed.getMustSupportElement()); 2824 if (ed.getMustSupport()) { 2825 mse.setValue(true); 2826 } 2827 } 2828 if (!(base.hasMustSupportElement() && Base.compareDeep(base.getMustSupportElement(), mse, false))) { 2829 if (base.hasMustSupport() && base.getMustSupport() && !derived.getMustSupport() && !fromSlicer) { 2830 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)); 2831 } 2832 base.setMustSupportElement(mse); 2833 } else if (trimDifferential) 2834 derived.setMustSupportElement(null); 2835 else 2836 derived.getMustSupportElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2837 } 2838 2839 if (derived.hasMustHaveValueElement()) { 2840 if (!(base.hasMustHaveValueElement() && Base.compareDeep(derived.getMustHaveValueElement(), base.getMustHaveValueElement(), false))) { 2841 if (base.hasMustHaveValue() && base.getMustHaveValue() && !derived.getMustHaveValue() && !fromSlicer) { 2842 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)); 2843 } 2844 base.setMustHaveValueElement(derived.getMustHaveValueElement().copy()); 2845 } else if (trimDifferential) 2846 derived.setMustHaveValueElement(null); 2847 else 2848 derived.getMustHaveValueElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2849 } 2850 if (derived.hasValueAlternatives()) { 2851 if (!Base.compareDeep(derived.getValueAlternatives(), base.getValueAlternatives(), false)) 2852 for (CanonicalType s : derived.getValueAlternatives()) { 2853 if (!base.hasValueAlternatives(s.getValue())) 2854 base.getValueAlternatives().add(s.copy()); 2855 } 2856 else if (trimDifferential) 2857 derived.getValueAlternatives().clear(); 2858 else 2859 for (CanonicalType t : derived.getValueAlternatives()) 2860 t.setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2861 } 2862 2863 // profiles cannot change : isModifier, defaultValue, meaningWhenMissing 2864 // but extensions can change isModifier 2865 if (isExtension) { 2866 if (derived.hasIsModifierElement() && !(base.hasIsModifierElement() && Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false))) { 2867 base.setIsModifierElement(derived.getIsModifierElement().copy()); 2868 } else if (trimDifferential) { 2869 derived.setIsModifierElement(null); 2870 } else if (derived.hasIsModifierElement()) { 2871 derived.getIsModifierElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2872 } 2873 if (derived.hasIsModifierReasonElement() && !(base.hasIsModifierReasonElement() && Base.compareDeep(derived.getIsModifierReasonElement(), base.getIsModifierReasonElement(), false))) { 2874 base.setIsModifierReasonElement(derived.getIsModifierReasonElement().copy()); 2875 } else if (trimDifferential) { 2876 derived.setIsModifierReasonElement(null); 2877 } else if (derived.hasIsModifierReasonElement()) { 2878 derived.getIsModifierReasonElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2879 } 2880 if (base.getIsModifier() && !base.hasIsModifierReason()) { 2881 // we get here because modifier extensions don't get a modifier reason from the type 2882 base.setIsModifierReason("Modifier extensions are labelled as such because they modify the meaning or interpretation of the resource or element that contains them"); 2883 } 2884 } 2885 2886 boolean hasBinding = derived.hasBinding(); 2887 for (ElementDefinition ed : obligationProfileElements) { 2888 hasBinding = hasBinding || ed.hasBinding(); 2889 } 2890 if (hasBinding) { 2891 updateExtensionsFromDefinition(dest.getBinding(), source.getBinding(), derivedSrc, srcSD); 2892 ElementDefinitionBindingComponent binding = derived.getBinding(); 2893 for (ElementDefinition ed : obligationProfileElements) { 2894 for (Extension ext : ed.getBinding().getExtension()) { 2895 if (ToolingExtensions.EXT_BINDING_ADDITIONAL.equals(ext.getUrl())) { 2896 String p = ext.getExtensionString("purpose"); 2897 if (!Utilities.existsInList(p, "maximum", "required", "extensible")) { 2898 if (!binding.hasExtension(ext)) { 2899 binding.getExtension().add(ext.copy()); 2900 } 2901 } 2902 } 2903 } 2904 for (ElementDefinitionBindingAdditionalComponent ab : ed.getBinding().getAdditional()) { 2905 if (!Utilities.existsInList(ab.getPurpose().toCode(), "maximum", "required", "extensible")) { 2906 if (binding.hasAdditional(ab)) { 2907 binding.getAdditional().add(ab.copy()); 2908 } 2909 } 2910 } 2911 } 2912 2913 if (!base.hasBinding() || !Base.compareDeep(derived.getBinding(), base.getBinding(), false)) { 2914 if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && derived.getBinding().getStrength() != BindingStrength.REQUIRED) 2915 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)); 2916// throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode()); 2917 else if (base.hasBinding() && derived.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && base.getBinding().hasValueSet() && derived.getBinding().hasValueSet()) { 2918 ValueSet baseVs = context.findTxResource(ValueSet.class, base.getBinding().getValueSet(), srcSD); 2919 ValueSet contextVs = context.findTxResource(ValueSet.class, derived.getBinding().getValueSet(), derivedSrc); 2920 if (baseVs == null) { 2921 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING)); 2922 } else if (contextVs == null) { 2923 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING)); 2924 } else { 2925 ValueSetExpansionOutcome expBase = context.expandVS(baseVs, true, false); 2926 ValueSetExpansionOutcome expDerived = context.expandVS(contextVs, true, false); 2927 if (expBase.getValueset() == null) 2928 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING)); 2929 else if (expDerived.getValueset() == null) 2930 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING)); 2931 else if (ToolingExtensions.hasExtension(expBase.getValueset().getExpansion(), ToolingExtensions.EXT_EXP_TOOCOSTLY)) { 2932 if (ToolingExtensions.hasExtension(expDerived.getValueset().getExpansion(), ToolingExtensions.EXT_EXP_TOOCOSTLY) || expDerived.getValueset().getExpansion().getContains().size() > 100) { 2933 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)); 2934 } else { 2935 boolean ok = true; 2936 for (ValueSetExpansionContainsComponent cc : expDerived.getValueset().getExpansion().getContains()) { 2937 ValidationResult vr = context.validateCode(new ValidationOptions(), cc.getSystem(), cc.getVersion(), cc.getCode(), null, baseVs); 2938 if (!vr.isOk()) { 2939 ok = false; 2940 break; 2941 } 2942 } 2943 if (!ok) { 2944 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)); 2945 } 2946 } 2947 } else if (expBase.getValueset().getExpansion().getContains().size() == 1000 || 2948 expDerived.getValueset().getExpansion().getContains().size() == 1000) { 2949 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)); 2950 } else { 2951 String msgs = checkSubset(expBase.getValueset(), expDerived.getValueset()); 2952 if (msgs != null) { 2953 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)); 2954 } 2955 } 2956 } 2957 } 2958 ElementDefinitionBindingComponent d = derived.getBinding(); 2959 ElementDefinitionBindingComponent nb = base.getBinding().copy(); 2960 if (!COPY_BINDING_EXTENSIONS) { 2961 nb.getExtension().clear(); 2962 } 2963 nb.setDescription(null); 2964 for (Extension dex : d.getExtension()) { 2965 nb.getExtension().add(markExtensionSource(dex.copy(), false, srcSD)); 2966 } 2967 if (d.hasStrength()) { 2968 nb.setStrength(d.getStrength()); 2969 } 2970 if (d.hasDescription()) { 2971 nb.setDescription(d.getDescription()); 2972 } 2973 if (d.hasValueSet()) { 2974 nb.setValueSet(d.getValueSet()); 2975 } 2976 for (ElementDefinitionBindingAdditionalComponent ab : d.getAdditional()) { 2977 ElementDefinitionBindingAdditionalComponent eab = getMatchingAdditionalBinding(nb, ab); 2978 if (eab != null) { 2979 mergeAdditionalBinding(eab, ab); 2980 } else { 2981 nb.getAdditional().add(ab); 2982 } 2983 } 2984 base.setBinding(nb); 2985 } else if (trimDifferential) 2986 derived.setBinding(null); 2987 else 2988 derived.getBinding().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2989 } else if (base.hasBinding()) { 2990 base.getBinding().getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), ProfileUtilities.NON_INHERITED_ED_URLS)); 2991 for (Extension ex : base.getBinding().getExtension()) { 2992 markExtensionSource(ex, false, srcSD); 2993 } 2994 } 2995 2996 if (derived.hasIsSummaryElement()) { 2997 if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false)) { 2998 if (base.hasIsSummary() && !context.getVersion().equals("1.4.0")) // work around a known issue with some 1.4.0 cosntraints 2999 throw new Error(context.formatMessage(I18nConstants.ERROR_IN_PROFILE__AT__BASE_ISSUMMARY___DERIVED_ISSUMMARY__, purl, derived.getPath(), base.getIsSummaryElement().asStringValue(), derived.getIsSummaryElement().asStringValue())); 3000 base.setIsSummaryElement(derived.getIsSummaryElement().copy()); 3001 } else if (trimDifferential) 3002 derived.setIsSummaryElement(null); 3003 else 3004 derived.getIsSummaryElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 3005 } 3006 3007 // this would make sense but blows up the process later, so we let it happen anyway, and sort out the business rule elsewhere 3008 //if (!derived.hasContentReference() && !base.hasContentReference()) { 3009 3010 if (derived.hasType()) { 3011 if (!Base.compareDeep(derived.getType(), base.getType(), false)) { 3012 if (base.hasType()) { 3013 for (TypeRefComponent ts : derived.getType()) { 3014 checkTypeDerivation(purl, derivedSrc, base, derived, ts, path); 3015 } 3016 } 3017 base.getType().clear(); 3018 for (TypeRefComponent t : derived.getType()) { 3019 TypeRefComponent tt = t.copy(); 3020 // tt.setUserData(DERIVATION_EQUALS, true); 3021 base.getType().add(tt); 3022 for (Extension ex : tt.getExtension()) { 3023 markExtensionSource(ex, false, srcSD); 3024 } 3025 } 3026 } 3027 else if (trimDifferential) 3028 derived.getType().clear(); 3029 else { 3030 for (TypeRefComponent t : derived.getType()) { 3031 t.setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 3032 for (Extension ex : t.getExtension()) { 3033 markExtensionSource(ex, true, derivedSrc); 3034 } 3035 } 3036 } 3037 } 3038 3039 mappings.merge(derived, base); // note reversal of names to be correct in .merge() 3040 3041 // todo: constraints are cumulative. there is no replacing 3042 for (ElementDefinitionConstraintComponent s : base.getConstraint()) { 3043 s.setUserData(UserDataNames.SNAPSHOT_IS_DERIVED, true); 3044 if (!s.hasSource()) { 3045 s.setSource(srcSD.getUrl()); 3046 } 3047 } 3048 if (derived.hasConstraint()) { 3049 for (ElementDefinitionConstraintComponent s : derived.getConstraint()) { 3050 if (!base.hasConstraint(s.getKey())) { 3051 ElementDefinitionConstraintComponent inv = s.copy(); 3052 base.getConstraint().add(inv); 3053 } 3054 } 3055 } 3056 for (IdType id : derived.getCondition()) { 3057 if (!base.hasCondition(id)) { 3058 base.getCondition().add(id); 3059 } 3060 } 3061 3062 // now, check that we still have a bindable type; if not, delete the binding - see task 8477 3063 if (dest.hasBinding() && !hasBindableType(dest)) { 3064 dest.setBinding(null); 3065 } 3066 3067// // finally, we copy any extensions from source to dest 3068 //no, we already did. 3069// for (Extension ex : derived.getExtension()) { 3070// ! 3071// StructureDefinition sd = context.fetchResource(StructureDefinition.class, ex.getUrl(), derivedSrc); 3072// if (sd == null || sd.getSnapshot() == null || sd.getSnapshot().getElementFirstRep().getMax().equals("1")) { 3073// ToolingExtensions.removeExtension(dest, ex.getUrl()); 3074// } 3075// dest.addExtension(ex.copy()); 3076// } 3077 } 3078 if (dest.hasFixed()) { 3079 checkTypeOk(dest, dest.getFixed().fhirType(), srcSD, "fixed"); 3080 } 3081 if (dest.hasPattern()) { 3082 checkTypeOk(dest, dest.getPattern().fhirType(), srcSD, "pattern"); 3083 } 3084 //updateURLs(url, webUrl, dest); 3085 } 3086 3087 private static Extension markExtensionSource(Extension extension, boolean overrideSource, StructureDefinition srcSD) { 3088 if (overrideSource || !extension.hasUserData(UserDataNames.SNAPSHOT_EXTENSION_SOURCE)) { 3089 extension.setUserData(UserDataNames.SNAPSHOT_EXTENSION_SOURCE, srcSD); 3090 } 3091 if (Utilities.existsInList(extension.getUrl(), ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS)) { 3092 Extension sub = extension.getExtensionByUrl(ToolingExtensions.EXT_OBLIGATION_SOURCE, ToolingExtensions.EXT_OBLIGATION_SOURCE_SHORT); 3093 if (sub == null || overrideSource) { 3094 ToolingExtensions.setUriExtension(extension, ToolingExtensions.EXT_OBLIGATION_SOURCE, srcSD.getVersionedUrl()); 3095 } 3096 } 3097 return extension; 3098 } 3099 3100 private void updateExtensionsFromDefinition(Element dest, Element source, StructureDefinition destSD, StructureDefinition srcSD) { 3101 dest.getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), NON_INHERITED_ED_URLS) || (Utilities.existsInList(ext.getUrl(), DEFAULT_INHERITED_ED_URLS) && source.hasExtension(ext.getUrl()))); 3102 3103 for (Extension ext : source.getExtension()) { 3104 if (!dest.hasExtension(ext.getUrl())) { 3105 dest.getExtension().add(markExtensionSource(ext.copy(), false, srcSD)); 3106 } else if (Utilities.existsInList(ext.getUrl(), NON_OVERRIDING_ED_URLS)) { 3107 // do nothing 3108 for (Extension ex2 : dest.getExtensionsByUrl(ext.getUrl())) { 3109 markExtensionSource(ex2, true, destSD); 3110 } 3111 } else if (Utilities.existsInList(ext.getUrl(), OVERRIDING_ED_URLS)) { 3112 dest.getExtensionByUrl(ext.getUrl()).setValue(ext.getValue()); 3113 markExtensionSource(dest.getExtensionByUrl(ext.getUrl()), false, srcSD); 3114 } else { 3115 dest.getExtension().add(markExtensionSource(ext.copy(), false, srcSD)); 3116 } 3117 } 3118 } 3119 3120 private void mergeAdditionalBinding(ElementDefinitionBindingAdditionalComponent dest, ElementDefinitionBindingAdditionalComponent source) { 3121 for (UsageContext t : source.getUsage()) { 3122 if (!hasUsage(dest, t)) { 3123 dest.addUsage(t); 3124 } 3125 } 3126 if (source.getAny()) { 3127 source.setAny(true); 3128 } 3129 if (source.hasShortDoco()) { 3130 dest.setShortDoco(source.getShortDoco()); 3131 } 3132 if (source.hasDocumentation()) { 3133 dest.setDocumentation(source.getDocumentation()); 3134 } 3135 3136 } 3137 3138 private boolean hasUsage(ElementDefinitionBindingAdditionalComponent dest, UsageContext tgt) { 3139 for (UsageContext t : dest.getUsage()) { 3140 if (t.getCode() != null && t.getCode().matches(tgt.getCode()) && t.getValue() != null && t.getValue().equals(tgt.getValue())) { 3141 return true; 3142 } 3143 } 3144 return false; 3145 } 3146 3147 private ElementDefinitionBindingAdditionalComponent getMatchingAdditionalBinding(ElementDefinitionBindingComponent nb,ElementDefinitionBindingAdditionalComponent ab) { 3148 for (ElementDefinitionBindingAdditionalComponent t : nb.getAdditional()) { 3149 if (t.getValueSet() != null && t.getValueSet().equals(ab.getValueSet()) && t.getPurpose() == ab.getPurpose() && !ab.hasUsage()) { 3150 return t; 3151 } 3152 } 3153 return null; 3154 } 3155 3156 private void mergeExtensions(Element tgt, Element src) { 3157 tgt.getExtension().addAll(src.getExtension()); 3158 } 3159 3160 private void checkTypeDerivation(String purl, StructureDefinition srcSD, ElementDefinition base, ElementDefinition derived, TypeRefComponent ts, String path) { 3161 boolean ok = false; 3162 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 3163 String t = ts.getWorkingCode(); 3164 String tDesc = ts.toString(); 3165 for (TypeRefComponent td : base.getType()) {; 3166 boolean matchType = false; 3167 String tt = td.getWorkingCode(); 3168 b.append(td.toString()); 3169 if (td.hasCode() && (tt.equals(t))) { 3170 matchType = true; 3171 } 3172 if (!matchType) { 3173 StructureDefinition sdt = context.fetchTypeDefinition(tt); 3174 if (sdt != null && (sdt.getAbstract() || sdt.getKind() == StructureDefinitionKind.LOGICAL)) { 3175 StructureDefinition sdb = context.fetchTypeDefinition(t); 3176 while (sdb != null && !matchType) { 3177 matchType = sdb.getType().equals(sdt.getType()); 3178 sdb = context.fetchResource(StructureDefinition.class, sdb.getBaseDefinition(), sdb); 3179 } 3180 } 3181 } 3182 // work around for old badly generated SDs 3183// if (DONT_DO_THIS && Utilities.existsInList(tt, "Extension", "uri", "string", "Element")) { 3184// matchType = true; 3185// } 3186// if (DONT_DO_THIS && Utilities.existsInList(tt, "Resource","DomainResource") && pkp.isResource(t)) { 3187// matchType = true; 3188// } 3189 if (matchType) { 3190 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"); 3191 if (ts.hasTargetProfile()) { 3192 // check that any derived target has a reference chain back to one of the base target profiles 3193 for (UriType u : ts.getTargetProfile()) { 3194 String url = u.getValue(); 3195 boolean tgtOk = !td.hasTargetProfile() || sdConformsToTargets(path, derived.getPath(), url, td); 3196 if (tgtOk) { 3197 ok = true; 3198 } else { 3199 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)); 3200 } 3201 } 3202 } else { 3203 ok = true; 3204 } 3205 } 3206 } 3207 if (!ok && !isSuppressIgnorableExceptions()) { 3208 throw new DefinitionException(context.formatMessage(I18nConstants.STRUCTUREDEFINITION__AT__ILLEGAL_CONSTRAINED_TYPE__FROM__IN_, purl, derived.getPath(), tDesc, b.toString(), srcSD.getUrl())); 3209 } 3210 } 3211 3212 3213 private boolean sdConformsToTargets(String path, String dPath, String url, TypeRefComponent td) { 3214 if (td.hasTargetProfile(url)) { 3215 return true; 3216 } 3217 if (url != null && url.contains("|") && td.hasTargetProfile(url.substring(0, url.indexOf("|")))) { 3218 return true; 3219 } 3220 StructureDefinition sd = context.fetchResource(StructureDefinition.class, url); 3221 if (sd == null) { 3222 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)); 3223 return true; 3224 } else { 3225 if (sd.hasBaseDefinition() && sdConformsToTargets(path, dPath, sd.getBaseDefinition(), td)) { 3226 return true; 3227 } 3228 for (Extension ext : sd.getExtensionsByUrl(ToolingExtensions.EXT_SD_IMPOSE_PROFILE)) { 3229 if (sdConformsToTargets(path, dPath, ext.getValueCanonicalType().asStringValue(), td)) { 3230 return true; 3231 } 3232 } 3233 } 3234 return false; 3235 } 3236 3237 private void checkTypeOk(ElementDefinition dest, String ft, StructureDefinition sd, String fieldName) { 3238 boolean ok = false; 3239 Set<String> types = new HashSet<>(); 3240 if (dest.getPath().contains(".")) { 3241 for (TypeRefComponent t : dest.getType()) { 3242 if (t.hasCode()) { 3243 types.add(t.getWorkingCode()); 3244 } 3245 ok = ok || ft.equals(t.getWorkingCode()); 3246 } 3247 } else { 3248 types.add(sd.getType()); 3249 ok = ok || ft.equals(sd.getType()); 3250 3251 } 3252 if (!ok) { 3253 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)); 3254 } 3255 } 3256 3257 private boolean hasBindableType(ElementDefinition ed) { 3258 for (TypeRefComponent tr : ed.getType()) { 3259 if (Utilities.existsInList(tr.getWorkingCode(), "Coding", "CodeableConcept", "Quantity", "uri", "string", "code", "CodeableReference")) { 3260 return true; 3261 } 3262 StructureDefinition sd = context.fetchTypeDefinition(tr.getCode()); 3263 if (sd != null && sd.hasExtension(ToolingExtensions.EXT_BINDING_STYLE)) { 3264 return true; 3265 } 3266 if (sd != null && sd.hasExtension(ToolingExtensions.EXT_TYPE_CHARACTERISTICS) && 3267 "can-bind".equals(ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_TYPE_CHARACTERISTICS))) { 3268 return true; 3269 } 3270 } 3271 return false; 3272 } 3273 3274 3275 private boolean isLargerMax(String derived, String base) { 3276 if ("*".equals(base)) { 3277 return false; 3278 } 3279 if ("*".equals(derived)) { 3280 return true; 3281 } 3282 return Integer.parseInt(derived) > Integer.parseInt(base); 3283 } 3284 3285 3286 private String checkSubset(ValueSet expBase, ValueSet expDerived) { 3287 Set<String> codes = new HashSet<>(); 3288 checkCodesInExpansion(codes, expDerived.getExpansion().getContains(), expBase.getExpansion()); 3289 if (codes.isEmpty()) { 3290 return null; 3291 } else { 3292 return "The codes '"+CommaSeparatedStringBuilder.join(",", codes)+"' are not in the base valueset"; 3293 } 3294 } 3295 3296 3297 private void checkCodesInExpansion(Set<String> codes, List<ValueSetExpansionContainsComponent> contains, ValueSetExpansionComponent expansion) { 3298 for (ValueSetExpansionContainsComponent cc : contains) { 3299 if (!inExpansion(cc, expansion.getContains())) { 3300 codes.add(cc.getCode()); 3301 } 3302 checkCodesInExpansion(codes, cc.getContains(), expansion); 3303 } 3304 } 3305 3306 3307 private boolean inExpansion(ValueSetExpansionContainsComponent cc, List<ValueSetExpansionContainsComponent> contains) { 3308 for (ValueSetExpansionContainsComponent cc1 : contains) { 3309 if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode())) { 3310 return true; 3311 } 3312 if (inExpansion(cc, cc1.getContains())) { 3313 return true; 3314 } 3315 } 3316 return false; 3317 } 3318 3319 public void closeDifferential(StructureDefinition base, StructureDefinition derived) throws FHIRException { 3320 for (ElementDefinition edb : base.getSnapshot().getElement()) { 3321 if (isImmediateChild(edb) && !edb.getPath().endsWith(".id")) { 3322 ElementDefinition edm = getMatchInDerived(edb, derived.getDifferential().getElement()); 3323 if (edm == null) { 3324 ElementDefinition edd = derived.getDifferential().addElement(); 3325 edd.setPath(edb.getPath()); 3326 edd.setMax("0"); 3327 } else if (edb.hasSlicing()) { 3328 closeChildren(base, edb, derived, edm); 3329 } 3330 } 3331 } 3332 sortDifferential(base, derived, derived.getName(), new ArrayList<String>(), false); 3333 } 3334 3335 private void closeChildren(StructureDefinition base, ElementDefinition edb, StructureDefinition derived, ElementDefinition edm) { 3336// String path = edb.getPath()+"."; 3337 int baseStart = base.getSnapshot().getElement().indexOf(edb); 3338 int baseEnd = findEnd(base.getSnapshot().getElement(), edb, baseStart+1); 3339 int diffStart = derived.getDifferential().getElement().indexOf(edm); 3340 int diffEnd = findEnd(derived.getDifferential().getElement(), edm, diffStart+1); 3341 3342 for (int cBase = baseStart; cBase < baseEnd; cBase++) { 3343 ElementDefinition edBase = base.getSnapshot().getElement().get(cBase); 3344 if (isImmediateChild(edBase, edb)) { 3345 ElementDefinition edMatch = getMatchInDerived(edBase, derived.getDifferential().getElement(), diffStart, diffEnd); 3346 if (edMatch == null) { 3347 ElementDefinition edd = derived.getDifferential().addElement(); 3348 edd.setPath(edBase.getPath()); 3349 edd.setMax("0"); 3350 } else { 3351 closeChildren(base, edBase, derived, edMatch); 3352 } 3353 } 3354 } 3355 } 3356 3357 private int findEnd(List<ElementDefinition> list, ElementDefinition ed, int cursor) { 3358 String path = ed.getPath()+"."; 3359 while (cursor < list.size() && list.get(cursor).getPath().startsWith(path)) { 3360 cursor++; 3361 } 3362 return cursor; 3363 } 3364 3365 3366 private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list) { 3367 for (ElementDefinition t : list) { 3368 if (t.getPath().equals(ed.getPath())) { 3369 return t; 3370 } 3371 } 3372 return null; 3373 } 3374 3375 private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list, int start, int end) { 3376 for (int i = start; i < end; i++) { 3377 ElementDefinition t = list.get(i); 3378 if (t.getPath().equals(ed.getPath())) { 3379 return t; 3380 } 3381 } 3382 return null; 3383 } 3384 3385 3386 private boolean isImmediateChild(ElementDefinition ed) { 3387 String p = ed.getPath(); 3388 if (!p.contains(".")) { 3389 return false; 3390 } 3391 p = p.substring(p.indexOf(".")+1); 3392 return !p.contains("."); 3393 } 3394 3395 private boolean isImmediateChild(ElementDefinition candidate, ElementDefinition base) { 3396 String p = candidate.getPath(); 3397 if (!p.contains(".")) 3398 return false; 3399 if (!p.startsWith(base.getPath()+".")) 3400 return false; 3401 p = p.substring(base.getPath().length()+1); 3402 return !p.contains("."); 3403 } 3404 3405 3406 3407 private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) { 3408 int i = ed.getSnapshot().getElement().indexOf(c) + 1; 3409 while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) { 3410 if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath()+".url")) 3411 return ed.getSnapshot().getElement().get(i); 3412 i++; 3413 } 3414 return null; 3415 } 3416 3417 3418 3419 protected ElementDefinitionResolution getElementById(StructureDefinition source, List<ElementDefinition> elements, String contentReference) { 3420 if (!contentReference.startsWith("#") && contentReference.contains("#")) { 3421 String url = contentReference.substring(0, contentReference.indexOf("#")); 3422 contentReference = contentReference.substring(contentReference.indexOf("#")); 3423 if (!url.equals(source.getUrl())){ 3424 source = context.fetchResource(StructureDefinition.class, url, source); 3425 if (source == null) { 3426 return null; 3427 } 3428 elements = source.getSnapshot().getElement(); 3429 } 3430 } 3431 for (ElementDefinition ed : elements) 3432 if (ed.hasId() && ("#"+ed.getId()).equals(contentReference)) 3433 return new ElementDefinitionResolution(source, ed); 3434 return null; 3435 } 3436 3437 3438 public static String describeExtensionContext(StructureDefinition ext) { 3439 StringBuilder b = new StringBuilder(); 3440 b.append("Use on "); 3441 for (int i = 0; i < ext.getContext().size(); i++) { 3442 StructureDefinitionContextComponent ec = ext.getContext().get(i); 3443 if (i > 0) 3444 b.append(i < ext.getContext().size() - 1 ? ", " : " or "); 3445 b.append(ec.getType().getDisplay()); 3446 b.append(" "); 3447 b.append(ec.getExpression()); 3448 } 3449 if (ext.hasContextInvariant()) { 3450 b.append(", with <a href=\"structuredefinition-definitions.html#StructureDefinition.contextInvariant\">Context Invariant</a> = "); 3451 boolean first = true; 3452 for (StringType s : ext.getContextInvariant()) { 3453 if (first) 3454 first = false; 3455 else 3456 b.append(", "); 3457 b.append("<code>"+s.getValue()+"</code>"); 3458 } 3459 } 3460 return b.toString(); 3461 } 3462 3463 3464 3465 3466// public XhtmlNode generateTable(String defFile, StructureDefinition profile, boolean diff, String imageFolder, boolean inlineGraphics, String profileBaseFileName, boolean snapshot, String corePath, String imagePath, 3467// boolean logicalModel, boolean allInvariants, Set<String> outputTracker, boolean mustSupport, RenderingContext rc) throws IOException, FHIRException { 3468// return generateTable(defFile, profile, diff, imageFolder, inlineGraphics, profileBaseFileName, snapshot, corePath, imagePath, logicalModel, allInvariants, outputTracker, mustSupport, rc, ""); 3469// } 3470 3471 3472 3473 3474 3475 protected String tail(String path) { 3476 if (path == null) { 3477 return ""; 3478 } else if (path.contains(".")) 3479 return path.substring(path.lastIndexOf('.')+1); 3480 else 3481 return path; 3482 } 3483 3484 private boolean isDataType(String value) { 3485 StructureDefinition sd = context.fetchTypeDefinition(value); 3486 if (sd == null) // might be running before all SDs are available 3487 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", 3488 "ContactDetail", "Contributor", "DataRequirement", "Expression", "ParameterDefinition", "RelatedArtifact", "TriggerDefinition", "UsageContext"); 3489 else 3490 return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION; 3491 } 3492 3493 private boolean isConstrainedDataType(String value) { 3494 StructureDefinition sd = context.fetchTypeDefinition(value); 3495 if (sd == null) // might be running before all SDs are available 3496 return Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity"); 3497 else 3498 return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.CONSTRAINT; 3499 } 3500 3501 private String baseType(String value) { 3502 StructureDefinition sd = context.fetchTypeDefinition(value); 3503 if (sd != null) // might be running before all SDs are available 3504 return sd.getTypeName(); 3505 if (Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity")) 3506 return "Quantity"; 3507 throw new Error(context.formatMessage(I18nConstants.INTERNAL_ERROR___TYPE_NOT_KNOWN_, value)); 3508 } 3509 3510 3511 protected boolean isPrimitive(String value) { 3512 StructureDefinition sd = context.fetchTypeDefinition(value); 3513 if (sd == null) // might be running before all SDs are available 3514 return Utilities.existsInList(value, "base64Binary", "boolean", "canonical", "code", "date", "dateTime", "decimal", "id", "instant", "integer", "integer64", "markdown", "oid", "positiveInt", "string", "time", "unsignedInt", "uri", "url", "uuid"); 3515 else 3516 return sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 3517 } 3518 3519// private static String listStructures(StructureDefinition p) { 3520// StringBuilder b = new StringBuilder(); 3521// boolean first = true; 3522// for (ProfileStructureComponent s : p.getStructure()) { 3523// if (first) 3524// first = false; 3525// else 3526// b.append(", "); 3527// if (pkp != null && pkp.hasLinkFor(s.getType())) 3528// b.append("<a href=\""+pkp.getLinkFor(s.getType())+"\">"+s.getType()+"</a>"); 3529// else 3530// b.append(s.getType()); 3531// } 3532// return b.toString(); 3533// } 3534 3535 3536 public StructureDefinition getProfile(StructureDefinition source, String url) { 3537 StructureDefinition profile = null; 3538 String code = null; 3539 if (url.startsWith("#")) { 3540 profile = source; 3541 code = url.substring(1); 3542 } else if (context != null) { 3543 String[] parts = url.split("\\#"); 3544 profile = context.fetchResource(StructureDefinition.class, parts[0], source); 3545 code = parts.length == 1 ? null : parts[1]; 3546 } 3547 if (profile == null) 3548 return null; 3549 if (code == null) 3550 return profile; 3551 for (Resource r : profile.getContained()) { 3552 if (r instanceof StructureDefinition && r.getId().equals(code)) 3553 return (StructureDefinition) r; 3554 } 3555 return null; 3556 } 3557 3558 3559 3560 private static class ElementDefinitionHolder { 3561 private String name; 3562 private ElementDefinition self; 3563 private int baseIndex = 0; 3564 private List<ElementDefinitionHolder> children; 3565 private boolean placeHolder = false; 3566 3567 public ElementDefinitionHolder(ElementDefinition self, boolean isPlaceholder) { 3568 super(); 3569 this.self = self; 3570 this.name = self.getPath(); 3571 this.placeHolder = isPlaceholder; 3572 children = new ArrayList<ElementDefinitionHolder>(); 3573 } 3574 3575 public ElementDefinitionHolder(ElementDefinition self) { 3576 this(self, false); 3577 } 3578 3579 public ElementDefinition getSelf() { 3580 return self; 3581 } 3582 3583 public List<ElementDefinitionHolder> getChildren() { 3584 return children; 3585 } 3586 3587 public int getBaseIndex() { 3588 return baseIndex; 3589 } 3590 3591 public void setBaseIndex(int baseIndex) { 3592 this.baseIndex = baseIndex; 3593 } 3594 3595 public boolean isPlaceHolder() { 3596 return this.placeHolder; 3597 } 3598 3599 @Override 3600 public String toString() { 3601 if (self.hasSliceName()) 3602 return self.getPath()+"("+self.getSliceName()+")"; 3603 else 3604 return self.getPath(); 3605 } 3606 } 3607 3608 private static class ElementDefinitionComparer implements Comparator<ElementDefinitionHolder> { 3609 3610 private boolean inExtension; 3611 private List<ElementDefinition> snapshot; 3612 private int prefixLength; 3613 private String base; 3614 private String name; 3615 private String baseName; 3616 private Set<String> errors = new HashSet<String>(); 3617 3618 public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base, int prefixLength, String name, String baseName) { 3619 this.inExtension = inExtension; 3620 this.snapshot = snapshot; 3621 this.prefixLength = prefixLength; 3622 this.base = base; 3623 if (Utilities.isAbsoluteUrl(base)) { 3624 this.base = urlTail(base); 3625 } 3626 this.name = name; 3627 this.baseName = baseName; 3628 } 3629 3630 @Override 3631 public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) { 3632 if (o1.getBaseIndex() == 0) { 3633 o1.setBaseIndex(find(o1.getSelf().getPath(), true)); 3634 } 3635 if (o2.getBaseIndex() == 0) { 3636 o2.setBaseIndex(find(o2.getSelf().getPath(), true)); 3637 } 3638 return o1.getBaseIndex() - o2.getBaseIndex(); 3639 } 3640 3641 private int find(String path, boolean mandatory) { 3642 String op = path; 3643 int lc = 0; 3644 String actual = base+path.substring(prefixLength); 3645 for (int i = 0; i < snapshot.size(); i++) { 3646 String p = snapshot.get(i).getPath(); 3647 if (p.equals(actual)) { 3648 return i; 3649 } 3650 if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length()-3)) && !(actual.endsWith("[x]")) && !actual.substring(p.length()-3).contains(".")) { 3651 return i; 3652 } 3653 if (actual.endsWith("[x]") && p.startsWith(actual.substring(0, actual.length()-3)) && !p.substring(actual.length()-3).contains(".")) { 3654 return i; 3655 } 3656 if (path.startsWith(p+".") && snapshot.get(i).hasContentReference()) { 3657 String ref = snapshot.get(i).getContentReference(); 3658 if (ref.substring(1, 2).toUpperCase().equals(ref.substring(1,2))) { 3659 actual = base+(ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength); 3660 path = actual; 3661 } else if (ref.startsWith("http:")) { 3662 actual = base+(ref.substring(ref.indexOf("#")+1)+"."+path.substring(p.length()+1)).substring(prefixLength); 3663 path = actual; 3664 } else { 3665 // Older versions of FHIR (e.g. 2016May) had reference of the style #parameter instead of #Parameters.parameter, so we have to handle that 3666 actual = base+(path.substring(0, path.indexOf(".")+1) + ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength); 3667 path = actual; 3668 } 3669 3670 i = 0; 3671 lc++; 3672 if (lc > MAX_RECURSION_LIMIT) 3673 throw new Error("Internal recursion detection: find() loop path recursion > "+MAX_RECURSION_LIMIT+" - check paths are valid (for path "+path+"/"+op+")"); 3674 } 3675 } 3676 if (mandatory) { 3677 if (prefixLength == 0) 3678 errors.add("Differential contains path "+path+" which is not found in the base "+baseName); 3679 else 3680 errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the in base "+ baseName); 3681 } 3682 return 0; 3683 } 3684 3685 public void checkForErrors(List<String> errorList) { 3686 if (errors.size() > 0) { 3687// CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 3688// for (String s : errors) 3689// b.append("StructureDefinition "+name+": "+s); 3690// throw new DefinitionException(b.toString()); 3691 for (String s : errors) 3692 if (s.startsWith("!")) 3693 errorList.add("!StructureDefinition "+name+": "+s.substring(1)); 3694 else 3695 errorList.add("StructureDefinition "+name+": "+s); 3696 } 3697 } 3698 } 3699 3700 3701 public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List<String> errors, boolean errorIfChanges) throws FHIRException { 3702 int index = 0; 3703 for (ElementDefinition ed : diff.getDifferential().getElement()) { 3704 ed.setUserData(UserDataNames.SNAPSHOT_SORT_ed_index, Integer.toString(index)); 3705 index++; 3706 } 3707 List<ElementDefinition> original = new ArrayList<>(); 3708 original.addAll(diff.getDifferential().getElement()); 3709 final List<ElementDefinition> diffList = diff.getDifferential().getElement(); 3710 int lastCount = diffList.size(); 3711 // first, we move the differential elements into a tree 3712 if (diffList.isEmpty()) 3713 return; 3714 3715 ElementDefinitionHolder edh = null; 3716 int i = 0; 3717 if (diffList.get(0).getPath().contains(".")) { 3718 String newPath = diffList.get(0).getPath().split("\\.")[0]; 3719 ElementDefinition e = new ElementDefinition(newPath); 3720 edh = new ElementDefinitionHolder(e, true); 3721 } else { 3722 edh = new ElementDefinitionHolder(diffList.get(0)); 3723 i = 1; 3724 } 3725 3726 boolean hasSlicing = false; 3727 List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly 3728 for(ElementDefinition elt : diffList) { 3729 if (elt.hasSlicing() || paths.contains(elt.getPath())) { 3730 hasSlicing = true; 3731 break; 3732 } 3733 paths.add(elt.getPath()); 3734 } 3735 3736 processElementsIntoTree(edh, i, diff.getDifferential().getElement()); 3737 3738 // now, we sort the siblings throughout the tree 3739 ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name, base.getType()); 3740 sortElements(edh, cmp, errors); 3741 3742 // now, we serialise them back to a list 3743 List<ElementDefinition> newDiff = new ArrayList<>(); 3744 writeElements(edh, newDiff); 3745 if (errorIfChanges) { 3746 compareDiffs(original, newDiff, errors); 3747 } 3748 diffList.clear(); 3749 diffList.addAll(newDiff); 3750 3751 if (lastCount != diffList.size()) 3752 errors.add("Sort failed: counts differ; at least one of the paths in the differential is illegal"); 3753 } 3754 3755 private void compareDiffs(List<ElementDefinition> diffList, List<ElementDefinition> newDiff, List<String> errors) { 3756 if (diffList.size() != newDiff.size()) { 3757 errors.add("The diff list size changed when sorting - was "+diffList.size()+" is now "+newDiff.size()+ 3758 " ["+CommaSeparatedStringBuilder.buildObjects(diffList)+"]/["+CommaSeparatedStringBuilder.buildObjects(newDiff)+"]"); 3759 } else { 3760 for (int i = 0; i < Integer.min(diffList.size(), newDiff.size()); i++) { 3761 ElementDefinition e = diffList.get(i); 3762 ElementDefinition n = newDiff.get(i); 3763 if (!n.getPath().equals(e.getPath())) { 3764 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)"); 3765 return; 3766 } 3767 } 3768 } 3769 } 3770 3771 3772 private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) { 3773 String path = edh.getSelf().getPath(); 3774 final String prefix = path + "."; 3775 while (i < list.size() && list.get(i).getPath().startsWith(prefix)) { 3776 if (list.get(i).getPath().substring(prefix.length()+1).contains(".")) { 3777 String newPath = prefix + list.get(i).getPath().substring(prefix.length()).split("\\.")[0]; 3778 ElementDefinition e = new ElementDefinition(newPath); 3779 ElementDefinitionHolder child = new ElementDefinitionHolder(e, true); 3780 edh.getChildren().add(child); 3781 i = processElementsIntoTree(child, i, list); 3782 3783 } else { 3784 ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i)); 3785 edh.getChildren().add(child); 3786 i = processElementsIntoTree(child, i+1, list); 3787 } 3788 } 3789 return i; 3790 } 3791 3792 private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) throws FHIRException { 3793 if (edh.getChildren().size() == 1) 3794 // 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 3795 edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath(), false); 3796 else 3797 Collections.sort(edh.getChildren(), cmp); 3798 if (debug) { 3799 cmp.checkForErrors(errors); 3800 } 3801 3802 for (ElementDefinitionHolder child : edh.getChildren()) { 3803 if (child.getChildren().size() > 0) { 3804 ElementDefinitionComparer ccmp = getComparer(cmp, child); 3805 if (ccmp != null) { 3806 sortElements(child, ccmp, errors); 3807 } 3808 } 3809 } 3810 } 3811 3812 3813 public ElementDefinitionComparer getComparer(ElementDefinitionComparer cmp, ElementDefinitionHolder child) throws FHIRException, Error { 3814 // what we have to check for here is running off the base profile into a data type profile 3815 ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex()); 3816 ElementDefinitionComparer ccmp; 3817 if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getWorkingCode()) || ed.getType().get(0).getWorkingCode().equals(ed.getPath())) { 3818 if (ed.hasType() && "Resource".equals(ed.getType().get(0).getWorkingCode()) && (child.getSelf().hasType() && child.getSelf().getType().get(0).hasProfile())) { 3819 if (child.getSelf().getType().get(0).getProfile().size() > 1) { 3820 throw new FHIRException(context.formatMessage(I18nConstants.UNHANDLED_SITUATION_RESOURCE_IS_PROFILED_TO_MORE_THAN_ONE_OPTION__CANNOT_SORT_PROFILE)); 3821 } 3822 StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue()); 3823 while (profile != null && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) { 3824 profile = context.fetchResource(StructureDefinition.class, profile.getBaseDefinition()); 3825 } 3826 if (profile==null) { 3827 ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case 3828 } else { 3829 ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), profile.getType(), child.getSelf().getPath().length(), cmp.name, profile.present()); 3830 } 3831 } else { 3832 ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name, cmp.name); 3833 } 3834 } else if (ed.getType().get(0).getWorkingCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) { 3835 StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue()); 3836 if (profile==null) 3837 ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case 3838 else 3839 ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), resolveType(ed.getType().get(0).getWorkingCode()), child.getSelf().getPath().length(), cmp.name, profile.present()); 3840 } else if (ed.getType().size() == 1 && !ed.getType().get(0).getWorkingCode().equals("*")) { 3841 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode())); 3842 if (profile==null) 3843 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath())); 3844 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), resolveType(ed.getType().get(0).getWorkingCode()), child.getSelf().getPath().length(), cmp.name, profile.present()); 3845 } else if (child.getSelf().getType().size() == 1) { 3846 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(child.getSelf().getType().get(0).getWorkingCode())); 3847 if (profile==null) 3848 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath())); 3849 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), child.getSelf().getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present()); 3850 } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) { 3851 String edLastNode = ed.getPath().replaceAll("(.*\\.)*(.*)", "$2"); 3852 String childLastNode = child.getSelf().getPath().replaceAll("(.*\\.)*(.*)", "$2"); 3853 String p = childLastNode.substring(edLastNode.length()-3); 3854 if (isPrimitive(Utilities.uncapitalize(p))) 3855 p = Utilities.uncapitalize(p); 3856 StructureDefinition sd = context.fetchResource(StructureDefinition.class, sdNs(p)); 3857 if (sd == null) 3858 throw new Error(context.formatMessage(I18nConstants.UNABLE_TO_FIND_PROFILE__AT_, p, ed.getId())); 3859 ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), cmp.name, sd.present()); 3860 } else if (child.getSelf().hasType() && child.getSelf().getType().get(0).getWorkingCode().equals("Reference")) { 3861 for (TypeRefComponent t: child.getSelf().getType()) { 3862 if (!t.getWorkingCode().equals("Reference")) { 3863 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()))); 3864 } 3865 } 3866 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode())); 3867 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present()); 3868 } else if (!child.getSelf().hasType() && ed.getType().get(0).getWorkingCode().equals("Reference")) { 3869 for (TypeRefComponent t: ed.getType()) { 3870 if (!t.getWorkingCode().equals("Reference")) { 3871 throw new Error(context.formatMessage(I18nConstants.NOT_HANDLED_YET_SORTELEMENTS_, ed.getPath(), typeCode(ed.getType()))); 3872 } 3873 } 3874 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode())); 3875 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present()); 3876 } else { 3877 // this is allowed if we only profile the extensions 3878 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs("Element")); 3879 if (profile==null) 3880 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath())); 3881 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), "Element", child.getSelf().getPath().length(), cmp.name, profile.present()); 3882// throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")"); 3883 } 3884 return ccmp; 3885 } 3886 3887 private String resolveType(String code) { 3888 if (Utilities.isAbsoluteUrl(code)) { 3889 StructureDefinition sd = context.fetchResource(StructureDefinition.class, code); 3890 if (sd != null) { 3891 return sd.getType(); 3892 } 3893 } 3894 return code; 3895 } 3896 3897 private static String sdNs(String type) { 3898 return sdNs(type, null); 3899 } 3900 3901 public static String sdNs(String type, String overrideVersionNs) { 3902 if (Utilities.isAbsoluteUrl(type)) 3903 return type; 3904 else if (overrideVersionNs != null) 3905 return Utilities.pathURL(overrideVersionNs, type); 3906 else 3907 return "http://hl7.org/fhir/StructureDefinition/"+type; 3908 } 3909 3910 3911 private boolean isAbstract(String code) { 3912 return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") || code.equals("DomainResource"); 3913 } 3914 3915 3916 private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) { 3917 if (!edh.isPlaceHolder()) 3918 list.add(edh.getSelf()); 3919 for (ElementDefinitionHolder child : edh.getChildren()) { 3920 writeElements(child, list); 3921 } 3922 } 3923 3924 /** 3925 * First compare element by path then by name if same 3926 */ 3927 private static class ElementNameCompare implements Comparator<ElementDefinition> { 3928 3929 @Override 3930 public int compare(ElementDefinition o1, ElementDefinition o2) { 3931 String path1 = normalizePath(o1); 3932 String path2 = normalizePath(o2); 3933 int cmp = path1.compareTo(path2); 3934 if (cmp == 0) { 3935 String name1 = o1.hasSliceName() ? o1.getSliceName() : ""; 3936 String name2 = o2.hasSliceName() ? o2.getSliceName() : ""; 3937 cmp = name1.compareTo(name2); 3938 } 3939 return cmp; 3940 } 3941 3942 private static String normalizePath(ElementDefinition e) { 3943 if (!e.hasPath()) return ""; 3944 String path = e.getPath(); 3945 // if sorting element names make sure onset[x] appears before onsetAge, onsetDate, etc. 3946 // so strip off the [x] suffix when comparing the path names. 3947 if (path.endsWith("[x]")) { 3948 path = path.substring(0, path.length()-3); 3949 } 3950 return path; 3951 } 3952 3953 } 3954 3955 3956 // generate schematrons for the rules in a structure definition 3957 public void generateSchematrons(OutputStream dest, StructureDefinition structure) throws IOException, DefinitionException { 3958 if (structure.getDerivation() != TypeDerivationRule.CONSTRAINT) 3959 throw new DefinitionException(context.formatMessage(I18nConstants.NOT_THE_RIGHT_KIND_OF_STRUCTURE_TO_GENERATE_SCHEMATRONS_FOR)); 3960 if (!structure.hasSnapshot()) 3961 throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT)); 3962 3963 StructureDefinition base = context.fetchResource(StructureDefinition.class, structure.getBaseDefinition(), structure); 3964 3965 if (base != null) { 3966 SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName()); 3967 3968 ElementDefinition ed = structure.getSnapshot().getElement().get(0); 3969 generateForChildren(sch, "f:"+ed.getPath(), ed, structure, base); 3970 sch.dump(); 3971 } 3972 } 3973 3974 // generate a CSV representation of the structure definition 3975 public void generateCsv(OutputStream dest, StructureDefinition structure, boolean asXml) throws IOException, DefinitionException, Exception { 3976 if (!structure.hasSnapshot()) 3977 throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT)); 3978 3979 CSVWriter csv = new CSVWriter(dest, structure, asXml); 3980 3981 for (ElementDefinition child : structure.getSnapshot().getElement()) { 3982 csv.processElement(null, child); 3983 } 3984 csv.dump(); 3985 } 3986 3987 // generate a CSV representation of the structure definition 3988 public void addToCSV(CSVWriter csv, StructureDefinition structure) throws IOException, DefinitionException, Exception { 3989 if (!structure.hasSnapshot()) 3990 throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT)); 3991 3992 for (ElementDefinition child : structure.getSnapshot().getElement()) { 3993 csv.processElement(structure, child); 3994 } 3995 } 3996 3997 3998 private class Slicer extends ElementDefinitionSlicingComponent { 3999 String criteria = ""; 4000 String name = ""; 4001 boolean check; 4002 public Slicer(boolean cantCheck) { 4003 super(); 4004 this.check = cantCheck; 4005 } 4006 } 4007 4008 private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, StructureDefinition structure) { 4009 // given a child in a structure, it's sliced. figure out the slicing xpath 4010 if (child.getPath().endsWith(".extension")) { 4011 ElementDefinition ued = getUrlFor(structure, child); 4012 if ((ued == null || !ued.hasFixed()) && !(child.hasType() && (child.getType().get(0).hasProfile()))) 4013 return new Slicer(false); 4014 else { 4015 Slicer s = new Slicer(true); 4016 String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile().get(0).getValue() : ((UriType) ued.getFixed()).asStringValue(); 4017 s.name = " with URL = '"+url+"'"; 4018 s.criteria = "[@url = '"+url+"']"; 4019 return s; 4020 } 4021 } else 4022 return new Slicer(false); 4023 } 4024 4025 private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed, StructureDefinition structure, StructureDefinition base) throws IOException { 4026 // generateForChild(txt, structure, child); 4027 List<ElementDefinition> children = getChildList(structure, ed); 4028 String sliceName = null; 4029 ElementDefinitionSlicingComponent slicing = null; 4030 for (ElementDefinition child : children) { 4031 String name = tail(child.getPath()); 4032 if (child.hasSlicing()) { 4033 sliceName = name; 4034 slicing = child.getSlicing(); 4035 } else if (!name.equals(sliceName)) 4036 slicing = null; 4037 4038 ElementDefinition based = getByPath(base, child.getPath()); 4039 boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin())); 4040 boolean doMax = child.hasMax() && !child.getMax().equals("*") && (based == null || (!child.getMax().equals(based.getMax()))); 4041 Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure); 4042 if (slicer.check) { 4043 if (doMin || doMax) { 4044 Section s = sch.section(xpath); 4045 Rule r = s.rule(xpath); 4046 if (doMin) 4047 r.assrt("count(f:"+name+slicer.criteria+") >= "+Integer.toString(child.getMin()), name+slicer.name+": minimum cardinality of '"+name+"' is "+Integer.toString(child.getMin())); 4048 if (doMax) 4049 r.assrt("count(f:"+name+slicer.criteria+") <= "+child.getMax(), name+slicer.name+": maximum cardinality of '"+name+"' is "+child.getMax()); 4050 } 4051 } 4052 } 4053/// xpath has been removed 4054// for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) { 4055// if (inv.hasXpath()) { 4056// Section s = sch.section(ed.getPath()); 4057// Rule r = s.rule(xpath); 4058// r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId()+": " : "")+inv.getHuman()+(inv.hasUserData(IS_DERIVED) ? " (inherited)" : "")); 4059// } 4060// } 4061 if (!ed.hasContentReference()) { 4062 for (ElementDefinition child : children) { 4063 String name = tail(child.getPath()); 4064 generateForChildren(sch, xpath+"/f:"+name, child, structure, base); 4065 } 4066 } 4067 } 4068 4069 4070 4071 4072 private ElementDefinition getByPath(StructureDefinition base, String path) { 4073 for (ElementDefinition ed : base.getSnapshot().getElement()) { 4074 if (ed.getPath().equals(path)) 4075 return ed; 4076 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))) 4077 return ed; 4078 } 4079 return null; 4080 } 4081 4082 4083 public void setIds(StructureDefinition sd, boolean checkFirst) throws DefinitionException { 4084 if (!checkFirst || !sd.hasDifferential() || hasMissingIds(sd.getDifferential().getElement())) { 4085 if (!sd.hasDifferential()) 4086 sd.setDifferential(new StructureDefinitionDifferentialComponent()); 4087 generateIds(sd.getDifferential().getElement(), sd.getUrl(), sd.getType(), sd); 4088 } 4089 if (!checkFirst || !sd.hasSnapshot() || hasMissingIds(sd.getSnapshot().getElement())) { 4090 if (!sd.hasSnapshot()) 4091 sd.setSnapshot(new StructureDefinitionSnapshotComponent()); 4092 generateIds(sd.getSnapshot().getElement(), sd.getUrl(), sd.getType(), sd); 4093 } 4094 } 4095 4096 4097 private boolean hasMissingIds(List<ElementDefinition> list) { 4098 for (ElementDefinition ed : list) { 4099 if (!ed.hasId()) 4100 return true; 4101 } 4102 return false; 4103 } 4104 4105 private class SliceList { 4106 4107 private Map<String, String> slices = new HashMap<>(); 4108 4109 public void seeElement(ElementDefinition ed) { 4110 Iterator<Map.Entry<String,String>> iter = slices.entrySet().iterator(); 4111 while (iter.hasNext()) { 4112 Map.Entry<String,String> entry = iter.next(); 4113 if (entry.getKey().length() > ed.getPath().length() || entry.getKey().equals(ed.getPath())) 4114 iter.remove(); 4115 } 4116 4117 if (ed.hasSliceName()) 4118 slices.put(ed.getPath(), ed.getSliceName()); 4119 } 4120 4121 public String[] analyse(List<String> paths) { 4122 String s = paths.get(0); 4123 String[] res = new String[paths.size()]; 4124 res[0] = null; 4125 for (int i = 1; i < paths.size(); i++) { 4126 s = s + "."+paths.get(i); 4127 if (slices.containsKey(s)) 4128 res[i] = slices.get(s); 4129 else 4130 res[i] = null; 4131 } 4132 return res; 4133 } 4134 4135 } 4136 4137 protected void generateIds(List<ElementDefinition> list, String name, String type, StructureDefinition srcSD) throws DefinitionException { 4138 if (list.isEmpty()) 4139 return; 4140 4141 Map<String, String> idList = new HashMap<String, String>(); 4142 Map<String, String> replacedIds = new HashMap<String, String>(); 4143 4144 SliceList sliceInfo = new SliceList(); 4145 // first pass, update the element ids 4146 for (ElementDefinition ed : list) { 4147 List<String> paths = new ArrayList<String>(); 4148 if (!ed.hasPath()) 4149 throw new DefinitionException(context.formatMessage(I18nConstants.NO_PATH_ON_ELEMENT_DEFINITION__IN_, Integer.toString(list.indexOf(ed)), name)); 4150 sliceInfo.seeElement(ed); 4151 String[] pl = ed.getPath().split("\\."); 4152 for (int i = paths.size(); i < pl.length; i++) // -1 because the last path is in focus 4153 paths.add(pl[i]); 4154 String slices[] = sliceInfo.analyse(paths); 4155 4156 StringBuilder b = new StringBuilder(); 4157 b.append(paths.get(0)); 4158 for (int i = 1; i < paths.size(); i++) { 4159 b.append("."); 4160 String s = paths.get(i); 4161 String p = slices[i]; 4162 b.append(fixChars(s)); 4163 if (p != null) { 4164 b.append(":"); 4165 b.append(p); 4166 } 4167 } 4168 String bs = b.toString(); 4169 if (ed.hasId()) { 4170 replacedIds.put(ed.getId(), ed.getPath()); 4171 } 4172 ed.setId(bs); 4173 if (idList.containsKey(bs)) { 4174 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)); 4175 } 4176 idList.put(bs, ed.getPath()); 4177 if (ed.hasContentReference() && ed.getContentReference().startsWith("#")) { 4178 String s = ed.getContentReference(); 4179 String typeURL = getUrlForSource(type, srcSD); 4180 if (replacedIds.containsKey(s.substring(1))) { 4181 ed.setContentReference(typeURL+"#"+replacedIds.get(s.substring(1))); 4182 } else { 4183 ed.setContentReference(typeURL+s); 4184 } 4185 } 4186 } 4187 // second path - fix up any broken path based id references 4188 4189 } 4190 4191 4192 private String getUrlForSource(String type, StructureDefinition srcSD) { 4193 if (srcSD.getKind() == StructureDefinitionKind.LOGICAL) { 4194 return srcSD.getUrl(); 4195 } else { 4196 return "http://hl7.org/fhir/StructureDefinition/"+type; 4197 } 4198 } 4199 4200 private Object fixChars(String s) { 4201 return s.replace("_", "-"); 4202 } 4203 4204 4205// private String describeExtension(ElementDefinition ed) { 4206// if (!ed.hasType() || !ed.getTypeFirstRep().hasProfile()) 4207// return ""; 4208// return "$"+urlTail(ed.getTypeFirstRep().getProfile()); 4209// } 4210// 4211 4212 private static String urlTail(String profile) { 4213 return profile.contains("/") ? profile.substring(profile.lastIndexOf("/")+1) : profile; 4214 } 4215// 4216// 4217// private String checkName(String name) { 4218//// if (name.contains(".")) 4219////// throw new Exception("Illegal name "+name+": no '.'"); 4220//// if (name.contains(" ")) 4221//// throw new Exception("Illegal name "+name+": no spaces"); 4222// StringBuilder b = new StringBuilder(); 4223// for (char c : name.toCharArray()) { 4224// if (!Utilities.existsInList(c, '.', ' ', ':', '"', '\'', '(', ')', '&', '[', ']')) 4225// b.append(c); 4226// } 4227// return b.toString().toLowerCase(); 4228// } 4229// 4230// 4231// private int charCount(String path, char t) { 4232// int res = 0; 4233// for (char ch : path.toCharArray()) { 4234// if (ch == t) 4235// res++; 4236// } 4237// return res; 4238// } 4239 4240// 4241//private void generateForChild(TextStreamWriter txt, 4242// StructureDefinition structure, ElementDefinition child) { 4243// // TODO Auto-generated method stub 4244// 4245//} 4246 4247 private interface ExampleValueAccessor { 4248 DataType getExampleValue(ElementDefinition ed); 4249 String getId(); 4250 } 4251 4252 private class BaseExampleValueAccessor implements ExampleValueAccessor { 4253 @Override 4254 public DataType getExampleValue(ElementDefinition ed) { 4255 if (ed.hasFixed()) 4256 return ed.getFixed(); 4257 if (ed.hasExample()) 4258 return ed.getExample().get(0).getValue(); 4259 else 4260 return null; 4261 } 4262 4263 @Override 4264 public String getId() { 4265 return "-genexample"; 4266 } 4267 } 4268 4269 private class ExtendedExampleValueAccessor implements ExampleValueAccessor { 4270 private String index; 4271 4272 public ExtendedExampleValueAccessor(String index) { 4273 this.index = index; 4274 } 4275 @Override 4276 public DataType getExampleValue(ElementDefinition ed) { 4277 if (ed.hasFixed()) 4278 return ed.getFixed(); 4279 for (Extension ex : ed.getExtension()) { 4280 String ndx = ToolingExtensions.readStringExtension(ex, "index"); 4281 DataType value = ToolingExtensions.getExtension(ex, "exValue").getValue(); 4282 if (index.equals(ndx) && value != null) 4283 return value; 4284 } 4285 return null; 4286 } 4287 @Override 4288 public String getId() { 4289 return "-genexample-"+index; 4290 } 4291 } 4292 4293 public List<org.hl7.fhir.r5.elementmodel.Element> generateExamples(StructureDefinition sd, boolean evenWhenNoExamples) throws FHIRException { 4294 List<org.hl7.fhir.r5.elementmodel.Element> examples = new ArrayList<org.hl7.fhir.r5.elementmodel.Element>(); 4295 if (sd.hasSnapshot()) { 4296 if (evenWhenNoExamples || hasAnyExampleValues(sd)) 4297 examples.add(generateExample(sd, new BaseExampleValueAccessor())); 4298 for (int i = 1; i <= 50; i++) { 4299 if (hasAnyExampleValues(sd, Integer.toString(i))) 4300 examples.add(generateExample(sd, new ExtendedExampleValueAccessor(Integer.toString(i)))); 4301 } 4302 } 4303 return examples; 4304 } 4305 4306 private org.hl7.fhir.r5.elementmodel.Element generateExample(StructureDefinition profile, ExampleValueAccessor accessor) throws FHIRException { 4307 ElementDefinition ed = profile.getSnapshot().getElementFirstRep(); 4308 org.hl7.fhir.r5.elementmodel.Element r = new org.hl7.fhir.r5.elementmodel.Element(ed.getPath(), new Property(context, ed, profile)); 4309 SourcedChildDefinitions children = getChildMap(profile, ed, true); 4310 for (ElementDefinition child : children.getList()) { 4311 if (child.getPath().endsWith(".id")) { 4312 org.hl7.fhir.r5.elementmodel.Element id = new org.hl7.fhir.r5.elementmodel.Element("id", new Property(context, child, profile)); 4313 id.setValue(profile.getId()+accessor.getId()); 4314 r.getChildren().add(id); 4315 } else { 4316 org.hl7.fhir.r5.elementmodel.Element e = createExampleElement(profile, child, accessor); 4317 if (e != null) 4318 r.getChildren().add(e); 4319 } 4320 } 4321 return r; 4322 } 4323 4324 private org.hl7.fhir.r5.elementmodel.Element createExampleElement(StructureDefinition profile, ElementDefinition ed, ExampleValueAccessor accessor) throws FHIRException { 4325 DataType v = accessor.getExampleValue(ed); 4326 if (v != null) { 4327 return new ObjectConverter(context).convert(new Property(context, ed, profile), v); 4328 } else { 4329 org.hl7.fhir.r5.elementmodel.Element res = new org.hl7.fhir.r5.elementmodel.Element(tail(ed.getPath()), new Property(context, ed, profile)); 4330 boolean hasValue = false; 4331 SourcedChildDefinitions children = getChildMap(profile, ed, true); 4332 for (ElementDefinition child : children.getList()) { 4333 if (!child.hasContentReference()) { 4334 org.hl7.fhir.r5.elementmodel.Element e = createExampleElement(profile, child, accessor); 4335 if (e != null) { 4336 hasValue = true; 4337 res.getChildren().add(e); 4338 } 4339 } 4340 } 4341 if (hasValue) 4342 return res; 4343 else 4344 return null; 4345 } 4346 } 4347 4348 private boolean hasAnyExampleValues(StructureDefinition sd, String index) { 4349 for (ElementDefinition ed : sd.getSnapshot().getElement()) 4350 for (Extension ex : ed.getExtension()) { 4351 String ndx = ToolingExtensions.readStringExtension(ex, "index"); 4352 Extension exv = ToolingExtensions.getExtension(ex, "exValue"); 4353 if (exv != null) { 4354 DataType value = exv.getValue(); 4355 if (index.equals(ndx) && value != null) 4356 return true; 4357 } 4358 } 4359 return false; 4360 } 4361 4362 4363 private boolean hasAnyExampleValues(StructureDefinition sd) { 4364 for (ElementDefinition ed : sd.getSnapshot().getElement()) 4365 if (ed.hasExample()) 4366 return true; 4367 return false; 4368 } 4369 4370 4371 public void populateLogicalSnapshot(StructureDefinition sd) throws FHIRException { 4372 sd.getSnapshot().getElement().add(sd.getDifferential().getElementFirstRep().copy()); 4373 4374 if (sd.hasBaseDefinition()) { 4375 StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); 4376 if (base == null) 4377 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_BASE_DEFINITION_FOR_LOGICAL_MODEL__FROM_, sd.getBaseDefinition(), sd.getUrl())); 4378 copyElements(sd, base.getSnapshot().getElement()); 4379 } 4380 copyElements(sd, sd.getDifferential().getElement()); 4381 } 4382 4383 4384 private void copyElements(StructureDefinition sd, List<ElementDefinition> list) { 4385 for (ElementDefinition ed : list) { 4386 if (ed.getPath().contains(".")) { 4387 ElementDefinition n = ed.copy(); 4388 n.setPath(sd.getSnapshot().getElementFirstRep().getPath()+"."+ed.getPath().substring(ed.getPath().indexOf(".")+1)); 4389 sd.getSnapshot().addElement(n); 4390 } 4391 } 4392 } 4393 4394 4395 public void cleanUpDifferential(StructureDefinition sd) { 4396 if (sd.getDifferential().getElement().size() > 1) 4397 cleanUpDifferential(sd, 1); 4398 } 4399 4400 private void cleanUpDifferential(StructureDefinition sd, int start) { 4401 int level = Utilities.charCount(sd.getDifferential().getElement().get(start).getPath(), '.'); 4402 int c = start; 4403 int len = sd.getDifferential().getElement().size(); 4404 HashSet<String> paths = new HashSet<String>(); 4405 while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') == level) { 4406 ElementDefinition ed = sd.getDifferential().getElement().get(c); 4407 if (!paths.contains(ed.getPath())) { 4408 paths.add(ed.getPath()); 4409 int ic = c+1; 4410 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 4411 ic++; 4412 ElementDefinition slicer = null; 4413 List<ElementDefinition> slices = new ArrayList<ElementDefinition>(); 4414 slices.add(ed); 4415 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') == level) { 4416 ElementDefinition edi = sd.getDifferential().getElement().get(ic); 4417 if (ed.getPath().equals(edi.getPath())) { 4418 if (slicer == null) { 4419 slicer = new ElementDefinition(); 4420 slicer.setPath(edi.getPath()); 4421 slicer.getSlicing().setRules(SlicingRules.OPEN); 4422 sd.getDifferential().getElement().add(c, slicer); 4423 c++; 4424 ic++; 4425 } 4426 slices.add(edi); 4427 } 4428 ic++; 4429 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 4430 ic++; 4431 } 4432 // now we're at the end, we're going to figure out the slicing discriminator 4433 if (slicer != null) 4434 determineSlicing(slicer, slices); 4435 } 4436 c++; 4437 if (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) { 4438 cleanUpDifferential(sd, c); 4439 c++; 4440 while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) 4441 c++; 4442 } 4443 } 4444 } 4445 4446 4447 private void determineSlicing(ElementDefinition slicer, List<ElementDefinition> slices) { 4448 // first, name them 4449 int i = 0; 4450 for (ElementDefinition ed : slices) { 4451 if (ed.hasUserData(UserDataNames.SNAPSHOT_slice_name)) { 4452 ed.setSliceName(ed.getUserString(UserDataNames.SNAPSHOT_slice_name)); 4453 } else { 4454 i++; 4455 ed.setSliceName("slice-"+Integer.toString(i)); 4456 } 4457 } 4458 // now, the hard bit, how are they differentiated? 4459 // right now, we hard code this... 4460 if (slicer.getPath().endsWith(".extension") || slicer.getPath().endsWith(".modifierExtension")) 4461 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url"); 4462 else if (slicer.getPath().equals("DiagnosticReport.result")) 4463 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("reference.code"); 4464 else if (slicer.getPath().equals("Observation.related")) 4465 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("target.reference.code"); 4466 else if (slicer.getPath().equals("Bundle.entry")) 4467 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("resource.@profile"); 4468 else 4469 throw new Error("No slicing for "+slicer.getPath()); 4470 } 4471 4472 4473 public static ElementDefinitionSlicingDiscriminatorComponent interpretR2Discriminator(String discriminator, boolean isExists) { 4474 if (discriminator.endsWith("@pattern")) 4475 return makeDiscriminator(DiscriminatorType.PATTERN, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 4476 if (discriminator.endsWith("@profile")) 4477 return makeDiscriminator(DiscriminatorType.PROFILE, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 4478 if (discriminator.endsWith("@type")) 4479 return makeDiscriminator(DiscriminatorType.TYPE, discriminator.length() == 5 ? "" : discriminator.substring(0,discriminator.length()-6)); 4480 if (discriminator.endsWith("@exists")) 4481 return makeDiscriminator(DiscriminatorType.EXISTS, discriminator.length() == 7 ? "" : discriminator.substring(0,discriminator.length()-8)); 4482 if (isExists) 4483 return makeDiscriminator(DiscriminatorType.EXISTS, discriminator); 4484 return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(discriminator); 4485 } 4486 4487 4488 private static ElementDefinitionSlicingDiscriminatorComponent makeDiscriminator(DiscriminatorType dType, String str) { 4489 return new ElementDefinitionSlicingDiscriminatorComponent().setType(dType).setPath(Utilities.noString(str)? "$this" : str); 4490 } 4491 4492 4493 public static String buildR2Discriminator(ElementDefinitionSlicingDiscriminatorComponent t) throws FHIRException { 4494 switch (t.getType()) { 4495 case PROFILE: return t.getPath()+"/@profile"; 4496 case PATTERN: return t.getPath()+"/@pattern"; 4497 case TYPE: return t.getPath()+"/@type"; 4498 case VALUE: return t.getPath(); 4499 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 4500 default: throw new FHIRException("Unable to represent "+t.getType().toCode()+":"+t.getPath()+" in R2"); 4501 } 4502 } 4503 4504 4505 public static StructureDefinition makeExtensionForVersionedURL(IWorkerContext context, String url) { 4506 String epath = url.substring(54); 4507 if (!epath.contains(".")) 4508 return null; 4509 String type = epath.substring(0, epath.indexOf(".")); 4510 StructureDefinition sd = context.fetchTypeDefinition(type); 4511 if (sd == null) 4512 return null; 4513 ElementDefinition ed = null; 4514 for (ElementDefinition t : sd.getSnapshot().getElement()) { 4515 if (t.getPath().equals(epath)) { 4516 ed = t; 4517 break; 4518 } 4519 } 4520 if (ed == null) 4521 return null; 4522 if ("Element".equals(ed.typeSummary()) || "BackboneElement".equals(ed.typeSummary())) { 4523 return null; 4524 } else { 4525 StructureDefinition template = context.fetchResource(StructureDefinition.class, "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities"); 4526 StructureDefinition ext = template.copy(); 4527 ext.setUrl(url); 4528 ext.setId("extension-"+epath); 4529 ext.setName("Extension-"+epath); 4530 ext.setTitle("Extension for r4 "+epath); 4531 ext.setStatus(sd.getStatus()); 4532 ext.setDate(sd.getDate()); 4533 ext.getContact().clear(); 4534 ext.getContact().addAll(sd.getContact()); 4535 ext.setFhirVersion(sd.getFhirVersion()); 4536 ext.setDescription(ed.getDefinition()); 4537 ext.getContext().clear(); 4538 ext.addContext().setType(ExtensionContextType.ELEMENT).setExpression(epath.substring(0, epath.lastIndexOf("."))); 4539 ext.getDifferential().getElement().clear(); 4540 ext.getSnapshot().getElement().get(3).setFixed(new UriType(url)); 4541 ext.getSnapshot().getElement().set(4, ed.copy()); 4542 ext.getSnapshot().getElement().get(4).setPath("Extension.value"+Utilities.capitalize(ed.typeSummary())); 4543 return ext; 4544 } 4545 4546 } 4547 4548 4549 public boolean isThrowException() { 4550 return wantThrowExceptions; 4551 } 4552 4553 4554 public void setThrowException(boolean exception) { 4555 this.wantThrowExceptions = exception; 4556 } 4557 4558 4559 public ValidationOptions getTerminologyServiceOptions() { 4560 return terminologyServiceOptions; 4561 } 4562 4563 4564 public void setTerminologyServiceOptions(ValidationOptions terminologyServiceOptions) { 4565 this.terminologyServiceOptions = terminologyServiceOptions; 4566 } 4567 4568 4569 public boolean isNewSlicingProcessing() { 4570 return newSlicingProcessing; 4571 } 4572 4573 4574 public ProfileUtilities setNewSlicingProcessing(boolean newSlicingProcessing) { 4575 this.newSlicingProcessing = newSlicingProcessing; 4576 return this; 4577 } 4578 4579 4580 public boolean isDebug() { 4581 return debug; 4582 } 4583 4584 4585 public void setDebug(boolean debug) { 4586 this.debug = debug; 4587 } 4588 4589 4590 public String getDefWebRoot() { 4591 return defWebRoot; 4592 } 4593 4594 4595 public void setDefWebRoot(String defWebRoot) { 4596 this.defWebRoot = defWebRoot; 4597 if (!this.defWebRoot.endsWith("/")) 4598 this.defWebRoot = this.defWebRoot + '/'; 4599 } 4600 4601 4602 public static StructureDefinition makeBaseDefinition(FHIRVersion fhirVersion) { 4603 return makeBaseDefinition(fhirVersion.toCode()); 4604 } 4605 public static StructureDefinition makeBaseDefinition(String fhirVersion) { 4606 StructureDefinition base = new StructureDefinition(); 4607 base.setId("Base"); 4608 base.setUrl("http://hl7.org/fhir/StructureDefinition/Base"); 4609 base.setVersion(fhirVersion); 4610 base.setName("Base"); 4611 base.setStatus(PublicationStatus.ACTIVE); 4612 base.setDate(new Date()); 4613 base.setFhirVersion(FHIRVersion.fromCode(fhirVersion)); 4614 base.setKind(StructureDefinitionKind.COMPLEXTYPE); 4615 base.setAbstract(true); 4616 base.setType("Base"); 4617 base.setWebPath("http://build.fhir.org/types.html#Base"); 4618 ElementDefinition e = base.getSnapshot().getElementFirstRep(); 4619 e.setId("Base"); 4620 e.setPath("Base"); 4621 e.setMin(0); 4622 e.setMax("*"); 4623 e.getBase().setPath("Base"); 4624 e.getBase().setMin(0); 4625 e.getBase().setMax("*"); 4626 e.setIsModifier(false); 4627 e = base.getDifferential().getElementFirstRep(); 4628 e.setId("Base"); 4629 e.setPath("Base"); 4630 e.setMin(0); 4631 e.setMax("*"); 4632 return base; 4633 } 4634 4635 public XVerExtensionManager getXver() { 4636 return xver; 4637 } 4638 4639 public ProfileUtilities setXver(XVerExtensionManager xver) { 4640 this.xver = xver; 4641 return this; 4642 } 4643 4644 4645 private List<ElementChoiceGroup> readChoices(ElementDefinition ed, List<ElementDefinition> children) { 4646 List<ElementChoiceGroup> result = new ArrayList<>(); 4647 for (ElementDefinitionConstraintComponent c : ed.getConstraint()) { 4648 ElementChoiceGroup grp = processConstraint(children, c); 4649 if (grp != null) { 4650 result.add(grp); 4651 } 4652 } 4653 return result; 4654 } 4655 4656 public ElementChoiceGroup processConstraint(List<ElementDefinition> children, ElementDefinitionConstraintComponent c) { 4657 if (!c.hasExpression()) { 4658 return null; 4659 } 4660 ExpressionNode expr = null; 4661 try { 4662 expr = fpe.parse(c.getExpression()); 4663 } catch (Exception e) { 4664 return null; 4665 } 4666 if (expr.getKind() != Kind.Group || expr.getOpNext() == null || !(expr.getOperation() == Operation.Equals || expr.getOperation() == Operation.LessOrEqual)) { 4667 return null; 4668 } 4669 ExpressionNode n1 = expr.getGroup(); 4670 ExpressionNode n2 = expr.getOpNext(); 4671 if (n2.getKind() != Kind.Constant || n2.getInner() != null || n2.getOpNext() != null || !"1".equals(n2.getConstant().primitiveValue())) { 4672 return null; 4673 } 4674 ElementChoiceGroup grp = new ElementChoiceGroup(c.getKey(), expr.getOperation() == Operation.Equals); 4675 while (n1 != null) { 4676 if (n1.getKind() != Kind.Name || n1.getInner() != null) { 4677 return null; 4678 } 4679 grp.elements.add(n1.getName()); 4680 if (n1.getOperation() == null || n1.getOperation() == Operation.Union) { 4681 n1 = n1.getOpNext(); 4682 } else { 4683 return null; 4684 } 4685 } 4686 int total = 0; 4687 for (String n : grp.elements) { 4688 boolean found = false; 4689 for (ElementDefinition child : children) { 4690 String name = tail(child.getPath()); 4691 if (n.equals(name)) { 4692 found = true; 4693 if (!"0".equals(child.getMax())) { 4694 total++; 4695 } 4696 } 4697 } 4698 if (!found) { 4699 return null; 4700 } 4701 } 4702 if (total <= 1) { 4703 return null; 4704 } 4705 return grp; 4706 } 4707 4708 public Set<String> getMasterSourceFileNames() { 4709 return masterSourceFileNames; 4710 } 4711 4712 public void setMasterSourceFileNames(Set<String> masterSourceFileNames) { 4713 this.masterSourceFileNames = masterSourceFileNames; 4714 } 4715 4716 4717 public Set<String> getLocalFileNames() { 4718 return localFileNames; 4719 } 4720 4721 public void setLocalFileNames(Set<String> localFileNames) { 4722 this.localFileNames = localFileNames; 4723 } 4724 4725 public ProfileKnowledgeProvider getPkp() { 4726 return pkp; 4727 } 4728 4729 4730 public static final String UD_ERROR_STATUS = "error-status"; 4731 public static final int STATUS_OK = 0; 4732 public static final int STATUS_HINT = 1; 4733 public static final int STATUS_WARNING = 2; 4734 public static final int STATUS_ERROR = 3; 4735 public static final int STATUS_FATAL = 4; 4736 private static final String ROW_COLOR_ERROR = "#ffcccc"; 4737 private static final String ROW_COLOR_FATAL = "#ff9999"; 4738 private static final String ROW_COLOR_WARNING = "#ffebcc"; 4739 private static final String ROW_COLOR_HINT = "#ebf5ff"; 4740 private static final String ROW_COLOR_NOT_MUST_SUPPORT = "#d6eaf8"; 4741 4742 public String getRowColor(ElementDefinition element, boolean isConstraintMode) { 4743 switch (element.getUserInt(UD_ERROR_STATUS)) { 4744 case STATUS_HINT: return ROW_COLOR_HINT; 4745 case STATUS_WARNING: return ROW_COLOR_WARNING; 4746 case STATUS_ERROR: return ROW_COLOR_ERROR; 4747 case STATUS_FATAL: return ROW_COLOR_FATAL; 4748 } 4749 if (isConstraintMode && !element.getMustSupport() && !element.getIsModifier() && element.getPath().contains(".")) 4750 return null; // ROW_COLOR_NOT_MUST_SUPPORT; 4751 else 4752 return null; 4753 } 4754 4755 public static boolean isExtensionDefinition(StructureDefinition sd) { 4756 return sd.getDerivation() == TypeDerivationRule.CONSTRAINT && sd.getType().equals("Extension"); 4757 } 4758 4759 public AllowUnknownProfile getAllowUnknownProfile() { 4760 return allowUnknownProfile; 4761 } 4762 4763 public void setAllowUnknownProfile(AllowUnknownProfile allowUnknownProfile) { 4764 this.allowUnknownProfile = allowUnknownProfile; 4765 } 4766 4767 public static boolean isSimpleExtension(StructureDefinition sd) { 4768 if (!isExtensionDefinition(sd)) { 4769 return false; 4770 } 4771 ElementDefinition value = sd.getSnapshot().getElementByPath("Extension.value"); 4772 return value != null && !value.isProhibited(); 4773 } 4774 4775 public static boolean isComplexExtension(StructureDefinition sd) { 4776 if (!isExtensionDefinition(sd)) { 4777 return false; 4778 } 4779 ElementDefinition value = sd.getSnapshot().getElementByPath("Extension.value"); 4780 return value == null || value.isProhibited(); 4781 } 4782 4783 public static boolean isModifierExtension(StructureDefinition sd) { 4784 ElementDefinition defn = sd.getSnapshot().getElementByPath("Extension"); 4785 return defn != null && defn.getIsModifier(); 4786 } 4787 4788 public boolean isForPublication() { 4789 return forPublication; 4790 } 4791 4792 public void setForPublication(boolean forPublication) { 4793 this.forPublication = forPublication; 4794 } 4795 4796 public List<ValidationMessage> getMessages() { 4797 return messages; 4798 } 4799 4800 public static boolean isResourceBoundary(ElementDefinition ed) { 4801 return ed.getType().size() == 1 && "Resource".equals(ed.getTypeFirstRep().getCode()); 4802 } 4803 4804 public static boolean isSuppressIgnorableExceptions() { 4805 return suppressIgnorableExceptions; 4806 } 4807 4808 public static void setSuppressIgnorableExceptions(boolean suppressIgnorableExceptions) { 4809 ProfileUtilities.suppressIgnorableExceptions = suppressIgnorableExceptions; 4810 } 4811 4812 public void setMessages(List<ValidationMessage> messages) { 4813 if (messages != null) { 4814 this.messages = messages; 4815 wantThrowExceptions = false; 4816 } 4817 } 4818 4819 private Map<String, List<Property>> propertyCache = new HashMap<>(); 4820 4821 public Map<String, List<Property>> getCachedPropertyList() { 4822 return propertyCache; 4823 } 4824 4825 public void checkExtensions(ElementDefinition outcome) { 4826 outcome.getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), ProfileUtilities.NON_INHERITED_ED_URLS)); 4827 if (outcome.hasBinding()) { 4828 outcome.getBinding().getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), ProfileUtilities.NON_INHERITED_ED_URLS)); 4829 } 4830 4831 } 4832 4833 public static void markExtensions(ElementDefinition ed, boolean overrideSource, StructureDefinition src) { 4834 for (Extension ex : ed.getExtension()) { 4835 markExtensionSource(ex, overrideSource, src); 4836 } 4837 for (Extension ex : ed.getBinding().getExtension()) { 4838 markExtensionSource(ex, overrideSource, src); 4839 } 4840 for (TypeRefComponent t : ed.getType()) { 4841 for (Extension ex : t.getExtension()) { 4842 markExtensionSource(ex, overrideSource, src); 4843 } 4844 } 4845 } 4846 4847 public static boolean hasObligations(StructureDefinition sd) { 4848 if (sd.hasExtension(ToolingExtensions.EXT_OBLIGATION_CORE)) { 4849 return true; 4850 } 4851 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 4852 if (ed.hasExtension(ToolingExtensions.EXT_OBLIGATION_CORE)) { 4853 return true; 4854 } 4855 for (TypeRefComponent tr : ed.getType()) { 4856 if (tr.hasExtension(ToolingExtensions.EXT_OBLIGATION_CORE)) { 4857 return true; 4858 } 4859 } 4860 } 4861 return false; 4862 } 4863 4864 public List<String> getSuppressedMappings() { 4865 return suppressedMappings; 4866 } 4867 4868 public void setSuppressedMappings(List<String> suppressedMappings) { 4869 this.suppressedMappings = suppressedMappings; 4870 } 4871 4872 public static String getCSUrl(StructureDefinition profile) { 4873 if (profile.hasExtension(ToolingExtensions.EXT_SD_CS_URL)) { 4874 return ToolingExtensions.readStringExtension(profile, ToolingExtensions.EXT_SD_CS_URL); 4875 } else { 4876 return profile.getUrl()+"?codesystem"; 4877 } 4878 } 4879 4880 public static String getUrlFromCSUrl(String url) { 4881 if (url == null) { 4882 return null; 4883 } 4884 if (url.endsWith("?codesystem")) { 4885 return url.replace("?codesystem", ""); 4886 } else { 4887 return null; 4888 } 4889 } 4890 4891 public FHIRPathEngine getFpe() { 4892 return fpe; 4893 } 4894 4895}