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