001package org.hl7.fhir.r5.terminologies.utilities;
002
003import java.util.ArrayList;
004import java.util.List;
005
006import org.hl7.fhir.r5.context.IWorkerContext;
007import org.hl7.fhir.r5.model.BooleanType;
008import org.hl7.fhir.r5.model.CanonicalResource;
009import org.hl7.fhir.r5.model.DataType;
010import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
011import org.hl7.fhir.r5.model.OperationOutcome.IssueType;
012import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent;
013import org.hl7.fhir.r5.model.Extension;
014import org.hl7.fhir.r5.model.Parameters;
015import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent;
016import org.hl7.fhir.r5.model.UriType;
017import org.hl7.fhir.r5.model.UrlType;
018import org.hl7.fhir.r5.model.ValueSet;
019import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent;
020import org.hl7.fhir.r5.utils.ToolingExtensions;
021import org.hl7.fhir.utilities.StandardsStatus;
022import org.hl7.fhir.utilities.Utilities;
023import org.hl7.fhir.utilities.i18n.I18nConstants;
024import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
025
026public class ValueSetProcessBase {
027
028  public enum OpIssueCode {
029    NotInVS, ThisNotInVS, InvalidCode, Display, DisplayComment, NotFound, CodeRule, VSProcessing, InferFailed, StatusCheck, InvalidData;
030    
031    public String toCode() {
032      switch (this) {
033      case CodeRule: return "code-rule";
034      case Display: return "invalid-display";
035      case DisplayComment: return "display-comment";
036      case InferFailed: return "cannot-infer";
037      case InvalidCode: return "invalid-code";
038      case NotFound: return "not-found";
039      case NotInVS: return "not-in-vs";
040      case InvalidData: return "invalid-data";
041      case StatusCheck: return "status-check";
042      case ThisNotInVS: return "this-code-not-in-vs";
043      case VSProcessing: return "vs-invalid";
044      default:
045        return "??";      
046      }
047    }
048  }
049  protected IWorkerContext context;
050  protected TerminologyOperationContext opContext;
051  protected List<String> requiredSupplements = new ArrayList<>();
052  
053  protected ValueSetProcessBase(IWorkerContext context, TerminologyOperationContext opContext) {
054    super();
055    this.context = context;
056    this.opContext = opContext;
057  }
058  public static class AlternateCodesProcessingRules {
059    private boolean all;
060    private List<String> uses = new ArrayList<>();
061    
062    public AlternateCodesProcessingRules(boolean b) {
063      all = b;
064    }
065
066    private void seeParameter(DataType value) {
067      if (value != null) {
068        if (value instanceof BooleanType) {
069          all = ((BooleanType) value).booleanValue();
070          uses.clear();
071        } else if (value.isPrimitive()) {
072          String s = value.primitiveValue();
073          if (!Utilities.noString(s)) {
074            uses.add(s);
075          }
076        }
077      }
078    }
079
080    public void seeParameters(Parameters pp) {
081      for (ParametersParameterComponent p : pp.getParameter()) {
082        String name = p.getName();
083        if ("includeAlternateCodes".equals(name)) {
084          DataType value = p.getValue();
085          seeParameter(value);
086        }
087      }
088    }
089
090    public void seeValueSet(ValueSet vs) {
091      if (vs != null) {
092        for (Extension ext : vs.getCompose().getExtension()) {
093          if ("http://hl7.org/fhir/tools/StructureDefinion/valueset-expansion-param".equals(ext.getUrl())) {
094            String name = ext.getExtensionString("name");
095            Extension value = ext.getExtensionByUrl("value");
096            if ("includeAlternateCodes".equals(name) && value != null && value.hasValue()) {
097              seeParameter(value.getValue());
098            }
099          }
100        }
101      }
102    }
103
104    public boolean passes(List<Extension> extensions) {
105      if (all) {
106        return true;
107      }
108
109      for (Extension ext : extensions) {
110        if (ToolingExtensions.EXT_CS_ALTERNATE_USE.equals(ext.getUrl())) {
111          if (ext.hasValueCoding() && Utilities.existsInList(ext.getValueCoding().getCode(), uses)) {
112            return true;
113          }
114        }
115      }
116      return false;
117    }
118  }
119
120
121  protected List<OperationOutcomeIssueComponent> makeIssue(IssueSeverity level, IssueType type, String location, String message, OpIssueCode code, String server) {
122    OperationOutcomeIssueComponent result = new OperationOutcomeIssueComponent();
123    switch (level) {
124    case ERROR:
125      result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.ERROR);
126      break;
127    case FATAL:
128      result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.FATAL);
129      break;
130    case INFORMATION:
131      result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION);
132      break;
133    case WARNING:
134      result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.WARNING);
135      break;
136    }
137    result.setCode(type);
138    if (location != null) {
139      result.addLocation(location);
140      result.addExpression(location);
141    }
142    result.getDetails().setText(message);
143    if (code != null) {
144      result.getDetails().addCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", code.toCode(), null);
145    }
146    if (server != null) {
147      result.addExtension(ToolingExtensions.EXT_ISSUE_SERVER, new UrlType(server));
148    }
149    ArrayList<OperationOutcomeIssueComponent> list = new ArrayList<>();
150    list.add(result);
151    return list;
152  }
153  
154  public void checkCanonical(List<OperationOutcomeIssueComponent> issues, String path, CanonicalResource resource, CanonicalResource source) {
155    if (resource != null) {
156      StandardsStatus standardsStatus = ToolingExtensions.getStandardsStatus(resource);
157      if (standardsStatus == StandardsStatus.DEPRECATED) {
158        addToIssues(issues, makeStatusIssue(path, "deprecated", I18nConstants.MSG_DEPRECATED, resource));
159      } else if (standardsStatus == StandardsStatus.WITHDRAWN) {
160        addToIssues(issues, makeStatusIssue(path, "withdrawn", I18nConstants.MSG_WITHDRAWN, resource));
161      } else if (resource.getStatus() == PublicationStatus.RETIRED) {
162        addToIssues(issues, makeStatusIssue(path, "retired", I18nConstants.MSG_RETIRED, resource));
163      } else if (source != null) {
164        if (resource.getExperimental() && !source.getExperimental()) {
165          addToIssues(issues, makeStatusIssue(path, "experimental", I18nConstants.MSG_EXPERIMENTAL, resource));
166        } else if ((resource.getStatus() == PublicationStatus.DRAFT || standardsStatus == StandardsStatus.DRAFT)
167            && !(source.getStatus() == PublicationStatus.DRAFT || ToolingExtensions.getStandardsStatus(source) == StandardsStatus.DRAFT)) {
168          addToIssues(issues, makeStatusIssue(path, "draft", I18nConstants.MSG_DRAFT, resource));
169        }
170      } else {
171        if (resource.getExperimental()) {
172          addToIssues(issues, makeStatusIssue(path, "experimental", I18nConstants.MSG_EXPERIMENTAL, resource));
173        } else if ((resource.getStatus() == PublicationStatus.DRAFT || standardsStatus == StandardsStatus.DRAFT)) {
174          addToIssues(issues, makeStatusIssue(path, "draft", I18nConstants.MSG_DRAFT, resource));
175        }
176      }
177    }
178  }
179
180  private List<OperationOutcomeIssueComponent> makeStatusIssue(String path, String id, String msg, CanonicalResource resource) {
181    List<OperationOutcomeIssueComponent> iss = makeIssue(IssueSeverity.INFORMATION, IssueType.BUSINESSRULE, null, context.formatMessage(msg, resource.getVersionedUrl(), null, resource.fhirType()), OpIssueCode.StatusCheck, null);
182
183    // this is a testing hack - see TerminologyServiceTests
184    iss.get(0).setUserData("status-msg-name", "warning-"+id);
185    iss.get(0).setUserData("status-msg-value", new UriType(resource.getVersionedUrl()));
186    ToolingExtensions.setStringExtension(iss.get(0), ToolingExtensions.EXT_ISSUE_MSG_ID, msg);
187    
188    return iss;
189  }
190  
191  private void addToIssues(List<OperationOutcomeIssueComponent> issues, List<OperationOutcomeIssueComponent> toAdd) {
192    for (OperationOutcomeIssueComponent t : toAdd) {
193      boolean found = false;
194      for (OperationOutcomeIssueComponent i : issues) {
195        if (i.getSeverity() == t.getSeverity() && i.getCode() == t.getCode() && i.getDetails().getText().equals(t.getDetails().getText())) { // ignore location
196          found = true;
197        }
198      }
199      if (!found) {
200        issues.add(t);
201      }
202    }    
203  }
204
205  public void checkCanonical(ValueSetExpansionComponent params, CanonicalResource resource, ValueSet source) {
206    if (resource != null) {
207      StandardsStatus standardsStatus = ToolingExtensions.getStandardsStatus(resource);
208      if (standardsStatus == StandardsStatus.DEPRECATED) {
209        if (!params.hasParameterValue("warning-deprecated", resource.getVersionedUrl())) {
210          params.addParameter("warning-deprecated", new UriType(resource.getVersionedUrl()));
211        } 
212      } else if (standardsStatus == StandardsStatus.WITHDRAWN) {
213        if (!params.hasParameterValue("warning-withdrawn", resource.getVersionedUrl())) {
214          params.addParameter("warning-withdrawn", new UriType(resource.getVersionedUrl()));
215        } 
216      } else if (resource.getStatus() == PublicationStatus.RETIRED) {
217        if (!params.hasParameterValue("warning-retired", resource.getVersionedUrl())) {
218          params.addParameter("warning-retired", new UriType(resource.getVersionedUrl()));
219        } 
220      } else if (resource.getExperimental() && !source.getExperimental()) {
221        if (!params.hasParameterValue("warning-experimental", resource.getVersionedUrl())) {
222          params.addParameter("warning-experimental", new UriType(resource.getVersionedUrl()));
223        }         
224      } else if ((resource.getStatus() == PublicationStatus.DRAFT || standardsStatus == StandardsStatus.DRAFT)
225          && !(source.getStatus() == PublicationStatus.DRAFT || ToolingExtensions.getStandardsStatus(source) == StandardsStatus.DRAFT)) {
226        if (!params.hasParameterValue("warning-draft", resource.getVersionedUrl())) {
227          params.addParameter("warning-draft", new UriType(resource.getVersionedUrl()));
228        }         
229      }
230    }
231  }
232
233  public TerminologyOperationContext getOpContext() {
234    return opContext;
235  }
236
237                         
238  protected AlternateCodesProcessingRules altCodeParams = new AlternateCodesProcessingRules(false);
239  protected AlternateCodesProcessingRules allAltCodes = new AlternateCodesProcessingRules(true);
240}