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