
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}