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              PEDefinitionElement pe = new PEDefinitionElement(this, profile, defn, parent.path());
350              pe.setRecursing(definition == defn || (profile.getDerivation() == TypeDerivationRule.SPECIALIZATION && profile.getType().equals("Extension")));
351              if (context.isPrimitiveType(definition.getTypeFirstRep().getWorkingCode()) && "value".equals(pe.name())) {
352                pe.setMustHaveValue(definition.getMustHaveValue());
353              }
354              pe.setInFixedValue(definition.hasFixed() || definition.hasPattern() || parent.isInFixedValue());
355              if (defn.hasSlicing()) {
356                if (defn.getSlicing().getRules() != SlicingRules.CLOSED) {
357                  res.add(pe);
358                  pe.setSlicer(true);
359                }
360                i++;
361                while (i < list.size() && list.get(i).getPath().equals(defn.getPath())) {
362                  StructureDefinition ext = getExtensionDefinition(list.get(i));
363                  if (ext != null) {
364                    res.add(new PEDefinitionExtension(this, list.get(i).getSliceName(), profile, list.get(i), defn, ext, parent.path()));
365                  } else if (isTypeSlicing(defn)) {
366                    res.add(new PEDefinitionTypeSlice(this, list.get(i).getSliceName(), profile, list.get(i), defn, parent.path()));
367                  } else {
368                    if (ProfileUtilities.isComplexExtension(profile) && defn.getPath().endsWith(".extension")) {
369                      res.add(new PEDefinitionSubExtension(this, profile, list.get(i), parent.path()));
370                    } else {
371                      res.add(new PEDefinitionSlice(this, list.get(i).getSliceName(), profile, list.get(i), defn, parent.path()));
372                    }
373                  }
374                  i++;
375                }
376              } else {
377                res.add(pe);
378                i++;
379              }
380            } else {
381              i++;
382            } 
383          } else {
384            i++;
385          }
386        }
387      }
388      return res;
389    } else if (list.isEmpty()) {
390      throw new DefinitionException("not done yet!");
391    } else {
392      throw new DefinitionException("not done yet");
393    }
394  }
395
396  protected PEDefinition makeChild(PEDefinition parent, StructureDefinition profileStructure, ElementDefinition definition) {
397    PEDefinitionElement pe = new PEDefinitionElement(this, profileStructure, definition, parent.path());
398    if (context.isPrimitiveType(definition.getTypeFirstRep().getWorkingCode()) && "value".equals(pe.name())) {
399      pe.setMustHaveValue(definition.getMustHaveValue());
400    }
401    pe.setInFixedValue(definition.hasFixed() || definition.hasPattern() || parent.isInFixedValue());
402    return pe;
403  }
404
405  private boolean passElementPropsCheck(ElementDefinition bdefn) {
406    switch (elementProps) {
407    case EXTENSION:
408      return !Utilities.existsInList(bdefn.getBase().getPath(), "Element.id");
409    case NONE:
410      return !Utilities.existsInList(bdefn.getBase().getPath(), "Element.id", "Element.extension");
411    case EXTENSION_ID:
412    default:
413      return true;
414    }
415  }
416
417  private boolean isTypeSlicing(ElementDefinition defn) {
418    ElementDefinitionSlicingComponent sl = defn.getSlicing();
419    return sl.getRules() == SlicingRules.CLOSED && sl.getDiscriminator().size() == 1 &&
420        sl.getDiscriminatorFirstRep().getType() == DiscriminatorType.TYPE && "$this".equals(sl.getDiscriminatorFirstRep().getPath());
421  }
422
423  private boolean include(ElementDefinition defn) {
424    if (fixedPropsDefault) { 
425      return true;
426    } else { 
427      return !(defn.hasFixed() || defn.hasPattern());
428    }
429  }
430
431  protected List<PEDefinition> listSlices(StructureDefinition profileStructure, ElementDefinition definition, PEDefinition parent) {
432    List<ElementDefinition> list = pu.getSliceList(profileStructure, definition);
433    List<PEDefinition> res = new ArrayList<>();
434    for (ElementDefinition ed : list) {
435      if (profileStructure.getDerivation() == TypeDerivationRule.CONSTRAINT && profileStructure.getType().equals("Extension")) {
436        res.add(new PEDefinitionSubExtension(this, profileStructure, ed, parent.path()));
437      } else {
438        PEDefinitionElement pe = new PEDefinitionElement(this, profileStructure, ed, parent.path());
439        pe.setRecursing(definition == ed || (profileStructure.getDerivation() == TypeDerivationRule.SPECIALIZATION && profileStructure.getType().equals("Extension")));
440        res.add(pe);
441      }
442    }
443    return res;
444  }
445
446
447  private boolean checkType(ElementDefinition defn, String url) {
448    for (TypeRefComponent t : defn.getType()) {
449      if (("http://hl7.org/fhir/StructureDefinition/"+t.getWorkingCode()).equals(url)) {
450        return true;
451      }
452      for (CanonicalType u : t.getProfile()) {
453        if (url.equals(u.getValue())) {
454          return true;
455        }
456      }
457    }
458    return !defn.getPath().contains(".");
459  }
460
461
462  private StructureDefinition getExtensionDefinition(ElementDefinition ed) {
463    if ("Extension".equals(ed.getTypeFirstRep().getWorkingCode()) && ed.getTypeFirstRep().getProfile().size() == 1) {
464      return context.fetchResource(StructureDefinition.class, ed.getTypeFirstRep().getProfile().get(0).asStringValue());
465    } else {
466      return null;
467    }
468  }
469
470
471  private ElementDefinition getByName(List<ElementDefinition> blist, String name) {
472    for (ElementDefinition ed : blist) {
473      if (name.equals(ed.getName())) {
474        return ed;
475      }
476    }
477    return null;
478  }
479
480
481  protected PEType makeType(TypeRefComponent t) {
482    if (t.hasProfile()) {
483      StructureDefinition sd = context.fetchResource(StructureDefinition.class, t.getProfile().get(0).getValue());
484      if (sd == null) {
485        return new PEType(tail(t.getProfile().get(0).getValue()), t.getWorkingCode(), t.getProfile().get(0).getValue());
486      } else {
487        return new PEType(sd.getName(), t.getWorkingCode(), t.getProfile().get(0).getValue());
488      }
489    } else {
490      return makeType(t.getWorkingCode());
491    } 
492  }
493
494  protected PEType makeType(TypeRefComponent t, CanonicalType u) {
495    StructureDefinition sd = context.fetchResource(StructureDefinition.class, u.getValue());
496    if (sd == null) {
497      return new PEType(tail(u.getValue()), t.getWorkingCode(), u.getValue());
498    } else {
499      return new PEType(sd.getName(), t.getWorkingCode(), u.getValue());
500    }
501  }
502
503
504  protected PEType makeType(String tn, String url) {
505    return new PEType(tn, tn, url);
506  }
507  
508  protected PEType makeType(String tn) {
509    return new PEType(tn, tn, "http://hl7.org/fhir/StructureDefinition/"+ tn);
510  }
511
512  private String tail(String value) {
513    return value.contains("/") ? value.substring(value.lastIndexOf("/")+1) : value;
514  }
515
516  protected List<ElementDefinition> getChildren(StructureDefinition profileStructure, ElementDefinition definition) {
517    return pu.getChildList(profileStructure, definition);
518  }
519
520  private PEInstance loadInstance(PEDefinition defn, Resource resource) {
521    return new PEInstance(this, defn, resource, resource, defn.name());
522  }
523
524  public IWorkerContext getContext() {
525    return context;
526  }
527
528  protected void populateByProfile(Base base, PEDefinition definition) {
529    if (definition.types().size() == 1) {
530      for (PEDefinition pe : definition.directChildren(true)) {
531        if (pe.fixedValue()) {
532          if (pe.definition().hasPattern()) {
533            base.setProperty(pe.schemaName(), pe.definition().getPattern());
534          } else { 
535            base.setProperty(pe.schemaName(), pe.definition().getFixed());
536          }
537        } else if (!pe.isSlicer() && pe.max() == 1) {
538          for (int i = 0; i < pe.min(); i++) {
539            Base b = null;
540            if (pe.schemaName().endsWith("[x]")) {
541              if (pe.types().size() == 1) {
542                b = base.addChild(pe.schemaName().replace("[x]", Utilities.capitalize(pe.types().get(0).getType())));
543              }
544            } else if (!pe.isBaseList()) {
545              b = base.makeProperty(pe.schemaName().hashCode(), pe.schemaName());
546            } else {
547              b = base.addChild(pe.schemaName());
548            }
549            if (b != null) {
550              populateByProfile(b, pe);
551            }
552          }
553        }
554      }
555    }
556  }
557
558  public String makeSliceExpression(StructureDefinition profile, ElementDefinitionSlicingComponent slicing, ElementDefinition definition) {
559    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(" and ");
560    for (ElementDefinitionSlicingDiscriminatorComponent d : slicing.getDiscriminator()) {
561      switch (d.getType()) {
562      case EXISTS:
563        throw new DefinitionException("The discriminator type 'exists' is not supported by the PEBuilder");
564      case PATTERN:
565        throw new DefinitionException("The discriminator type 'pattern' is not supported by the PEBuilder");
566      case PROFILE:
567        throw new DefinitionException("The discriminator type 'profile' is not supported by the PEBuilder");
568      case TYPE:
569        throw new DefinitionException("The discriminator type 'type' is not supported by the PEBuilder");
570      case VALUE:
571        String path = d.getPath();
572        if (path.contains(".")) {
573          throw new DefinitionException("The discriminator path '"+path+"' is not supported by the PEBuilder");          
574        }
575        ElementDefinition ed = getChildElement(profile, definition, path);
576        if (ed == null) {
577          throw new DefinitionException("The discriminator path '"+path+"' could not be resolved by the PEBuilder");          
578        }
579        if (!ed.hasFixed()) {
580          throw new DefinitionException("The discriminator path '"+path+"' has no fixed value - this is not supported by the PEBuilder");          
581        }
582        if (!ed.getFixed().isPrimitive()) {
583          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");          
584        }
585        b.append(path+" = '"+ed.getFixed().primitiveValue()+"'");
586        break;
587      case NULL:
588        throw new DefinitionException("The discriminator type 'null' is not supported by the PEBuilder");
589      default:
590        throw new DefinitionException("The discriminator type '??' is not supported by the PEBuilder"); 
591      }
592    }
593    return b.toString();
594  }
595
596  private ElementDefinition getChildElement(StructureDefinition profile, ElementDefinition definition, String path) {
597    List<ElementDefinition> elements = pu.getChildList(profile, definition);
598    if (elements.size() == 0) {
599      profile = definition.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, definition.getTypeFirstRep().getProfile().get(0).asStringValue()) :
600        context.fetchTypeDefinition(definition.getTypeFirstRep().getWorkingCode());
601      elements = pu.getChildList(profile, profile.getSnapshot().getElementFirstRep());
602    }
603    return getByName(elements, path);
604  }
605
606  public List<Base> exec(Resource resource, Base data, String fhirpath) {
607    return fpe.evaluate(this, resource, resource, data, fhirpath);
608  }
609
610  public boolean isResource(String name) {
611    return context.getResourceNamesAsSet().contains(name);
612  }
613}