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}