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, CodeComment;
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      case CodeComment: return "code-comment";
070      default:
071        return "??";      
072      }
073    }
074  }
075  protected IWorkerContext context;
076  private ContextUtilities cu;
077  protected TerminologyOperationContext opContext;
078  protected List<String> requiredSupplements = new ArrayList<>();
079  
080  protected ValueSetProcessBase(IWorkerContext context, TerminologyOperationContext opContext) {
081    super();
082    this.context = context;
083    this.opContext = opContext;
084  }
085  public static class AlternateCodesProcessingRules {
086    private boolean all;
087    private List<String> uses = new ArrayList<>();
088    
089    public AlternateCodesProcessingRules(boolean b) {
090      all = b;
091    }
092
093    private void seeParameter(DataType value) {
094      if (value != null) {
095        if (value instanceof BooleanType) {
096          all = ((BooleanType) value).booleanValue();
097          uses.clear();
098        } else if (value.isPrimitive()) {
099          String s = value.primitiveValue();
100          if (!Utilities.noString(s)) {
101            uses.add(s);
102          }
103        }
104      }
105    }
106
107    public void seeParameters(Parameters pp) {
108      for (ParametersParameterComponent p : pp.getParameter()) {
109        String name = p.getName();
110        if ("includeAlternateCodes".equals(name)) {
111          DataType value = p.getValue();
112          seeParameter(value);
113        }
114      }
115    }
116
117    public void seeValueSet(ValueSet vs) {
118      if (vs != null) {
119        for (Extension ext : vs.getCompose().getExtension()) {
120          if (Utilities.existsInList(ext.getUrl(), ExtensionDefinitions.EXT_VS_EXP_PARAM_NEW, ExtensionDefinitions.EXT_VS_EXP_PARAM_OLD)) {
121            String name = ext.getExtensionString("name");
122            Extension value = ext.getExtensionByUrl("value");
123            if ("includeAlternateCodes".equals(name) && value != null && value.hasValue()) {
124              seeParameter(value.getValue());
125            }
126          }
127        }
128      }
129    }
130
131    public boolean passes(List<Extension> extensions) {
132      if (all) {
133        return true;
134      }
135
136      for (Extension ext : extensions) {
137        if (ExtensionDefinitions.EXT_CS_ALTERNATE_USE.equals(ext.getUrl())) {
138          if (ext.hasValueCoding() && Utilities.existsInList(ext.getValueCoding().getCode(), uses)) {
139            return true;
140          }
141        }
142      }
143      return false;
144    }
145  }
146
147  protected List<OperationOutcomeIssueComponent> makeIssue(IssueSeverity level, IssueType type, String location, String message, OpIssueCode code, String server, String msgId) {
148    OperationOutcomeIssueComponent result = new OperationOutcomeIssueComponent();
149    switch (level) {
150    case ERROR:
151      result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.ERROR);
152      break;
153    case FATAL:
154      result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.FATAL);
155      break;
156    case INFORMATION:
157      result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION);
158      break;
159    case WARNING:
160      result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.WARNING);
161      break;
162    }
163    result.setCode(type);
164    if (location != null) {
165      result.addLocation(location);
166      result.addExpression(location);
167    }
168    result.getDetails().setText(message);
169    if (code != null) {
170      result.getDetails().addCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", code.toCode(), null);
171    }
172    if (server != null) {
173      result.addExtension(ExtensionDefinitions.EXT_ISSUE_SERVER, new UrlType(server));
174    }
175    if (msgId != null) {      
176      result.addExtension(ExtensionDefinitions.EXT_ISSUE_MSG_ID, new StringType(msgId));
177    }
178    ArrayList<OperationOutcomeIssueComponent> list = new ArrayList<>();
179    list.add(result);
180    return list;
181  }
182  
183  public void checkCanonical(List<OperationOutcomeIssueComponent> issues, String path, CanonicalResource resource, CanonicalResource source) {
184    if (resource != null) {
185      StandardsStatus standardsStatus = ExtensionUtilities.getStandardsStatus(resource);
186      if (standardsStatus == StandardsStatus.DEPRECATED) {
187        addToIssues(issues, makeStatusIssue(path, "deprecated", I18nConstants.MSG_DEPRECATED, resource));
188      } else if (standardsStatus == StandardsStatus.WITHDRAWN) {
189        addToIssues(issues, makeStatusIssue(path, "withdrawn", I18nConstants.MSG_WITHDRAWN, resource));
190      } else if (resource.getStatus() == PublicationStatus.RETIRED) {
191        addToIssues(issues, makeStatusIssue(path, "retired", I18nConstants.MSG_RETIRED, resource));
192      } else if (source != null) {
193        if (resource.getExperimental() && !source.getExperimental()) {
194          addToIssues(issues, makeStatusIssue(path, "experimental", I18nConstants.MSG_EXPERIMENTAL, resource));
195        } else if ((resource.getStatus() == PublicationStatus.DRAFT || standardsStatus == StandardsStatus.DRAFT)
196            && !(source.getStatus() == PublicationStatus.DRAFT || ExtensionUtilities.getStandardsStatus(source) == StandardsStatus.DRAFT)) {
197          addToIssues(issues, makeStatusIssue(path, "draft", I18nConstants.MSG_DRAFT, resource));
198        }
199      } else {
200        if (resource.getExperimental()) {
201          addToIssues(issues, makeStatusIssue(path, "experimental", I18nConstants.MSG_EXPERIMENTAL, resource));
202        } else if ((resource.getStatus() == PublicationStatus.DRAFT || standardsStatus == StandardsStatus.DRAFT)) {
203          addToIssues(issues, makeStatusIssue(path, "draft", I18nConstants.MSG_DRAFT, resource));
204        }
205      }
206    }
207  }
208
209  private List<OperationOutcomeIssueComponent> makeStatusIssue(String path, String id, String msg, CanonicalResource resource) {
210    List<OperationOutcomeIssueComponent> iss = makeIssue(IssueSeverity.INFORMATION, IssueType.BUSINESSRULE, null,
211      context.formatMessage(msg, resource.getVersionedUrl(), null, resource.fhirType()), OpIssueCode.StatusCheck, null, msg);
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    ExtensionUtilities.setStringExtension(iss.get(0), ExtensionDefinitions.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 = ExtensionUtilities.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 || ExtensionUtilities.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}