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