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.CodeSystem;
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.utils.NodeStack;
020
021import ca.uhn.fhir.validation.ValidationResult;
022
023public class CodeSystemValidator  extends BaseValidator {
024
025  public CodeSystemValidator(IWorkerContext context, TimeTracker timeTracker, XVerExtensionManager xverManager) {
026    super(context, xverManager);
027    source = Source.InstanceValidator;
028    this.timeTracker = timeTracker;
029  }
030
031  public void validateCodeSystem(List<ValidationMessage> errors, Element cs, NodeStack stack, ValidationOptions options) {
032    String url = cs.getNamedChildValue("url");
033    String content = cs.getNamedChildValue("content");
034    String caseSensitive = cs.getNamedChildValue("caseSensitive");
035    String hierarchyMeaning = cs.getNamedChildValue("hierarchyMeaning");
036    String supp = cs.getNamedChildValue("supplements");
037
038    metaChecks(errors, cs, stack, url, content, caseSensitive, hierarchyMeaning, !Utilities.noString(supp));
039
040    String vsu = cs.getNamedChildValue("valueSet");
041    if (!Utilities.noString(vsu)) {
042      hint(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), "complete".equals(content), I18nConstants.CODESYSTEM_CS_NO_VS_NOTCOMPLETE);
043      ValueSet vs;
044      try {
045        vs = context.fetchResourceWithException(ValueSet.class, vsu);
046      } catch (FHIRException e) {
047        vs = null;
048      }
049      if (vs != null) {
050        if (rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs.hasCompose(), I18nConstants.CODESYSTEM_CS_VS_INVALID, url, vsu)) { 
051          if (rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs.getCompose().getInclude().size() == 1, I18nConstants.CODESYSTEM_CS_VS_INVALID, url, vsu)) {
052            if (rule(errors, 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())) {
053              rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), !vs.getCompose().getInclude().get(0).hasValueSet()
054                  && !vs.getCompose().getInclude().get(0).hasConcept() && !vs.getCompose().getInclude().get(0).hasFilter(), I18nConstants.CODESYSTEM_CS_VS_INCLUDEDETAILS, url, vsu);
055              if (vs.hasExpansion()) {
056                int count = countConcepts(cs); 
057                rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs.getExpansion().getContains().size() == count, I18nConstants.CODESYSTEM_CS_VS_EXP_MISMATCH, url, vsu, count, vs.getExpansion().getContains().size());
058              }
059            }
060          }
061        }
062      }
063    } // todo... try getting the value set the other way...
064
065    if (supp != null) {
066      if (context.supportsSystem(supp)) {
067        List<Element> concepts = cs.getChildrenByName("concept");
068        int ce = 0;
069        for (Element concept : concepts) {
070          validateSupplementConcept(errors, concept, stack.push(concept, ce, null, null), supp, options);
071          ce++;
072        }    
073      } else {
074        if (cs.hasChildren("concept")) {
075          warning(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_SUPP_CANT_CHECK, supp);
076        }
077      }
078    }
079  }
080
081  private void metaChecks(List<ValidationMessage> errors, Element cs, NodeStack stack, String url,  String content, String caseSensitive, String hierarchyMeaning, boolean isSupplement) {
082    if (isSupplement) {
083      if (!"supplement".equals(content)) {
084        NodeStack s = stack.push(cs.getNamedChild("content"), -1, null, null);
085        rule(errors, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_PRESENT_ELEMENT_SUPPL_WRONG);
086      }
087      if (!Utilities.noString(caseSensitive)) {
088        NodeStack s = stack.push(cs.getNamedChild("caseSensitive"), -1, null, null);
089        rule(errors, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_PRESENT_ELEMENT_SUPPL, "caseSensitive");
090      }
091      if (!Utilities.noString(hierarchyMeaning)) {
092        NodeStack s = stack.push(cs.getNamedChild("hierarchyMeaning"), -1, null, null);
093        rule(errors, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_PRESENT_ELEMENT_SUPPL, "caseSensitive");
094      }
095
096    } else {
097      boolean isHL7 = url != null && (url.contains("hl7.org") || url.contains("fhir.org"));
098      if (Utilities.noString(content)) {
099        NodeStack s = stack;
100        Element c = cs.getNamedChild("content");
101        if (c != null) {
102          s = stack.push(c, -1, null, null);
103        }
104        if (isHL7) {
105          rule(errors, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_MISSING_ELEMENT_SHALL, "content");
106        } else {
107          warning(errors, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_NONHL7_MISSING_ELEMENT, "content");          
108        } 
109      } else if ("supplement".equals(content)) {
110        NodeStack s = stack.push(cs.getNamedChild("content"), -1, null, null);
111        rule(errors, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_PRESENT_ELEMENT_SUPPL_MISSING);        
112      }
113      if (Utilities.noString(caseSensitive)) {
114        NodeStack s = stack;
115        Element c = cs.getNamedChild("caseSensitive");
116        if (c != null) {
117          s = stack.push(c, -1, null, null);
118        }
119        if (isHL7) {
120          warning(errors, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_MISSING_ELEMENT_SHOULD, "caseSensitive");
121        } else {
122          hint(errors, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_NONHL7_MISSING_ELEMENT, "caseSensitive");          
123        } 
124      }      
125      if (Utilities.noString(hierarchyMeaning) && hasHeirarchy(cs)) {
126        NodeStack s = stack;
127        Element c = cs.getNamedChild("hierarchyMeaning");
128        if (c != null) {
129          s = stack.push(c, -1, null, null);
130        }
131        if (isHL7) {
132          warning(errors, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_MISSING_ELEMENT_SHOULD, "hierarchyMeaning");
133        } else {
134          hint(errors, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_NONHL7_MISSING_ELEMENT, "hierarchyMeaning");          
135        } 
136      }     
137    }
138  }
139
140
141  private boolean hasHeirarchy(Element cs) {
142    for (Element c : cs.getChildren("concept")) {
143      if (c.hasChildren("concept")) {
144        return true;
145      }
146    }
147    return false;
148  }
149
150  private void validateSupplementConcept(List<ValidationMessage> errors, Element concept, NodeStack stack, String supp, ValidationOptions options) {
151    String code = concept.getChildValue("code");
152    if (!Utilities.noString(code)) {
153      org.hl7.fhir.r5.context.IWorkerContext.ValidationResult res = context.validateCode(options, systemFromCanonical(supp), versionFromCanonical(supp), code, null);
154      rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), res.isOk(), I18nConstants.CODESYSTEM_CS_SUPP_INVALID_CODE, supp, code);
155    }
156
157  }
158
159  private int countConcepts(Element cs) {
160    List<Element> concepts = cs.getChildrenByName("concept");
161    int res = concepts.size();
162    for (Element concept : concepts) {
163      res = res + countConcepts(concept);
164    }
165    return res;
166  }
167
168
169}