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}