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