001package org.hl7.fhir.validation.instance.type;
002
003import java.io.ByteArrayOutputStream;
004import java.io.IOException;
005import java.util.ArrayList;
006import java.util.Comparator;
007import java.util.HashSet;
008import java.util.List;
009import java.util.Set;
010
011import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50;
012import org.hl7.fhir.convertors.factory.VersionConvertorFactory_14_50;
013import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_50;
014import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50;
015import org.hl7.fhir.exceptions.FHIRException;
016import org.hl7.fhir.r5.conformance.ProfileUtilities;
017import org.hl7.fhir.r5.context.IWorkerContext;
018import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult;
019import org.hl7.fhir.r5.elementmodel.Element;
020import org.hl7.fhir.r5.elementmodel.Manager;
021import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
022import org.hl7.fhir.r5.formats.IParser.OutputStyle;
023import org.hl7.fhir.r5.model.Coding;
024import org.hl7.fhir.r5.model.ElementDefinition;
025import org.hl7.fhir.r5.model.ExpressionNode;
026import org.hl7.fhir.r5.model.Extension;
027import org.hl7.fhir.r5.model.Resource;
028import org.hl7.fhir.r5.model.StructureDefinition;
029import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
030import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
031import org.hl7.fhir.r5.model.ValueSet;
032import org.hl7.fhir.r5.utils.FHIRPathEngine;
033import org.hl7.fhir.r5.utils.ToolingExtensions;
034import org.hl7.fhir.r5.utils.XVerExtensionManager;
035import org.hl7.fhir.utilities.Utilities;
036import org.hl7.fhir.utilities.VersionUtilities;
037import org.hl7.fhir.utilities.i18n.I18nConstants;
038import org.hl7.fhir.utilities.validation.ValidationMessage;
039import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
040import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
041import org.hl7.fhir.utilities.validation.ValidationOptions;
042import org.hl7.fhir.validation.BaseValidator;
043import org.hl7.fhir.validation.TimeTracker;
044import org.hl7.fhir.validation.instance.utils.NodeStack;
045
046public class StructureDefinitionValidator extends BaseValidator {
047
048  public class FhirPathSorter implements Comparator<ExpressionNode> {
049
050    @Override
051    public int compare(ExpressionNode arg0, ExpressionNode arg1) {
052      return arg0.toString().compareTo(arg1.toString());
053    }
054
055  }
056
057  private FHIRPathEngine fpe;
058  private boolean wantCheckSnapshotUnchanged;
059
060  public StructureDefinitionValidator(IWorkerContext context, TimeTracker timeTracker, FHIRPathEngine fpe, boolean wantCheckSnapshotUnchanged, XVerExtensionManager xverManager, Coding jurisdiction) {
061    super(context, xverManager);
062    source = Source.InstanceValidator;
063    this.fpe = fpe;
064    this.timeTracker = timeTracker;
065    this.wantCheckSnapshotUnchanged = wantCheckSnapshotUnchanged;
066    this.jurisdiction = jurisdiction;
067  }
068  
069  public boolean validateStructureDefinition(List<ValidationMessage> errors, Element src, NodeStack stack)  {
070    boolean ok = true;
071    StructureDefinition sd = null;
072    String typeName = null;
073    try {
074      sd = loadAsSD(src);
075      List<ElementDefinition> snapshot = sd.getSnapshot().getElement();
076      sd.setSnapshot(null);
077      typeName = sd.getTypeName();
078      StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
079      if (warning(errors, NO_RULE_DATE, IssueType.NOTFOUND, stack.getLiteralPath(), base != null, I18nConstants.UNABLE_TO_FIND_BASE__FOR_, sd.getBaseDefinition(), "StructureDefinition, so can't check the differential")) {
080        if (rule(errors, NO_RULE_DATE, IssueType.NOTFOUND, stack.getLiteralPath(), sd.hasDerivation(), I18nConstants.SD_MUST_HAVE_DERIVATION, sd.getUrl())) {
081          boolean bok = base.getAbstract() || sd.hasKind() && sd.getKind() == base.getKind();
082          rule(errors, "2022-11-02", IssueType.NOTFOUND, stack.getLiteralPath(), bok, I18nConstants.SD_CONSTRAINED_KIND_NO_MATCH, sd.getKind().toCode(), base.getKind().toCode(), base.getType(), base.getUrl());
083          if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) {
084            rule(errors, "2022-11-02", IssueType.NOTFOUND, stack.getLiteralPath(), sd.hasType() && sd.getType().equals(base.getType()), I18nConstants.SD_CONSTRAINED_TYPE_NO_MATCH, sd.getType(), base.getType());
085            List<ValidationMessage> msgs = new ArrayList<>();
086            ProfileUtilities pu = new ProfileUtilities(context, msgs, null);
087            pu.setXver(xverManager);
088            pu.setNewSlicingProcessing(!sd.hasFhirVersion() || VersionUtilities.isR4Plus(sd.getFhirVersion().toCode()));
089            pu.generateSnapshot(base, sd, sd.getUrl(), "http://hl7.org/fhir/R4/", sd.getName());
090            if (msgs.size() > 0) {
091              for (ValidationMessage msg : msgs) {
092                // we need to set the location for the context 
093                String loc = msg.getLocation();
094                if (loc.contains("#")) {
095                  msg.setLocation(stack.getLiteralPath()+".differential.element.where(path = '"+loc.substring(loc.indexOf("#")+1)+"')");
096                } else {
097                  msg.setLocation(stack.getLiteralPath());
098                }
099                errors.add(msg);
100                ok = false;
101              }
102            }
103            if (!snapshot.isEmpty() && wantCheckSnapshotUnchanged) {
104              int was = snapshot.size();
105              int is = sd.getSnapshot().getElement().size();
106              ok = rule(errors, NO_RULE_DATE, IssueType.NOTFOUND, stack.getLiteralPath(), was == is, I18nConstants.SNAPSHOT_EXISTING_PROBLEM, was, is) && ok;
107            }
108          } else {
109            rule(errors, NO_RULE_DATE, IssueType.NOTFOUND, stack.getLiteralPath(), sd.hasType() && !sd.getType().equals(base.getType()), I18nConstants.SD_SPECIALIZED_TYPE_MATCHES, sd.getType(), base.getType());
110          }
111        } else {
112          ok = false;
113        }
114        if ("constraint".equals(src.getChildValue("derivation"))) {
115          ok = rule(errors, "2022-11-02", IssueType.NOTFOUND, stack.getLiteralPath(), base.getKindElement().primitiveValue().equals(src.getChildValue("kind")), 
116              I18nConstants.SD_DERIVATION_KIND_MISMATCH, base.getKindElement().primitiveValue(), src.getChildValue("kind")) && ok;
117        }
118      }
119    } catch (FHIRException | IOException e) {
120      rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.ERROR_GENERATING_SNAPSHOT, e.getMessage());
121      ok = false;
122    }
123    List<Element> differentials = src.getChildrenByName("differential");
124    List<Element> snapshots = src.getChildrenByName("snapshot");
125    for (Element differential : differentials) {
126      ok = validateElementList(errors, differential, stack.push(differential, -1, null, null), false, snapshots.size() > 0, sd, typeName) && ok;
127    }
128    for (Element snapshot : snapshots) {
129      ok = validateElementList(errors, snapshot, stack.push(snapshot, -1, null, null), true, true, sd, typeName) && ok;
130    }
131    return ok;
132  }
133  
134  private boolean validateElementList(List<ValidationMessage> errors, Element elementList, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName) {
135    boolean ok = true;
136    List<Element> elements = elementList.getChildrenByName("element");
137    int cc = 0;
138    for (Element element : elements) {
139      ok = validateElementDefinition(errors, element, stack.push(element, cc, null, null), snapshot, hasSnapshot, sd, typeName) && ok;
140      cc++;
141    }    
142    return ok;
143  }
144
145  private boolean validateElementDefinition(List<ValidationMessage> errors, Element element, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName) {
146    boolean ok = true;
147    boolean typeMustSupport = false;
148    String path = element.getNamedChildValue("path");
149    rule(errors, "2022-11-02", IssueType.NOTFOUND, stack.getLiteralPath(), typeName == null || path == null || path.equals(typeName) || path.startsWith(typeName+"."), I18nConstants.SD_PATH_TYPE_MISMATCH, typeName, path);
150    List<Element> types = element.getChildrenByName("type");
151    Set<String> typeCodes = new HashSet<>();
152    Set<String> characteristics = new HashSet<>();
153    
154    for (Element type : types) {
155      if (hasMustSupportExtension(type)) {
156        typeMustSupport = true;
157      }
158      String tc = type.getChildValue("code");
159      if (type.hasExtension(ToolingExtensions.EXT_FHIR_TYPE)) {
160        tc = type.getExtensionValue(ToolingExtensions.EXT_FHIR_TYPE).primitiveValue();
161      }
162      if (Utilities.noString(tc) && type.hasChild("code")) {
163        throw new Error("WTF?");
164//        if (type.getNamedChild("code").hasExtension(" http://hl7.org/fhir/StructureDefinition/structuredefinition-json-type")) {
165//          tc = "*";
166//        }
167      }
168      typeCodes.add(tc);
169      Set<String> tcharacteristics = new HashSet<>();
170      StructureDefinition tsd = context.fetchTypeDefinition(tc);
171      if (tsd != null && tsd.hasExtension(ToolingExtensions.EXT_TYPE_CHARACTERISTICS)) {
172        for (Extension ext : tsd.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_CHARACTERISTICS)) {
173          tcharacteristics.add(ext.getValue().primitiveValue());
174        }
175      } else {
176        // nothing specified, so infer from known types
177        addCharacteristics(tcharacteristics, tc);
178      }
179      characteristics.addAll(tcharacteristics);
180      if (type.hasChildren("targetProfile")) {
181        ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), tcharacteristics.contains("has-target") , I18nConstants.SD_ILLEGAL_CHARACTERISTICS, "targetProfile", tc) && ok;
182      }
183      // check the stated profile - must be a constraint on the type 
184      if (snapshot || sd != null) {
185        ok = validateElementType(errors, type, stack.push(type, -1, null, null), sd, path) && ok;
186      }
187    }
188    if (typeMustSupport) {
189      if (snapshot) {
190        ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), "true".equals(element.getChildValue("mustSupport")), I18nConstants.SD_NESTED_MUST_SUPPORT_SNAPSHOT, path) && ok;
191      } else {
192        hint(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), hasSnapshot || "true".equals(element.getChildValue("mustSupport")), I18nConstants.SD_NESTED_MUST_SUPPORT_DIFF, path);        
193      }
194    }
195    if (element.hasChild("binding")) {
196      if (!typeCodes.isEmpty()) {
197        ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), characteristics.contains("can-bind") , I18nConstants.SD_ILLEGAL_CHARACTERISTICS, "Binding", typeCodes) && ok;
198      }
199      Element binding = element.getNamedChild("binding");
200      ok = validateBinding(errors, binding, stack.push(binding, -1, null, null), typeCodes, snapshot, path) && ok;
201    } else {
202      // this is a good idea but there's plenty of cases where the rule isn't met; maybe one day it's worth investing the time to exclude these cases and bring this rule back
203//      String bt = boundType(typeCodes);
204//      hint(errors, UNKNOWN_DATE_TIME, IssueType.BUSINESSRULE, stack.getLiteralPath(), !snapshot || bt == null, I18nConstants.SD_ED_SHOULD_BIND, element.getNamedChildValue("path"), bt);              
205    }
206    if (!typeCodes.isEmpty()) {
207      if (element.hasChild("maxLength")) {
208        ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), characteristics.contains("has-length") , I18nConstants.SD_ILLEGAL_CHARACTERISTICS, "MaxLength", typeCodes) && ok;      
209      }
210      if (element.hasExtension(ToolingExtensions.EXT_MIN_LENGTH)) {
211        ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), characteristics.contains("has-length") , I18nConstants.SD_ILLEGAL_CHARACTERISTICS, "MinLength Extension", typeCodes) && ok;      
212      }
213      if (element.hasChild("minValue")) {
214        ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), characteristics.contains("has-range") , I18nConstants.SD_ILLEGAL_CHARACTERISTICS, "MinValue", typeCodes) && ok;      
215      }
216      if (element.hasChild("maxValue")) {
217        ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), characteristics.contains("has-range") , I18nConstants.SD_ILLEGAL_CHARACTERISTICS, "MaxValue", typeCodes) && ok;      
218      }
219      if (element.hasExtension(ToolingExtensions.EXT_MAX_DECIMALS)) {
220        ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), characteristics.contains("is-continuous") , I18nConstants.SD_ILLEGAL_CHARACTERISTICS, "Max Decimal Places Extension", typeCodes) && ok;      
221      }
222      if (element.hasExtension(ToolingExtensions.EXT_MAX_SIZE)) {
223        ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), characteristics.contains("has-size") , I18nConstants.SD_ILLEGAL_CHARACTERISTICS, "Max Size", typeCodes) && ok;      
224      }
225    }
226    // in a snapshot, we validate that fixedValue, pattern, and defaultValue, if present, are all of the right type
227    if (snapshot && (element.getIdBase() != null) && (element.getIdBase().contains("."))) {
228      if (rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), !typeCodes.isEmpty() || element.hasChild("contentReference"), I18nConstants.SD_NO_TYPES_OR_CONTENTREF, element.getIdBase())) {     
229        // if we see fixed[x] or pattern[x] applied to a repeating element, we'll give the user a hint
230        boolean repeating = !Utilities.existsInList(element.getChildValue("max"), "0", "1");
231        
232        Element v = element.getNamedChild("defaultValue");
233        if (v != null) {
234          ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.push(v, -1, null, null).getLiteralPath(), typeCodes.contains(v.fhirType()), I18nConstants.SD_VALUE_TYPE_IILEGAL, element.getIdBase(), "defaultValue", v.fhirType(), typeCodes) && ok;
235        }
236        v = element.getNamedChild("fixed");
237        if (v != null) {
238          ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.push(v, -1, null, null).getLiteralPath(), typeCodes.contains(v.fhirType()), I18nConstants.SD_VALUE_TYPE_IILEGAL, element.getIdBase(), "fixed", v.fhirType(), typeCodes) && ok;
239          hint(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.push(v, -1, null, null).getLiteralPath(), !repeating, I18nConstants.SD_VALUE_TYPE_REPEAT_HINT, element.getIdBase(), "fixed");
240          if (isPrimitiveType(v.fhirType())) {
241            warning(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.push(v, -1, null, null).getLiteralPath(), !repeating, I18nConstants.SD_VALUE_TYPE_REPEAT_WARNING_DOTNET, element.getIdBase(), "fixed");
242          } else {
243            warning(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.push(v, -1, null, null).getLiteralPath(), false, I18nConstants.SD_VALUE_COMPLEX_FIXED, v.fhirType());            
244          }
245        }
246        v = element.getNamedChild("pattern");
247        if (v != null) {
248          ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.push(v, -1, null, null).getLiteralPath(), typeCodes.contains(v.fhirType()), I18nConstants.SD_VALUE_TYPE_IILEGAL, element.getIdBase(), "pattern", v.fhirType(), typeCodes) && ok;
249          hint(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.push(v, -1, null, null).getLiteralPath(), !repeating, I18nConstants.SD_VALUE_TYPE_REPEAT_HINT, element.getIdBase(), "pattern");
250          if (isPrimitiveType(v.fhirType())) {
251            warning(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.push(v, -1, null, null).getLiteralPath(), !repeating, I18nConstants.SD_VALUE_TYPE_REPEAT_WARNING_DOTNET, element.getIdBase(), "pattern");
252          }
253        }
254      }
255      // if we see fixed[x] or pattern[x] applied to a repeating element, we'll give the user a hint
256    }
257    return ok;
258  }
259  
260  private boolean addCharacteristics(Set<String> set, String tc) {
261    switch (tc) {
262    case "boolean" : return addCharacteristicsForType(set);
263    case "integer" : return addCharacteristicsForType(set, "has-range", "has-length");
264    case "integer64" : return addCharacteristicsForType(set, "has-range", "has-length");
265    case "decimal" :return  addCharacteristicsForType(set, "has-range", "is-continuous", "has-length");
266    case "base64Binary" : return addCharacteristicsForType(set, "has-size");
267    case "instant" : return addCharacteristicsForType(set, "has-range", "is-continuous", "has-length");
268    case "string" : return addCharacteristicsForType(set, "has-length", "do-translations", "can-bind");
269    case "uri" : return addCharacteristicsForType(set, "has-length", "can-bind");
270    case "date" :return  addCharacteristicsForType(set, "has-range", "has-length");
271    case "dateTime" : return addCharacteristicsForType(set, "has-range", "is-continuous", "has-length");
272    case "time" :return  addCharacteristicsForType(set, "has-range", "is-continuous", "has-length");
273    case "canonical" :return  addCharacteristicsForType(set, "has-target", "has-length");
274    case "code" :return  addCharacteristicsForType(set, "has-length", "can-bind");
275    case "id" :return  addCharacteristicsForType(set, "has-length");
276    case "markdown" :return  addCharacteristicsForType(set, "do-translations", "has-length");
277    case "oid" :return  addCharacteristicsForType(set, "has-length", "can-bind");
278    case "positiveInt" :return  addCharacteristicsForType(set, "has-range", "has-length");
279    case "unsignedInt" :return  addCharacteristicsForType(set, "has-range", "has-length");
280    case "url" :return  addCharacteristicsForType(set, "has-length", "can-bind");
281    case "uuid" :return  addCharacteristicsForType(set, "has-length", "can-bind");
282    case "xhtml" :return  addCharacteristicsForType(set);
283    case "Address" :return  addCharacteristicsForType(set, "do-translations");
284    case "Age" : return addCharacteristicsForType(set, "has-range", "is-continuous", "can-bind", "has-units");
285    case "Annotation" :return  addCharacteristicsForType(set);
286    case "Attachment" :return  addCharacteristicsForType(set, "has-size", "do-translations");
287    case "CodeableConcept" :return  addCharacteristicsForType(set, "can-bind", "do-translations");
288    case "CodeableReference" : return addCharacteristicsForType(set, "has-target", "can-bind", "do-translations");
289    case "Coding" : return addCharacteristicsForType(set, "can-bind", "do-translations");
290    case "ContactPoint" :return  addCharacteristicsForType(set);
291    case "Count" :return  addCharacteristicsForType(set, "has-range");
292    case "Distance" :return  addCharacteristicsForType(set, "has-range", "is-continuous", "can-bind", "has-units");
293    case "Duration" : return addCharacteristicsForType(set, "has-range", "is-continuous", "can-bind", "has-units");
294    case "HumanName" :return  addCharacteristicsForType(set);
295    case "Identifier" : return addCharacteristicsForType(set);
296    case "Money" : return addCharacteristicsForType(set, "has-range", "is-continuous");
297    case "Period" : return addCharacteristicsForType(set);
298    case "Quantity" :return  addCharacteristicsForType(set, "has-range", "is-continuous", "can-bind", "has-units");
299    case "Range" :return  addCharacteristicsForType(set, "has-units", "can-bind", "has-units");
300    case "Ratio" :return  addCharacteristicsForType(set, "has-units");
301    case "RatioRange" : return addCharacteristicsForType(set, "has-units");
302    case "Reference" : return addCharacteristicsForType(set, "has-target");
303    case "SampledData" :return  addCharacteristicsForType(set);
304    case "Signature" : return addCharacteristicsForType(set);
305    case "Timing" : return addCharacteristicsForType(set);
306    case "ContactDetail" :return  addCharacteristicsForType(set);
307    case "Contributor" :return  addCharacteristicsForType(set);
308    case "DataRequirement" :return  addCharacteristicsForType(set);
309    case "Expression" : return addCharacteristicsForType(set);
310    case "ParameterDefinition" : return addCharacteristicsForType(set);
311    case "RelatedArtifact" :return  addCharacteristicsForType(set);
312    case "TriggerDefinition" :return  addCharacteristicsForType(set);
313    case "UsageContext" :return  addCharacteristicsForType(set);
314    case "Dosage" : return addCharacteristicsForType(set);
315    case "Meta" :return  addCharacteristicsForType(set);
316    case "Resource" :return  addCharacteristicsForType(set);
317    case "Extension" :return  addCharacteristicsForType(set, "can-bind");
318    case "Narrative" :return  addCharacteristicsForType(set);
319    case "MoneyQuantity" :return  addCharacteristicsForType(set, "has-range", "is-continuous", "can-bind", "has-units");
320    case "SimpleQuantity" :return  addCharacteristicsForType(set, "has-range", "is-continuous", "can-bind", "has-units");
321    case "MarketingStatus" :return  addCharacteristicsForType(set);
322    case "ExtendedContactDetail" :return  addCharacteristicsForType(set);
323    case "VirtualServiceDetail" :return  addCharacteristicsForType(set);
324    case "Availability" :return  addCharacteristicsForType(set);
325    case "MonetaryComponent" :return  addCharacteristicsForType(set);
326    case "ElementDefinition" :return  addCharacteristicsForType(set);
327
328    case "BackboneElement" :return  addCharacteristicsForType(set);
329    case "Element" :return  addCharacteristicsForType(set);
330    case "Base" :return  addCharacteristicsForType(set);
331    default:
332//      if (!context.getResourceNames().contains(tc)) {
333//        System.out.println("Unhandled data type in addCharacteristics: "+tc);        
334//      }
335      return addCharacteristicsForType(set);
336    }
337  }
338
339
340  private boolean addCharacteristicsForType(Set<String> set, String... cl) {
341    for (String c : cl) {
342      set.add(c);
343    }
344    return true;
345  }
346
347  private boolean isPrimitiveType(String fhirType) {
348    StructureDefinition sd = context.fetchTypeDefinition(fhirType);
349    return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
350  }
351
352  private String boundType(Set<String> typeCodes) {
353    for (String tc : typeCodes) {
354      if (Utilities.existsInList(tc, "code", "Coding", "CodeableConcept", "Quantity", "CodeableReference")) {
355        return tc;
356      }
357    }
358    return null;
359  }
360
361  private String bindableType(Set<String> typeCodes) {
362    String ret = boundType(typeCodes);
363    if (ret != null) {
364      return ret;
365    }
366    for (String tc : typeCodes) {
367      if (Utilities.existsInList(tc, "string", "uri", "CodeableConcept", "Quantity", "CodeableReference")) {
368        return tc;
369      }
370      StructureDefinition sd = context.fetchTypeDefinition(tc);
371      if (sd != null) {
372        if (sd.hasExtension(ToolingExtensions.EXT_BINDING_STYLE)) {
373          return tc;          
374        }
375        for (Extension ext : sd.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_CHARACTERISTICS)) {
376          if ("can-bind".equals(ext.getValue().primitiveValue())) {
377            return tc;
378          }
379        }
380      }
381    }
382    return null;
383  }
384
385  private boolean validateBinding(List<ValidationMessage> errors, Element binding, NodeStack stack, Set<String> typeCodes, boolean snapshot, String path) {
386    boolean ok = true;
387    if (bindableType(typeCodes) == null) {
388      ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), !snapshot, I18nConstants.SD_ED_BIND_NO_BINDABLE, path, typeCodes.toString()) && ok;
389    } 
390    if (!snapshot) {
391      Set<String> bindables = getListofBindableTypes(typeCodes);    
392      hint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), bindables.size() <= 1, I18nConstants.SD_ED_BIND_MULTIPLE_TYPES, path, typeCodes.toString());
393    }
394    
395    if (binding.hasChild("valueSet")) {
396      Element valueSet = binding.getNamedChild("valueSet");
397      String ref = valueSet.hasPrimitiveValue() ? valueSet.primitiveValue() : valueSet.getNamedChildValue("reference");
398      if (warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), !snapshot || ref != null, I18nConstants.SD_ED_SHOULD_BIND_WITH_VS, path)) {
399        Resource vs = context.fetchResource(Resource.class, ref);
400        
401        // just because we can't resolve it directly doesn't mean that terminology server can't. Check with it
402        
403        if (warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs != null || serverSupportsValueSet(ref), I18nConstants.SD_ED_BIND_UNKNOWN_VS, path, ref)) {
404          if (vs != null) {
405            ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs instanceof ValueSet, I18nConstants.SD_ED_BIND_NOT_VS, path, ref, vs.fhirType()) && ok;
406          }
407        }
408      }
409    } 
410    return ok;
411  }
412
413  private Set<String> getListofBindableTypes(Set<String> types) {
414    Set<String> res = new HashSet<>();
415    for (String s : types) {
416      if (Utilities.existsInList(s, "code", "string", "url", "uri", "Coding", "CodeableConcept", "Quantity", "CodeableReference")) {
417        res.add(s);
418      }
419    }
420    return res;
421  }
422
423  private boolean serverSupportsValueSet(String ref) {
424    ValidationResult vr = context.validateCode(new ValidationOptions().checkValueSetOnly().setVsAsUrl().noClient(), new Coding("http://loinc.org", "5792-7", null), new ValueSet().setUrl(ref));
425    return vr.getErrorClass() == null;
426  }
427
428  private boolean validateElementType(List<ValidationMessage> errors, Element type, NodeStack stack, StructureDefinition sd, String path) {
429    boolean ok = true;
430    String code = type.getNamedChildValue("code");
431    if (code == null && path != null) {
432      code = getTypeCodeFromSD(sd, path);
433    }
434    if (code != null) {
435      List<Element> profiles = type.getChildrenByName("profile");
436      if (VersionUtilities.isR2Ver(context.getVersion()) || VersionUtilities.isR2BVer(context.getVersion()) ) {
437        for (Element profile : profiles) {
438          ok = validateProfileTypeOrTarget(errors, profile, code, stack.push(profile, -1, null, null), path) && ok;
439        }
440        
441      } else {
442        for (Element profile : profiles) {
443          ok = validateTypeProfile(errors, profile, code, stack.push(profile, -1, null, null), path) && ok;
444        }
445        profiles = type.getChildrenByName("targetProfile");
446        for (Element profile : profiles) {
447          ok = validateTargetProfile(errors, profile, code, stack.push(profile, -1, null, null), path) && ok;
448        }
449      }
450    }
451    return true;
452  }
453
454  private boolean validateProfileTypeOrTarget(List<ValidationMessage> errors, Element profile, String code, NodeStack stack, String path) {
455    boolean ok = true;
456    String p = profile.primitiveValue();
457    StructureDefinition sd = context.fetchResource(StructureDefinition.class, p);
458    if (code.equals("Reference")) {
459      if (warning(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), sd != null, I18nConstants.SD_ED_TYPE_PROFILE_UNKNOWN, p)) {
460        StructureDefinition t = determineBaseType(sd);
461        if (t == null) {
462          ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.SD_ED_TYPE_PROFILE_NOTYPE, p) && ok;
463        } else {
464          ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), sd.getKind() == StructureDefinitionKind.RESOURCE, I18nConstants.SD_ED_TYPE_PROFILE_WRONG, p, t, code, path) && ok;
465        }
466      }
467    } else {
468      if (sd == null ) {
469        sd = getXverExt(errors, stack.getLiteralPath(), profile, p);
470      }
471      if (warning(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), sd != null, I18nConstants.SD_ED_TYPE_PROFILE_UNKNOWN, p)) {
472        StructureDefinition t = determineBaseType(sd);
473        if (t == null) {
474          ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.SD_ED_TYPE_PROFILE_NOTYPE, p) && ok;
475        } else {
476          ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), isInstanceOf(t, code), I18nConstants.SD_ED_TYPE_PROFILE_WRONG, p, t, code, path) && ok;
477          if (t.getType().equals("Extension")) {
478            boolean isModifierDefinition = checkIsModifierExtension(sd);
479            boolean isModifierContext = path.endsWith(".modifierExtension");
480            if (isModifierDefinition) {
481              ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), isModifierContext, I18nConstants.SD_ED_TYPE_PROFILE_NOT_MODIFIER, p, t, code, path) && ok;            
482            } else {
483              ok =rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), !isModifierContext, I18nConstants.SD_ED_TYPE_PROFILE_IS_MODIFIER, p, t, code, path) && ok;
484            }          
485          }
486        }
487      }      
488    }
489    return ok;
490  }
491
492  private String getTypeCodeFromSD(StructureDefinition sd, String path) {
493    ElementDefinition ed = null;
494    for (ElementDefinition t : sd.getSnapshot().getElement()) {
495      if (t.hasPath() && t.getPath().equals(path)) {
496        if (ed == null) {
497          ed = t;
498        } else {
499          return null; // more than one match, we don't know which is which
500        }
501      }
502    }
503    return ed != null && ed.getType().size() == 1 ? ed.getTypeFirstRep().getCode() : null;
504  }
505
506  private boolean validateTypeProfile(List<ValidationMessage> errors, Element profile, String code, NodeStack stack, String path) {
507    boolean ok = true;
508    String p = profile.primitiveValue();
509    StructureDefinition sd = context.fetchResource(StructureDefinition.class, p);
510    if (sd == null ) {
511      sd = getXverExt(errors, stack.getLiteralPath(), profile, p);
512    }
513    if (warning(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), sd != null, I18nConstants.SD_ED_TYPE_PROFILE_UNKNOWN, p)) {
514      StructureDefinition t = determineBaseType(sd);
515      if (t == null) {
516        ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.SD_ED_TYPE_PROFILE_NOTYPE, p) && ok;
517      } else if (!isInstanceOf(t, code)) {
518        ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.SD_ED_TYPE_PROFILE_WRONG, p, t, code, path) && ok;
519      } else {
520        if (t.getType().equals("Extension")) {
521          boolean isModifierDefinition = checkIsModifierExtension(sd);
522          boolean isModifierContext = path.endsWith(".modifierExtension");
523          if (isModifierDefinition) {
524            ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), isModifierContext, I18nConstants.SD_ED_TYPE_PROFILE_NOT_MODIFIER, p, t, code, path) && ok;            
525          } else {
526            ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), !isModifierContext, I18nConstants.SD_ED_TYPE_PROFILE_IS_MODIFIER, p, t, code, path) && ok;
527          }          
528        }
529      }
530    }
531    return ok;
532  }
533
534  private boolean checkIsModifierExtension(StructureDefinition t) {
535    return t.getSnapshot().getElementFirstRep().getIsModifier();
536  }
537
538  private boolean validateTargetProfile(List<ValidationMessage> errors, Element profile, String code, NodeStack stack, String path) {
539    boolean ok = true;
540    String p = profile.primitiveValue();
541    StructureDefinition sd = context.fetchResource(StructureDefinition.class, p);
542    if (code.equals("Reference") || code.equals("CodeableReference")) {
543      if (warning(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), sd != null, I18nConstants.SD_ED_TYPE_PROFILE_UNKNOWN, p)) {
544        StructureDefinition t = determineBaseType(sd);
545        if (t == null) {
546          ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.SD_ED_TYPE_PROFILE_NOTYPE, p) && ok;
547        } else {
548          ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), sd.getKind() == StructureDefinitionKind.RESOURCE, I18nConstants.SD_ED_TYPE_PROFILE_WRONG_TARGET, p, t, code, path, "Resource") && ok;
549        }
550      }
551    } else if (code.equals("canonical")) {
552      if (warning(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), sd != null, I18nConstants.SD_ED_TYPE_PROFILE_UNKNOWN, p)) {
553        StructureDefinition t = determineBaseType(sd);
554        if (t == null) {
555          ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.SD_ED_TYPE_PROFILE_NOTYPE, p) && ok;
556        } else if (!VersionUtilities.isR5Ver(context.getVersion())) {
557          ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), VersionUtilities.getCanonicalResourceNames(context.getVersion()).contains(t.getType()) || "Resource".equals(t.getType()), I18nConstants.SD_ED_TYPE_PROFILE_WRONG_TARGET, p, t, code, path, "Canonical Resource") && ok;
558        } else {
559          ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), VersionUtilities.getCanonicalResourceNames(context.getVersion()).contains(t.getType()), I18nConstants.SD_ED_TYPE_PROFILE_WRONG_TARGET, p, t, code, path, "Canonical Resource") && ok;
560        }  
561      }
562    } else {
563      ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.SD_ED_TYPE_NO_TARGET_PROFILE, code) && ok;
564    }
565    return ok;
566  }
567
568  private boolean isInstanceOf(StructureDefinition sd, String code) {
569    while (sd != null) {
570      if (sd.getType().equals(code)) {
571        return true;
572      }
573      if (sd.getUrl().equals(code)) {
574        return true;
575      }
576      sd = sd.hasBaseDefinition() ? context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()) : null;
577      if (!(VersionUtilities.isR2Ver(context.getVersion()) || VersionUtilities.isR2BVer(context.getVersion())) && sd != null && !sd.getAbstract() && sd.getKind() != StructureDefinitionKind.LOGICAL) {
578        sd = null;
579      }
580    }
581    
582    return false;
583  }
584
585  private StructureDefinition determineBaseType(StructureDefinition sd) {
586    while (sd != null && sd.getDerivation() == TypeDerivationRule.CONSTRAINT) {
587      sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
588    }
589    return sd;
590  }
591
592  private boolean hasMustSupportExtension(Element type) {
593    if ("true".equals(getExtensionValue(type, ToolingExtensions.EXT_MUST_SUPPORT))) {
594      return true;
595    }
596    List<Element> profiles = type.getChildrenByName("profile");
597    for (Element profile : profiles) {
598      if ("true".equals(getExtensionValue(profile, ToolingExtensions.EXT_MUST_SUPPORT))) {
599        return true;
600      }
601    }
602    profiles = type.getChildrenByName("targetProfile");
603    for (Element profile : profiles) {
604      if ("true".equals(getExtensionValue(profile, ToolingExtensions.EXT_MUST_SUPPORT))) {
605        return true;
606      }
607    }
608    return false;
609  }
610
611  private String getExtensionValue(Element element, String url) {
612    List<Element> extensions = element.getChildrenByName("extension");
613    for (Element extension : extensions) {
614      if (url.equals(extension.getNamedChildValue("url"))) {
615        return extension.getNamedChildValue("value");
616      }
617    }
618    return null;
619  }
620
621  private StructureDefinition loadAsSD(Element src) throws FHIRException, IOException {
622    ByteArrayOutputStream bs = new ByteArrayOutputStream();
623    Manager.compose(context, src, bs, FhirFormat.JSON, OutputStyle.NORMAL, null);
624    if (VersionUtilities.isR2Ver(context.getVersion())) {
625      org.hl7.fhir.dstu2.model.Resource r2 = new org.hl7.fhir.dstu2.formats.JsonParser().parse(bs.toByteArray());
626      return (StructureDefinition) VersionConvertorFactory_10_50.convertResource(r2);
627    }
628    if (VersionUtilities.isR2BVer(context.getVersion())) {
629      org.hl7.fhir.dstu2016may.model.Resource r2b = new org.hl7.fhir.dstu2016may.formats.JsonParser().parse(bs.toByteArray());
630      return (StructureDefinition) VersionConvertorFactory_14_50.convertResource(r2b);
631    }
632    if (VersionUtilities.isR3Ver(context.getVersion())) {
633      org.hl7.fhir.dstu3.model.Resource r3 = new org.hl7.fhir.dstu3.formats.JsonParser().parse(bs.toByteArray());
634      return (StructureDefinition) VersionConvertorFactory_30_50.convertResource(r3);
635    }
636    if (VersionUtilities.isR4Ver(context.getVersion())) {
637      org.hl7.fhir.r4.model.Resource r4 = new org.hl7.fhir.r4.formats.JsonParser().parse(bs.toByteArray());
638      return (StructureDefinition) VersionConvertorFactory_40_50.convertResource(r4);
639    }
640    return (StructureDefinition) new org.hl7.fhir.r5.formats.JsonParser().parse(bs.toByteArray());
641  }
642
643}