
001package org.hl7.fhir.r4.profilemodel; 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 031import java.util.ArrayList; 032import java.util.HashSet; 033import java.util.List; 034import java.util.Set; 035 036import org.apache.commons.lang3.NotImplementedException; 037import org.hl7.fhir.exceptions.DefinitionException; 038import org.hl7.fhir.exceptions.FHIRException; 039import org.hl7.fhir.r4.conformance.ProfileUtilities; 040import org.hl7.fhir.r4.context.ContextUtilities; 041import org.hl7.fhir.r4.context.IWorkerContext; 042import org.hl7.fhir.r4.fhirpath.FHIRPathEngine; 043import org.hl7.fhir.r4.model.Base; 044import org.hl7.fhir.r4.model.CanonicalType; 045import org.hl7.fhir.r4.model.ElementDefinition; 046import org.hl7.fhir.r4.model.ElementDefinition.DiscriminatorType; 047import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionSlicingComponent; 048import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent; 049import org.hl7.fhir.r4.model.ElementDefinition.SlicingRules; 050import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; 051import org.hl7.fhir.r4.model.Enumerations.BindingStrength; 052import org.hl7.fhir.r4.model.Resource; 053import org.hl7.fhir.r4.model.ResourceFactory; 054import org.hl7.fhir.r4.model.StructureDefinition; 055import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule; 056import org.hl7.fhir.r4.model.ValueSet; 057import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent; 058import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome; 059import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 060import org.hl7.fhir.utilities.Utilities; 061 062/** 063 * Factory class for the ProfiledElement sub-system 064 * 065 * *** NOTE: This sub-system is still under development *** 066 * 067 * This subsystem takes a profile and creates a view of the profile that stitches 068 * all the parts together, and presents it as a seamless tree. There's two views: 069 * 070 * - definition: A logical view of the contents of the profile 071 * - instance: a logical view of a resource that conforms to the profile 072 * 073 * The tree of elements in the profile model is different to the the base resource: 074 * - some elements are removed (max = 0) 075 * - extensions are turned into named elements 076 * - slices are turned into named elements 077 * - element properties - doco, cardinality, binding etc is updated for what the profile says 078 * 079 * Definition 080 * ---------- 081 * This presents a single view of the contents of a resource as specified by 082 * the profile. It's suitable for use in any kind of tree view. 083 * 084 * Each node has a unique name amongst it's siblings, but this name may not be 085 * the name in the instance, since slicing splits up a single named element into 086 * different definitions. 087 * 088 * Each node has: 089 * - name (unique amongst siblings) 090 * - schema name (the actual name in the instance) 091 * - min cardinality 092 * - max cardinality 093 * - short documentation (for the tree view) 094 * - full documentation (markdown source) 095 * - profile definition - the full definition in the profile 096 * - base definition - the full definition at the resource level 097 * - types() - a list of possible types 098 * - children(type) - a list of child nodes for the provided type 099 * - expansion - if there's a binding, the codes in the expansion based on the binding 100 * 101 * Note that the tree may not have leaves; the trees recurse indefinitely because 102 * extensions have extensions etc. So you can't do a depth-first search of the tree 103 * without some kind of decision to stop at a given point. 104 * 105 * Instance 106 * -------- 107 * 108 * todo 109 * 110 * @author grahamegrieve 111 * 112 */ 113public class PEBuilder { 114 115 public enum PEElementPropertiesPolicy { 116 NONE, EXTENSION, EXTENSION_ID 117 } 118 119 private IWorkerContext context; 120 private ProfileUtilities pu; 121 private ContextUtilities cu; 122 private PEElementPropertiesPolicy elementProps; 123 private boolean fixedPropsDefault; 124 private FHIRPathEngine fpe; 125 126 /** 127 * @param context - must be loaded with R5 definitions 128 * @param elementProps - whether to include Element.id and Element.extension in the tree. Recommended choice: Extension 129 */ 130 public PEBuilder(IWorkerContext context, PEElementPropertiesPolicy elementProps, boolean fixedPropsDefault) { 131 super(); 132 this.context = context; 133 this.elementProps = elementProps; 134 this.fixedPropsDefault = fixedPropsDefault; 135 pu = new ProfileUtilities(context, null, null); 136 cu = new ContextUtilities(context); 137 fpe = new FHIRPathEngine(context, pu); 138 } 139 140 /** 141 * Given a profile, return a tree of the elements defined in the profile model. This builds the profile model 142 * for the provided version of the nominated profile 143 * 144 * The tree of elements in the profile model is different to those defined in the base resource: 145 * - some elements are removed (max = 0) 146 * - extensions are turned into named elements 147 * - slices are turned into named elements 148 * - element properties - doco, cardinality, binding etc is updated for what the profile says 149 * 150 * Warning: profiles and resources are recursive; you can't iterate this tree until it you get 151 * to the leaves because there are nodes that don't terminate (extensions have extensions) 152 * 153 */ 154 public PEDefinition buildPEDefinition(StructureDefinition profile) { 155 if (!profile.hasSnapshot()) { 156 throw new DefinitionException("Profile '"+profile.getVersionedUrl()+"' does not have a snapshot"); 157 } 158 return new PEDefinitionResource(this, profile, null); 159 } 160 161 /** 162 * Given a profile, return a tree of the elements defined in the profile model. This builds the profile model 163 * for the latest version of the nominated profile 164 * 165 * The tree of elements in the profile model is different to those defined in the base resource: 166 * - some elements are removed (max = 0) 167 * - extensions are turned into named elements 168 * - slices are turned into named elements 169 * - element properties - doco, cardinality, binding etc is updated for what the profile says 170 * 171 * Warning: profiles and resources are recursive; you can't iterate this tree until it you get 172 * to the leaves because there are nodes that don't terminate (extensions have extensions) 173 * 174 */ 175 public PEDefinition buildPEDefinition(String url) { 176 StructureDefinition profile = getProfile(url); 177 if (profile == null) { 178 throw new DefinitionException("Unable to find profile for URL '"+url+"'"); 179 } 180 if (!profile.hasSnapshot()) { 181 throw new DefinitionException("Profile '"+url+"' does not have a snapshot"); 182 } 183 return new PEDefinitionResource(this, profile, profile.getName()); 184 } 185 186 /** 187 * Given a profile, return a tree of the elements defined in the profile model. This builds the profile model 188 * for the nominated version of the nominated profile 189 * 190 * The tree of elements in the profile model is different to the the base resource: 191 * - some elements are removed (max = 0) 192 * - extensions are turned into named elements 193 * - slices are turned into named elements 194 * - element properties - doco, cardinality, binding etc is updated for what the profile says 195 * 196 * Warning: profiles and resources can be recursive; you can't iterate this tree until it you get 197 * to the leaves because you will never get to a child that doesn't have children 198 * 199 */ 200 public PEDefinition buildPEDefinition(String url, String version) { 201 StructureDefinition profile = getProfile(url, version); 202 if (profile == null) { 203 throw new DefinitionException("Unable to find profile for URL '"+url+"'"); 204 } 205 if (!profile.hasSnapshot()) { 206 throw new DefinitionException("Profile '"+url+"' does not have a snapshot"); 207 } 208 return new PEDefinitionResource(this, profile, profile.getName()); 209 } 210 211 /** 212 * Given a resource and a profile, return a tree of instance data as defined by the profile model 213 * using the latest version of the profile 214 * 215 * The tree is a facade to the underlying resource - all actual data is stored against the resource, 216 * and retrieved on the fly from the resource, so that applications can work at either level, as 217 * convenient. 218 * 219 * Note that there's a risk that deleting something through the resource while holding 220 * a handle to a PEInstance that is a facade on what is deleted leaves an orphan facade 221 * that will continue to function, but is making changes to resource content that is no 222 * longer part of the resource 223 * 224 */ 225 public PEInstance buildPEInstance(String url, Resource resource) { 226 PEDefinition defn = buildPEDefinition(url); 227 return loadInstance(defn, resource); 228 } 229 230 /** 231 * Given a resource and a profile, return a tree of instance data as defined by the profile model 232 * using the provided version of the profile 233 * 234 * The tree is a facade to the underlying resource - all actual data is stored against the resource, 235 * and retrieved on the fly from the resource, so that applications can work at either level, as 236 * convenient. 237 * 238 * Note that there's a risk that deleting something through the resource while holding 239 * a handle to a PEInstance that is a facade on what is deleted leaves an orphan facade 240 * that will continue to function, but is making changes to resource content that is no 241 * longer part of the resource 242 * 243 */ 244 public PEInstance buildPEInstance(StructureDefinition profile, Resource resource) { 245 PEDefinition defn = buildPEDefinition(profile); 246 return loadInstance(defn, resource); 247 } 248 249 /** 250 * Given a resource and a profile, return a tree of instance data as defined by the profile model 251 * using the nominated version of the profile 252 * 253 * The tree is a facade to the underlying resource - all actual data is stored against the resource, 254 * and retrieved on the fly from the resource, so that applications can work at either level, as 255 * convenient. 256 * 257 * Note that there's a risk that deleting something through the resource while holding 258 * a handle to a PEInstance that is a facade on what is deleted leaves an orphan facade 259 * that will continue to function, but is making changes to resource content that is no 260 * longer part of the resource 261 */ 262 public PEInstance buildPEInstance(String url, String version, Resource resource) { 263 PEDefinition defn = buildPEDefinition(url, version); 264 return loadInstance(defn, resource); 265 } 266 267 /** 268 * For the current version of a profile, construct a resource and fill out any fixed or required elements 269 * 270 * Note that fixed values are filled out irrespective of the value of fixedProps when the builder is created 271 * 272 * @param url identifies the profile 273 * @param version identifies the version of the profile 274 * @param meta whether to mark the profile in Resource.meta.profile 275 * @return constructed resource 276 */ 277 public Resource createResource(String url, String version, boolean meta) { 278 PEDefinition definition = buildPEDefinition(url, version); 279 Resource res = ResourceFactory.createResource(definition.types().get(0).getType()); 280 populateByProfile(res, definition); 281 if (meta) { 282 res.getMeta().addProfile(definition.profile.getUrl()); 283 } 284 return res; 285 } 286 287 /** 288 * For the provided version of a profile, construct a resource and fill out any fixed or required elements 289 * 290 * Note that fixed values are filled out irrespective of the value of fixedProps when the builder is created 291 * 292 * @param profile the profile 293 * @param meta whether to mark the profile in Resource.meta.profile 294 * @return constructed resource 295 */ 296 public Resource createResource(StructureDefinition profile, boolean meta) { 297 PEDefinition definition = buildPEDefinition(profile); 298 Resource res = ResourceFactory.createResource(definition.types().get(0).getType()); 299 populateByProfile(res, definition); 300 if (meta) { 301 res.getMeta().addProfile(definition.profile.getUrl()); 302 } 303 return res; 304 } 305 306 /** 307 * For the current version of a profile, construct a resource and fill out any fixed or required elements 308 * 309 * Note that fixed values are filled out irrespective of the value of fixedProps when the builder is created 310 * 311 * @param url identifies the profile 312 * @param meta whether to mark the profile in Resource.meta.profile 313 * @return constructed resource 314 */ 315 public Resource createResource(String url, boolean meta) { 316 PEDefinition definition = buildPEDefinition(url); 317 Resource res = ResourceFactory.createResource(definition.types().get(0).getType()); 318 populateByProfile(res, definition); 319 if (meta) { 320 res.getMeta().addProfile(definition.profile.getUrl()); 321 } 322 return res; 323 } 324 325 326 327 // -- methods below here are only used internally to the package 328 329 private StructureDefinition getProfile(String url) { 330 return context.fetchResource(StructureDefinition.class, url); 331 } 332 333 334 private StructureDefinition getProfile(String url, String version) { 335 return context.fetchResource(StructureDefinition.class, url, version); 336 } 337// 338// protected List<PEDefinition> listChildren(boolean allFixed, StructureDefinition profileStructure, ElementDefinition definition, TypeRefComponent t, CanonicalType u) { 339// // TODO Auto-generated method stub 340// return null; 341// } 342 343 protected List<PEDefinition> listChildren(boolean allFixed, PEDefinition parent, StructureDefinition profileStructure, ElementDefinition definition, String url, String... omitList) { 344 StructureDefinition profile = profileStructure; 345 boolean inExtension = profile.getDerivation() == TypeDerivationRule.CONSTRAINT && "Extension".equals(profile.getType()); 346 List<ElementDefinition> list = pu.getChildList(profile, definition); 347 if (definition.getType().size() == 1 || (!definition.getPath().contains(".")) || list.isEmpty()) { 348 assert url == null || checkType(definition, url); 349 List<PEDefinition> res = new ArrayList<>(); 350 if (list.size() == 0) { 351 profile = context.fetchResource(StructureDefinition.class, url); 352 if (profile == null) { 353 throw new FHIRException("Unable to resolve profile "+url); 354 } 355 list = pu.getChildList(profile, profile.getSnapshot().getElementFirstRep()); 356 } 357 if (list.size() > 0) { 358 Set<String> names = new HashSet<>(); 359 int i = 0; 360 while (i < list.size()) { 361 ElementDefinition defn = list.get(i); 362 if (!defn.getMax().equals("0") && (allFixed || include(defn))) { 363 if (passElementPropsCheck(defn, inExtension) && !Utilities.existsInList(defn.getName(), omitList)) { 364 String name = uniquefy(names, defn.getName()); 365 PEDefinitionElement pe = new PEDefinitionElement(this, name, profile, defn, parent.path()); 366 pe.setRecursing(definition == defn || (profile.getDerivation() == TypeDerivationRule.SPECIALIZATION && profile.getType().equals("Extension"))); 367 if (context.isPrimitiveType(definition.getTypeFirstRep().getWorkingCode()) && "value".equals(pe.name())) { 368 pe.setMustHaveValue(definition.getMustHaveValue()); 369 } 370 pe.setInFixedValue(definition.hasFixed() || definition.hasPattern() || parent.isInFixedValue()); 371 if (defn.hasSlicing()) { 372 if (defn.getSlicing().getRules() != SlicingRules.CLOSED) { 373 res.add(pe); 374 pe.setSlicer(true); 375 } 376 i++; 377 while (i < list.size() && list.get(i).getPath().equals(defn.getPath())) { 378 StructureDefinition ext = getExtensionDefinition(list.get(i)); 379 if (ext != null) { 380 res.add(new PEDefinitionExtension(this, uniquefy(names, list.get(i).getSliceName()), profile, list.get(i), defn, ext, parent.path())); 381 } else if (isTypeSlicing(defn)) { 382 res.add(new PEDefinitionTypeSlice(this, uniquefy(names, list.get(i).getSliceName()), profile, list.get(i), defn, parent.path())); 383 } else { 384 if (ProfileUtilities.isComplexExtension(profile) && defn.getPath().endsWith(".extension")) { 385 res.add(new PEDefinitionSubExtension(this, profile, list.get(i), parent.path())); 386 } else { 387 res.add(new PEDefinitionSlice(this, uniquefy(names, list.get(i).getSliceName()), profile, list.get(i), defn, parent.path())); 388 } 389 } 390 i++; 391 } 392 } else { 393 res.add(pe); 394 i++; 395 } 396 } else { 397 i++; 398 } 399 } else { 400 i++; 401 } 402 } 403 } 404 return res; 405 } else if (list.isEmpty()) { 406 throw new DefinitionException("not done yet!"); 407 } else { 408 throw new DefinitionException("not done yet"); 409 } 410 } 411 412 private String uniquefy(Set<String> names, String name) { 413 if (names.contains(name)) { 414 int i = 0; 415 while (names.contains(name+i)) { 416 i++; 417 } 418 name = name+i; 419 } 420 names.add(name); 421 return name; 422 } 423 424 protected PEDefinition makeChild(PEDefinition parent, StructureDefinition profileStructure, ElementDefinition definition) { 425 PEDefinitionElement pe = new PEDefinitionElement(this, profileStructure, definition, parent.path()); 426 if (context.isPrimitiveType(definition.getTypeFirstRep().getWorkingCode()) && "value".equals(pe.name())) { 427 pe.setMustHaveValue(definition.getMustHaveValue()); 428 } 429 pe.setInFixedValue(definition.hasFixed() || definition.hasPattern() || parent.isInFixedValue()); 430 return pe; 431 } 432 433 private boolean passElementPropsCheck(ElementDefinition bdefn, boolean inExtension) { 434 if (inExtension) { 435 return !Utilities.existsInList(bdefn.getBase().getPath(), "Element.id"); 436 } 437 switch (elementProps) { 438 case EXTENSION: 439 return !Utilities.existsInList(bdefn.getBase().getPath(), "Element.id"); 440 case NONE: 441 return !Utilities.existsInList(bdefn.getBase().getPath(), "Element.id", "Element.extension"); 442 case EXTENSION_ID: 443 default: 444 return true; 445 } 446 } 447 448 private boolean isTypeSlicing(ElementDefinition defn) { 449 ElementDefinitionSlicingComponent sl = defn.getSlicing(); 450 return sl.getRules() == SlicingRules.CLOSED && sl.getDiscriminator().size() == 1 && 451 sl.getDiscriminatorFirstRep().getType() == DiscriminatorType.TYPE && "$this".equals(sl.getDiscriminatorFirstRep().getPath()); 452 } 453 454 private boolean include(ElementDefinition defn) { 455 if (fixedPropsDefault) { 456 return true; 457 } else { 458 return !(defn.hasFixed() || defn.hasPattern()); 459 } 460 } 461 462 protected List<PEDefinition> listSlices(StructureDefinition profileStructure, ElementDefinition definition, PEDefinition parent) { 463 List<ElementDefinition> list = pu.getSliceList(profileStructure, definition); 464 List<PEDefinition> res = new ArrayList<>(); 465 for (ElementDefinition ed : list) { 466 if (profileStructure.getDerivation() == TypeDerivationRule.CONSTRAINT && profileStructure.getType().equals("Extension")) { 467 res.add(new PEDefinitionSubExtension(this, profileStructure, ed, parent.path())); 468 } else { 469 PEDefinitionElement pe = new PEDefinitionElement(this, profileStructure, ed, parent.path()); 470 pe.setRecursing(definition == ed || (profileStructure.getDerivation() == TypeDerivationRule.SPECIALIZATION && profileStructure.getType().equals("Extension"))); 471 res.add(pe); 472 } 473 } 474 return res; 475 } 476 477 478 private boolean checkType(ElementDefinition defn, String url) { 479 for (TypeRefComponent t : defn.getType()) { 480 if (("http://hl7.org/fhir/StructureDefinition/"+t.getWorkingCode()).equals(url)) { 481 return true; 482 } 483 for (CanonicalType u : t.getProfile()) { 484 if (url.equals(u.getValue())) { 485 return true; 486 } 487 } 488 } 489 return !defn.getPath().contains("."); 490 } 491 492 493 private StructureDefinition getExtensionDefinition(ElementDefinition ed) { 494 if ("Extension".equals(ed.getTypeFirstRep().getWorkingCode()) && ed.getTypeFirstRep().getProfile().size() == 1) { 495 return context.fetchResource(StructureDefinition.class, ed.getTypeFirstRep().getProfile().get(0).asStringValue()); 496 } else { 497 return null; 498 } 499 } 500 501 502 private ElementDefinition getByName(List<ElementDefinition> blist, String name) { 503 for (ElementDefinition ed : blist) { 504 if (name.equals(ed.getName())) { 505 return ed; 506 } 507 } 508 return null; 509 } 510 511 512 protected PEType makeType(TypeRefComponent t) { 513 if (t.hasProfile()) { 514 StructureDefinition sd = context.fetchResource(StructureDefinition.class, t.getProfile().get(0).getValue()); 515 if (sd == null) { 516 return new PEType(tail(t.getProfile().get(0).getValue()), t.getWorkingCode(), t.getProfile().get(0).getValue()); 517 } else { 518 return new PEType(sd.getName(), t.getWorkingCode(), t.getProfile().get(0).getValue()); 519 } 520 } else { 521 return makeType(t.getWorkingCode()); 522 } 523 } 524 525 protected PEType makeType(TypeRefComponent t, CanonicalType u) { 526 StructureDefinition sd = context.fetchResource(StructureDefinition.class, u.getValue()); 527 if (sd == null) { 528 return new PEType(tail(u.getValue()), t.getWorkingCode(), u.getValue()); 529 } else { 530 return new PEType(sd.getName(), t.getWorkingCode(), u.getValue()); 531 } 532 } 533 534 535 protected PEType makeType(String tn, String url) { 536 return new PEType(tn, tn, url); 537 } 538 539 protected PEType makeType(String tn) { 540 return new PEType(tn, tn, "http://hl7.org/fhir/StructureDefinition/"+ tn); 541 } 542 543 private String tail(String value) { 544 return value.contains("/") ? value.substring(value.lastIndexOf("/")+1) : value; 545 } 546 547 protected List<ElementDefinition> getChildren(StructureDefinition profileStructure, ElementDefinition definition) { 548 return pu.getChildList(profileStructure, definition); 549 } 550 551 private PEInstance loadInstance(PEDefinition defn, Resource resource) { 552 return new PEInstance(this, defn, resource, resource, defn.name()); 553 } 554 555 public IWorkerContext getContext() { 556 return context; 557 } 558 559 protected void populateByProfile(Base base, PEDefinition definition) { 560 if (definition.types().size() == 1) { 561 for (PEDefinition pe : definition.directChildren(true)) { 562 if (pe.hasFixedValue()) { 563 if (pe.definition().hasPattern()) { 564 base.setProperty(pe.schemaName(), pe.definition().getPattern()); 565 } else { 566 base.setProperty(pe.schemaName(), pe.definition().getFixed()); 567 } 568 } else if (!pe.isSlicer() && pe.max() == 1) { 569 for (int i = 0; i < pe.min(); i++) { 570 Base b = null; 571 if (pe.schemaName().endsWith("[x]")) { 572 if (pe.types().size() == 1) { 573 b = base.addChild(pe.schemaName().replace("[x]", Utilities.capitalize(pe.types().get(0).getType()))); 574 } 575 } else if (!pe.isBaseList()) { 576 b = base.makeProperty(pe.schemaName().hashCode(), pe.schemaName()); 577 } else { 578 b = base.addChild(pe.schemaName()); 579 } 580 if (b != null) { 581 populateByProfile(b, pe); 582 } 583 } 584 } 585 } 586 } 587 } 588 589 public String makeSliceExpression(StructureDefinition profile, ElementDefinitionSlicingComponent slicing, ElementDefinition definition) { 590 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(" and "); 591 for (ElementDefinitionSlicingDiscriminatorComponent d : slicing.getDiscriminator()) { 592 switch (d.getType()) { 593 case EXISTS: 594 throw new DefinitionException("The discriminator type 'exists' is not supported by the PEBuilder"); 595 case PATTERN: 596 throw new DefinitionException("The discriminator type 'pattern' is not supported by the PEBuilder"); 597 case PROFILE: 598 throw new DefinitionException("The discriminator type 'profile' is not supported by the PEBuilder"); 599 case TYPE: 600 throw new DefinitionException("The discriminator type 'type' is not supported by the PEBuilder"); 601 case VALUE: 602 String path = d.getPath(); 603 ElementDefinition ed = getChildElement(profile, definition, path); 604 if (ed == null) { 605 throw new DefinitionException("The discriminator path '"+path+"' could not be resolved by the PEBuilder"); 606 } 607 // three things possible: 608 // fixed, pattern, or binding 609 if (ed.hasFixed()) { 610 if (!ed.getFixed().isPrimitive()) { 611 throw new DefinitionException("The discriminator path '"+path+"' has a fixed value that is not a primitive ("+ed.getFixed().fhirType()+") - this is not supported by the PEBuilder"); 612 } 613 b.append(path+" = '"+ed.getFixed().primitiveValue()+"'"); 614 } else if (ed.hasPattern()) { 615 throw new DefinitionException("The discriminator path '"+path+"' has a pattern on the element '"+ed.getId()+"' - this is not supported by the PEBuilder"); 616 } else if (ed.hasBinding()) { 617 if (ed.getBinding().getStrength() != BindingStrength.REQUIRED) { 618 throw new DefinitionException("The discriminator path '"+path+"' has a binding on the element '"+ed.getId()+"' but the strength is not required - this is not supported by the PEBuilder"); 619 } else { 620 ValueSet vs = context.fetchResource(ValueSet.class, ed.getBinding().getValueSet()); 621 if (vs == null) { 622 throw new DefinitionException("The discriminator path '"+path+"' has a binding on the element '"+ed.getId()+"' but the valueSet '"+ed.getBinding().getValueSet()+"' is not known - this is not supported by the PEBuilder"); 623 } 624 ValueSetExpansionOutcome exp = context.expandVS(vs, true, false); 625 if (exp.isOk()) { 626 CommaSeparatedStringBuilder bs = new CommaSeparatedStringBuilder(" | "); 627 for (ValueSetExpansionContainsComponent cc : exp.getValueset().getExpansion().getContains()) { 628 bs.append("'"+cc.getCode()+"'"); 629 } 630 b.append(path+" in ("+bs.toString()+")"); 631 } else { 632 throw new DefinitionException("The discriminator path '"+path+"' has a binding on the element '"+ed.getId()+"' but the valueSet '"+ed.getBinding().getValueSet()+"' could not be expanded: "+exp.getError()); 633 } 634 } 635 } else { 636 throw new DefinitionException("The discriminator path '"+path+"' has no fixed or pattern value or a binding on the element '"+ed.getId()+"' - this is not supported by the PEBuilder"); 637 638 } 639 break; 640 case NULL: 641 throw new DefinitionException("The discriminator type 'null' is not supported by the PEBuilder"); 642 default: 643 throw new DefinitionException("The discriminator type '??' is not supported by the PEBuilder"); 644 } 645 } 646 return b.toString(); 647 } 648 649 private ElementDefinition getChildElement(StructureDefinition profile, ElementDefinition definition, String path) { 650 String head = path.contains(".") ? path.substring(0, path.indexOf(".")) : path; 651 String tail = path.contains(".") ? path.substring(path.indexOf(".")+1) : null; 652 ElementDefinition focus = definition; 653 654 do { 655 List<ElementDefinition> elements = pu.getChildList(profile, focus); 656 if (elements.size() == 0) { 657 profile = focus.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, focus.getTypeFirstRep().getProfile().get(0).asStringValue()) : 658 context.fetchTypeDefinition(focus.getTypeFirstRep().getWorkingCode()); 659 elements = pu.getChildList(profile, profile.getSnapshot().getElementFirstRep()); 660 } 661 focus = getByName(elements, head); 662 if (tail != null) { 663 head = tail.contains(".") ? tail.substring(0, tail.indexOf(".")) : tail; 664 tail = tail.contains(".") ? tail.substring(tail.indexOf(".")+1) : null; 665 } else { 666 head = null; 667 } 668 } while (head != null && focus != null); 669 return focus; 670 } 671 672 public List<Base> exec(Resource resource, Base data, String fhirpath) { 673 return fpe.evaluate(this, resource, resource, data, fhirpath); 674 } 675 676 public boolean isResource(String name) { 677 return cu.isResource(name); 678 } 679}