
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}