001package org.hl7.fhir.validation.instance.type;
002
003import java.util.ArrayList;
004import java.util.List;
005
006import org.hl7.fhir.r5.context.IWorkerContext;
007import org.hl7.fhir.r5.context.IWorkerContext.CodingValidationRequest;
008import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult;
009import org.hl7.fhir.r5.elementmodel.Element;
010import org.hl7.fhir.r5.model.Coding;
011import org.hl7.fhir.r5.model.Resource;
012import org.hl7.fhir.r5.model.ValueSet;
013import org.hl7.fhir.r5.terminologies.ValueSetExpander.TerminologyServiceErrorClass;
014import org.hl7.fhir.r5.utils.XVerExtensionManager;
015import org.hl7.fhir.utilities.Utilities;
016import org.hl7.fhir.utilities.VersionUtilities;
017import org.hl7.fhir.utilities.i18n.I18nConstants;
018import org.hl7.fhir.utilities.validation.ValidationMessage;
019import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
020import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
021import org.hl7.fhir.utilities.validation.ValidationOptions;
022import org.hl7.fhir.validation.BaseValidator;
023import org.hl7.fhir.validation.TimeTracker;
024import org.hl7.fhir.validation.instance.InstanceValidator;
025import org.hl7.fhir.validation.instance.utils.NodeStack;
026
027public class ValueSetValidator extends BaseValidator {
028
029  public class VSCodingValidationRequest extends CodingValidationRequest {
030
031    private NodeStack stack;
032
033    public VSCodingValidationRequest(NodeStack stack, Coding code) {
034      super(code);
035      this.stack = stack;
036    }
037
038    public NodeStack getStack() {
039      return stack;
040    }
041    
042  }
043
044  private InstanceValidator parent;
045
046  public ValueSetValidator(IWorkerContext context, TimeTracker timeTracker, InstanceValidator parent, XVerExtensionManager xverManager) {
047    super(context, xverManager);
048    source = Source.InstanceValidator;
049    this.timeTracker = timeTracker;
050    this.parent = parent;
051  }
052  
053  public void validateValueSet(List<ValidationMessage> errors, Element vs, NodeStack stack) {
054    if (!VersionUtilities.isR2Ver(context.getVersion())) {
055      List<Element> composes = vs.getChildrenByName("compose");
056      int cc = 0;
057      for (Element compose : composes) {
058        validateValueSetCompose(errors, compose, stack.push(compose, cc, null, null), vs.getNamedChildValue("url"), "retired".equals(vs.getNamedChildValue("url")));
059        cc++;
060      }
061    }
062  }
063
064  private void validateValueSetCompose(List<ValidationMessage> errors, Element compose, NodeStack stack, String vsid, boolean retired) {
065    List<Element> includes = compose.getChildrenByName("include");
066    int ci = 0;
067    for (Element include : includes) {
068      validateValueSetInclude(errors, include, stack.push(include, ci, null, null), vsid, retired);
069      ci++;
070    }    
071    List<Element> excludes = compose.getChildrenByName("exclude");
072    int ce = 0;
073    for (Element exclude : excludes) {
074      validateValueSetInclude(errors, exclude, stack.push(exclude, ce, null, null), vsid, retired);
075      ce++;
076    }    
077  }
078  
079  private void validateValueSetInclude(List<ValidationMessage> errors, Element include, NodeStack stack, String vsid, boolean retired) {
080    String system = include.getChildValue("system");
081    String version = include.getChildValue("version");
082    List<Element> valuesets = include.getChildrenByName("valueSet");
083    int i = 0;
084    for (Element ve : valuesets) {
085      String v = ve.getValue();
086      ValueSet vs = context.fetchResource(ValueSet.class, v);
087      if (vs == null) {
088        NodeStack ns = stack.push(ve, i, ve.getProperty().getDefinition(), ve.getProperty().getDefinition());
089
090        Resource rs = context.fetchResource(Resource.class, v);
091        if (rs != null) {
092          warning(errors, IssueType.BUSINESSRULE, ns.getLiteralPath(), false, I18nConstants.VALUESET_REFERENCE_INVALID_TYPE, v, rs.fhirType());                      
093        } else { 
094          // todo: it's possible, at this point, that the txx server knows the value set, but it's not in scope
095          // should we handle this case?
096          warning(errors, IssueType.BUSINESSRULE, ns.getLiteralPath(), false, I18nConstants.VALUESET_REFERENCE_UNKNOWN, v);            
097        }
098      }
099      i++;
100    }
101    List<Element> concepts = include.getChildrenByName("concept");
102    List<Element> filters = include.getChildrenByName("filter");
103    if (!Utilities.noString(system)) {
104      boolean systemOk = true;
105      int cc = 0;
106      List<VSCodingValidationRequest> batch = new ArrayList<>();
107      boolean first = true;
108      for (Element concept : concepts) {
109        // we treat the first differently because we want to know if tbe system is worth validating. if it is, then we batch the rest
110        if (first) {
111          systemOk = validateValueSetIncludeConcept(errors, concept, stack.push(concept, cc, null, null), system, version);
112          first = false;
113        } else if (systemOk) {
114          batch.add(prepareValidateValueSetIncludeConcept(errors, concept, stack.push(concept, cc, null, null), system, version));
115        }
116        cc++;
117      }    
118      if (parent.isValidateValueSetCodesOnTxServer() && batch.size() > 0) {
119        long t = System.currentTimeMillis();
120        if (parent.isDebug()) {
121          System.out.println("  : Validate "+batch.size()+" codes from "+system+" for "+vsid);
122        }
123        context.validateCodeBatch(ValidationOptions.defaults(), batch, null);
124        if (parent.isDebug()) {
125          System.out.println("  :   .. "+(System.currentTimeMillis()-t)+"ms");
126        }
127        for (VSCodingValidationRequest cv : batch) {
128          if (version == null) {
129            warningOrHint(errors, IssueType.BUSINESSRULE, cv.getStack().getLiteralPath(), cv.getResult().isOk(), !retired, I18nConstants.VALUESET_INCLUDE_INVALID_CONCEPT_CODE, system, cv.getCoding().getCode());
130          } else {
131            warningOrHint(errors, IssueType.BUSINESSRULE, cv.getStack().getLiteralPath(), cv.getResult().isOk(), !retired, I18nConstants.VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER, system, version, cv.getCoding().getCode());
132          }
133        }
134      }
135      
136      int cf = 0;
137      for (Element filter : filters) {
138        if (systemOk && !validateValueSetIncludeFilter(errors, include, stack.push(filter, cf, null, null), system, version)) {
139          systemOk = false;          
140        }
141        cf++;
142      }    
143      warning(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), systemOk, version == null ? I18nConstants.VALUESET_UNC_SYSTEM_WARNING :  I18nConstants.VALUESET_UNC_SYSTEM_WARNING_VER);            
144    } else {
145      warning(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), filters.size() == 0 && concepts.size() == 0, I18nConstants.VALUESET_NO_SYSTEM_WARNING);      
146    }
147  }
148
149  private boolean validateValueSetIncludeConcept(List<ValidationMessage> errors, Element concept, NodeStack stack, String system, String version) {
150    String code = concept.getChildValue("code");
151    if (version == null) {
152      ValidationResult vv = context.validateCode(ValidationOptions.defaults(), new Coding(system, code, null), null);
153      if (vv.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) {
154        return false;
155      } else {
156        boolean ok = vv.isOk();
157        warning(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), ok, I18nConstants.VALUESET_INCLUDE_INVALID_CONCEPT_CODE, system, code);
158      }
159    } else {
160      ValidationResult vv = context.validateCode(ValidationOptions.defaults(), new Coding(system, code, null).setVersion(version), null);
161      if (vv.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) {
162        return false;        
163      } else {
164        boolean ok = vv.isOk();
165        warning(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), ok, I18nConstants.VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER, system, version, code);
166      }
167    }
168    return true;
169  }
170
171  private VSCodingValidationRequest prepareValidateValueSetIncludeConcept(List<ValidationMessage> errors, Element concept, NodeStack stack, String system, String version) {
172    String code = concept.getChildValue("code");
173    Coding c = new Coding(system, code, null);
174    if (version != null) {
175       c.setVersion(version);
176    }
177    return new VSCodingValidationRequest(stack, c);
178  }
179
180  private boolean validateValueSetIncludeFilter(List<ValidationMessage> errors, Element include, NodeStack push, String system, String version) {
181    return true;
182  }
183}