001package org.hl7.fhir.validation.instance.type;
002
003import java.util.List;
004
005import org.hl7.fhir.exceptions.FHIRException;
006import org.hl7.fhir.r5.context.IWorkerContext;
007import org.hl7.fhir.r5.elementmodel.Element;
008import org.hl7.fhir.r5.model.Coding;
009import org.hl7.fhir.r5.model.ValueSet;
010import org.hl7.fhir.r5.utils.XVerExtensionManager;
011import org.hl7.fhir.utilities.Utilities;
012import org.hl7.fhir.utilities.i18n.I18nConstants;
013import org.hl7.fhir.utilities.validation.ValidationMessage;
014import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
015import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
016import org.hl7.fhir.utilities.validation.ValidationOptions;
017import org.hl7.fhir.validation.BaseValidator;
018import org.hl7.fhir.validation.TimeTracker;
019import org.hl7.fhir.validation.instance.InstanceValidator;
020import org.hl7.fhir.validation.instance.utils.NodeStack;
021
022public class CodeSystemValidator  extends BaseValidator {
023
024  private InstanceValidator parent;
025
026  public CodeSystemValidator(IWorkerContext context, TimeTracker timeTracker, InstanceValidator parent, XVerExtensionManager xverManager, Coding jurisdiction) {
027    super(context, xverManager);
028    source = Source.InstanceValidator;
029    this.timeTracker = timeTracker;
030    this.jurisdiction = jurisdiction;
031    this.parent = parent;
032
033  }
034
035  public boolean validateCodeSystem(List<ValidationMessage> errors, Element cs, NodeStack stack, ValidationOptions options) {
036    boolean ok = true;
037    String url = cs.getNamedChildValue("url");
038    String content = cs.getNamedChildValue("content");
039    String caseSensitive = cs.getNamedChildValue("caseSensitive");
040    String hierarchyMeaning = cs.getNamedChildValue("hierarchyMeaning");
041    String supp = cs.getNamedChildValue("supplements");
042
043    metaChecks(errors, cs, stack, url, content, caseSensitive, hierarchyMeaning, !Utilities.noString(supp));
044
045    String vsu = cs.getNamedChildValue("valueSet");
046    if (!Utilities.noString(vsu)) {
047      hint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), "complete".equals(content), I18nConstants.CODESYSTEM_CS_NO_VS_NOTCOMPLETE);
048      ValueSet vs;
049      try {
050        vs = context.fetchResourceWithException(ValueSet.class, vsu);
051      } catch (FHIRException e) {
052        vs = null;
053      }
054      if (vs != null) {
055        if (rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs.hasCompose(), I18nConstants.CODESYSTEM_CS_VS_INVALID, url, vsu)) { 
056          if (rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs.getCompose().getInclude().size() == 1, I18nConstants.CODESYSTEM_CS_VS_INVALID, url, vsu)) {
057            if (rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs.getCompose().getInclude().get(0).getSystem().equals(url), I18nConstants.CODESYSTEM_CS_VS_WRONGSYSTEM, url, vsu, vs.getCompose().getInclude().get(0).getSystem())) {
058              ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), !vs.getCompose().getInclude().get(0).hasValueSet()
059                  && !vs.getCompose().getInclude().get(0).hasConcept() && !vs.getCompose().getInclude().get(0).hasFilter(), I18nConstants.CODESYSTEM_CS_VS_INCLUDEDETAILS, url, vsu) && ok;
060              if (vs.hasExpansion()) {
061                int count = countConcepts(cs); 
062                ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs.getExpansion().getContains().size() == count, I18nConstants.CODESYSTEM_CS_VS_EXP_MISMATCH, url, vsu, count, vs.getExpansion().getContains().size()) && ok;
063              }
064            } else {
065              ok = false;
066            }
067          } else {
068            ok = false;
069          }
070        } else {
071          ok = false;
072        }
073      }
074    } // todo... try getting the value set the other way...
075
076    if (supp != null) {
077      if (context.supportsSystem(supp)) {
078        List<Element> concepts = cs.getChildrenByName("concept");
079        int ce = 0;
080        for (Element concept : concepts) {
081          ok = validateSupplementConcept(errors, concept, stack.push(concept, ce, null, null), supp, options) && ok;
082          ce++;
083        }    
084      } else {
085        if (cs.hasChildren("concept")) {
086          warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_SUPP_CANT_CHECK, supp);
087        }
088      }
089    }
090
091    if (!stack.isContained()) {
092      ok = checkShareableCodeSystem(errors, cs, stack) && ok;
093    }
094    return ok;
095  }
096
097
098  private boolean checkShareableCodeSystem(List<ValidationMessage> errors, Element cs, NodeStack stack) {
099    if (parent.isForPublication()) { 
100      if (isHL7(cs)) {
101        boolean ok = true;
102        ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("url"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING_HL7, "url") && ok;                      
103        ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("version"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING_HL7, "version") && ok;                      
104        ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("title"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING_HL7, "title") && ok;                      
105        warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("name"), I18nConstants.CODESYSTEM_SHAREABLE_EXTRA_MISSING_HL7, "name");                      
106        ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("status"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING_HL7, "status") && ok;                      
107        ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("experimental"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING_HL7, "experimental") && ok;                      
108        ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("description"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING_HL7, "description") && ok; 
109        ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("content"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING_HL7, "content") && ok; 
110        if (!"supplement".equals(cs.getChildValue("content"))) {
111          ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("caseSensitive"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING_HL7, "caseSensitive") && ok;
112        }
113        return ok;
114      } else {
115        warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("url"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING, "url");                      
116        warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("version"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING, "version");                      
117        warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("title"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING, "title");                      
118        warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("name"), I18nConstants.CODESYSTEM_SHAREABLE_EXTRA_MISSING, "name");                      
119        warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("status"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING, "status");                      
120        warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("experimental"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING, "experimental");                      
121        warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("description"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING, "description"); 
122        warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("content"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING, "content"); 
123        if (!"supplement".equals(cs.getChildValue("content"))) {
124          warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("caseSensitive"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING, "caseSensitive");
125        }
126      }
127    }
128    return true;
129  }
130  
131  private void metaChecks(List<ValidationMessage> errors, Element cs, NodeStack stack, String url,  String content, String caseSensitive, String hierarchyMeaning, boolean isSupplement) {
132    if (isSupplement) {
133      if (!"supplement".equals(content)) {
134        NodeStack s = stack.push(cs.getNamedChild("content"), -1, null, null);
135        rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_PRESENT_ELEMENT_SUPPL_WRONG);
136      }
137      if (!Utilities.noString(caseSensitive)) {
138        NodeStack s = stack.push(cs.getNamedChild("caseSensitive"), -1, null, null);
139        rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_PRESENT_ELEMENT_SUPPL, "caseSensitive");
140      }
141      if (!Utilities.noString(hierarchyMeaning)) {
142        NodeStack s = stack.push(cs.getNamedChild("hierarchyMeaning"), -1, null, null);
143        rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_PRESENT_ELEMENT_SUPPL, "hierarchyMeaning");
144      }
145
146    } else {
147      boolean isHL7 = url != null && (url.contains("hl7.org") || url.contains("fhir.org"));
148      if (Utilities.noString(content)) {
149        NodeStack s = stack;
150        Element c = cs.getNamedChild("content");
151        if (c != null) {
152          s = stack.push(c, -1, null, null);
153        }
154        if (isHL7) {
155          rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_MISSING_ELEMENT_SHALL, "content");
156        } else {
157          warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_NONHL7_MISSING_ELEMENT, "content");          
158        } 
159      } else if ("supplement".equals(content)) {
160        NodeStack s = stack.push(cs.getNamedChild("content"), -1, null, null);
161        rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_PRESENT_ELEMENT_SUPPL_MISSING);        
162      }
163      if (Utilities.noString(caseSensitive)) {
164        NodeStack s = stack;
165        Element c = cs.getNamedChild("caseSensitive");
166        if (c != null) {
167          s = stack.push(c, -1, null, null);
168        }
169        if (isHL7) {
170          warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_MISSING_ELEMENT_SHOULD, "caseSensitive");
171        } else {
172          hint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_NONHL7_MISSING_ELEMENT, "caseSensitive");          
173        } 
174      }      
175      if (Utilities.noString(hierarchyMeaning) && hasHeirarchy(cs)) {
176        NodeStack s = stack;
177        Element c = cs.getNamedChild("hierarchyMeaning");
178        if (c != null) {
179          s = stack.push(c, -1, null, null);
180        }
181        if (isHL7) {
182          warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_MISSING_ELEMENT_SHOULD, "hierarchyMeaning");
183        } else {
184          hint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_NONHL7_MISSING_ELEMENT, "hierarchyMeaning");          
185        } 
186      }     
187    }
188  }
189
190
191  private boolean hasHeirarchy(Element cs) {
192    for (Element c : cs.getChildren("concept")) {
193      if (c.hasChildren("concept")) {
194        return true;
195      }
196    }
197    return false;
198  }
199
200  private boolean validateSupplementConcept(List<ValidationMessage> errors, Element concept, NodeStack stack, String supp, ValidationOptions options) {
201    String code = concept.getChildValue("code");
202    if (!Utilities.noString(code)) {
203      org.hl7.fhir.r5.context.IWorkerContext.ValidationResult res = context.validateCode(options, systemFromCanonical(supp), versionFromCanonical(supp), code, null);
204      return rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), res.isOk(), I18nConstants.CODESYSTEM_CS_SUPP_INVALID_CODE, supp, code);
205    } else {
206      return true;
207    }
208
209  }
210
211  private int countConcepts(Element cs) {
212    List<Element> concepts = cs.getChildrenByName("concept");
213    int res = concepts.size();
214    for (Element concept : concepts) {
215      res = res + countConcepts(concept);
216    }
217    return res;
218  }
219
220
221}