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}