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