
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.model.BooleanType; 010import org.hl7.fhir.r5.model.CanonicalResource; 011import org.hl7.fhir.r5.model.CodeSystem; 012import org.hl7.fhir.r5.model.DataType; 013import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; 014import org.hl7.fhir.r5.model.OperationOutcome.IssueType; 015import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent; 016import org.hl7.fhir.r5.model.Extension; 017import org.hl7.fhir.r5.model.Parameters; 018import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent; 019import org.hl7.fhir.r5.model.StringType; 020import org.hl7.fhir.r5.model.UriType; 021import org.hl7.fhir.r5.model.UrlType; 022import org.hl7.fhir.r5.model.ValueSet; 023import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent; 024import org.hl7.fhir.r5.utils.ToolingExtensions; 025import org.hl7.fhir.r5.utils.UserDataNames; 026import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 027import org.hl7.fhir.utilities.StandardsStatus; 028import org.hl7.fhir.utilities.Utilities; 029import org.hl7.fhir.utilities.i18n.I18nConstants; 030import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 031 032@MarkedToMoveToAdjunctPackage 033public class ValueSetProcessBase { 034 035 public static class TerminologyOperationDetails implements ITerminologyOperationDetails { 036 037 private List<String> supplements; 038 039 public TerminologyOperationDetails(List<String> supplements) { 040 super(); 041 this.supplements = supplements; 042 } 043 044 @Override 045 public void seeSupplement(CodeSystem supp) { 046 supplements.remove(supp.getUrl()); 047 supplements.remove(supp.getVersionedUrl()); 048 } 049 } 050 051 public enum OpIssueCode { 052 NotInVS, ThisNotInVS, InvalidCode, Display, DisplayComment, NotFound, CodeRule, VSProcessing, InferFailed, StatusCheck, InvalidData; 053 054 public String toCode() { 055 switch (this) { 056 case CodeRule: return "code-rule"; 057 case Display: return "invalid-display"; 058 case DisplayComment: return "display-comment"; 059 case InferFailed: return "cannot-infer"; 060 case InvalidCode: return "invalid-code"; 061 case NotFound: return "not-found"; 062 case NotInVS: return "not-in-vs"; 063 case InvalidData: return "invalid-data"; 064 case StatusCheck: return "status-check"; 065 case ThisNotInVS: return "this-code-not-in-vs"; 066 case VSProcessing: return "vs-invalid"; 067 default: 068 return "??"; 069 } 070 } 071 } 072 protected IWorkerContext context; 073 private ContextUtilities cu; 074 protected TerminologyOperationContext opContext; 075 protected List<String> requiredSupplements = new ArrayList<>(); 076 077 protected ValueSetProcessBase(IWorkerContext context, TerminologyOperationContext opContext) { 078 super(); 079 this.context = context; 080 this.opContext = opContext; 081 } 082 public static class AlternateCodesProcessingRules { 083 private boolean all; 084 private List<String> uses = new ArrayList<>(); 085 086 public AlternateCodesProcessingRules(boolean b) { 087 all = b; 088 } 089 090 private void seeParameter(DataType value) { 091 if (value != null) { 092 if (value instanceof BooleanType) { 093 all = ((BooleanType) value).booleanValue(); 094 uses.clear(); 095 } else if (value.isPrimitive()) { 096 String s = value.primitiveValue(); 097 if (!Utilities.noString(s)) { 098 uses.add(s); 099 } 100 } 101 } 102 } 103 104 public void seeParameters(Parameters pp) { 105 for (ParametersParameterComponent p : pp.getParameter()) { 106 String name = p.getName(); 107 if ("includeAlternateCodes".equals(name)) { 108 DataType value = p.getValue(); 109 seeParameter(value); 110 } 111 } 112 } 113 114 public void seeValueSet(ValueSet vs) { 115 if (vs != null) { 116 for (Extension ext : vs.getCompose().getExtension()) { 117 if ("http://hl7.org/fhir/tools/StructureDefinion/valueset-expansion-param".equals(ext.getUrl())) { 118 String name = ext.getExtensionString("name"); 119 Extension value = ext.getExtensionByUrl("value"); 120 if ("includeAlternateCodes".equals(name) && value != null && value.hasValue()) { 121 seeParameter(value.getValue()); 122 } 123 } 124 } 125 } 126 } 127 128 public boolean passes(List<Extension> extensions) { 129 if (all) { 130 return true; 131 } 132 133 for (Extension ext : extensions) { 134 if (ToolingExtensions.EXT_CS_ALTERNATE_USE.equals(ext.getUrl())) { 135 if (ext.hasValueCoding() && Utilities.existsInList(ext.getValueCoding().getCode(), uses)) { 136 return true; 137 } 138 } 139 } 140 return false; 141 } 142 } 143 144 145 protected List<OperationOutcomeIssueComponent> makeIssue(IssueSeverity level, IssueType type, String location, String message, OpIssueCode code, String server) { 146 return makeIssue(level, type, location, message, code, server, null); 147 } 148 protected List<OperationOutcomeIssueComponent> makeIssue(IssueSeverity level, IssueType type, String location, String message, OpIssueCode code, String server, String msgId) { 149 OperationOutcomeIssueComponent result = new OperationOutcomeIssueComponent(); 150 switch (level) { 151 case ERROR: 152 result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.ERROR); 153 break; 154 case FATAL: 155 result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.FATAL); 156 break; 157 case INFORMATION: 158 result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION); 159 break; 160 case WARNING: 161 result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.WARNING); 162 break; 163 } 164 result.setCode(type); 165 if (location != null) { 166 result.addLocation(location); 167 result.addExpression(location); 168 } 169 result.getDetails().setText(message); 170 if (code != null) { 171 result.getDetails().addCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", code.toCode(), null); 172 } 173 if (server != null) { 174 result.addExtension(ToolingExtensions.EXT_ISSUE_SERVER, new UrlType(server)); 175 } 176 if (msgId != null) { 177 result.addExtension(ToolingExtensions.EXT_ISSUE_MSG_ID, new StringType(msgId)); 178 } 179 ArrayList<OperationOutcomeIssueComponent> list = new ArrayList<>(); 180 list.add(result); 181 return list; 182 } 183 184 public void checkCanonical(List<OperationOutcomeIssueComponent> issues, String path, CanonicalResource resource, CanonicalResource source) { 185 if (resource != null) { 186 StandardsStatus standardsStatus = ToolingExtensions.getStandardsStatus(resource); 187 if (standardsStatus == StandardsStatus.DEPRECATED) { 188 addToIssues(issues, makeStatusIssue(path, "deprecated", I18nConstants.MSG_DEPRECATED, resource)); 189 } else if (standardsStatus == StandardsStatus.WITHDRAWN) { 190 addToIssues(issues, makeStatusIssue(path, "withdrawn", I18nConstants.MSG_WITHDRAWN, resource)); 191 } else if (resource.getStatus() == PublicationStatus.RETIRED) { 192 addToIssues(issues, makeStatusIssue(path, "retired", I18nConstants.MSG_RETIRED, resource)); 193 } else if (source != null) { 194 if (resource.getExperimental() && !source.getExperimental()) { 195 addToIssues(issues, makeStatusIssue(path, "experimental", I18nConstants.MSG_EXPERIMENTAL, resource)); 196 } else if ((resource.getStatus() == PublicationStatus.DRAFT || standardsStatus == StandardsStatus.DRAFT) 197 && !(source.getStatus() == PublicationStatus.DRAFT || ToolingExtensions.getStandardsStatus(source) == StandardsStatus.DRAFT)) { 198 addToIssues(issues, makeStatusIssue(path, "draft", I18nConstants.MSG_DRAFT, resource)); 199 } 200 } else { 201 if (resource.getExperimental()) { 202 addToIssues(issues, makeStatusIssue(path, "experimental", I18nConstants.MSG_EXPERIMENTAL, resource)); 203 } else if ((resource.getStatus() == PublicationStatus.DRAFT || standardsStatus == StandardsStatus.DRAFT)) { 204 addToIssues(issues, makeStatusIssue(path, "draft", I18nConstants.MSG_DRAFT, resource)); 205 } 206 } 207 } 208 } 209 210 private List<OperationOutcomeIssueComponent> makeStatusIssue(String path, String id, String msg, CanonicalResource resource) { 211 List<OperationOutcomeIssueComponent> iss = makeIssue(IssueSeverity.INFORMATION, IssueType.BUSINESSRULE, null, context.formatMessage(msg, resource.getVersionedUrl(), null, resource.fhirType()), OpIssueCode.StatusCheck, null); 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 ToolingExtensions.setStringExtension(iss.get(0), ToolingExtensions.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 = ToolingExtensions.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 || ToolingExtensions.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}