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