
001package org.hl7.fhir.r5.terminologies.expansion; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032 033 034import static org.apache.commons.lang3.StringUtils.isNotBlank; 035 036import java.io.FileNotFoundException; 037import java.io.IOException; 038import java.text.MessageFormat; 039 040/* 041 * Copyright (c) 2011+, HL7, Inc 042 * All rights reserved. 043 * 044 * Redistribution and use in source and binary forms, with or without modification, 045 * are permitted provided that the following conditions are met: 046 * 047 * Redistributions of source code must retain the above copyright notice, this 048 * list of conditions and the following disclaimer. 049 * Redistributions in binary form must reproduce the above copyright notice, 050 * this list of conditions and the following disclaimer in the documentation 051 * and/or other materials provided with the distribution. 052 * Neither the name of HL7 nor the names of its contributors may be used to 053 * endorse or promote products derived from this software without specific 054 * prior written permission. 055 * 056 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 057 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 058 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 059 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 060 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 061 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 062 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 063 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 064 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 065 * POSSIBILITY OF SUCH DAMAGE. 066 * 067 */ 068 069import java.util.ArrayList; 070import java.util.Calendar; 071import java.util.Collection; 072import java.util.GregorianCalendar; 073import java.util.HashSet; 074import java.util.List; 075import java.util.Set; 076 077import org.hl7.fhir.exceptions.FHIRException; 078import org.hl7.fhir.exceptions.FHIRFormatError; 079import org.hl7.fhir.exceptions.NoTerminologyServiceException; 080import org.hl7.fhir.exceptions.TerminologyServiceException; 081import org.hl7.fhir.r5.context.IWorkerContext; 082import org.hl7.fhir.r5.elementmodel.LanguageUtils; 083import org.hl7.fhir.r5.extensions.ExtensionConstants; 084import org.hl7.fhir.r5.extensions.ExtensionsUtils; 085import org.hl7.fhir.r5.model.BooleanType; 086import org.hl7.fhir.r5.model.CanonicalType; 087import org.hl7.fhir.r5.model.CodeSystem; 088import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; 089import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent; 090import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent; 091import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent; 092import org.hl7.fhir.r5.model.CodeType; 093import org.hl7.fhir.r5.model.Coding; 094import org.hl7.fhir.r5.model.DataType; 095import org.hl7.fhir.r5.model.DateTimeType; 096import org.hl7.fhir.r5.model.DecimalType; 097import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode; 098import org.hl7.fhir.r5.model.Enumerations.FilterOperator; 099import org.hl7.fhir.r5.model.OperationOutcome.IssueType; 100import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent; 101import org.hl7.fhir.r5.model.Extension; 102import org.hl7.fhir.r5.model.Factory; 103import org.hl7.fhir.r5.model.IntegerType; 104import org.hl7.fhir.r5.model.PackageInformation; 105import org.hl7.fhir.r5.model.Parameters; 106import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent; 107import org.hl7.fhir.r5.model.PrimitiveType; 108import org.hl7.fhir.r5.model.Resource; 109import org.hl7.fhir.r5.model.StringType; 110import org.hl7.fhir.r5.model.UriType; 111import org.hl7.fhir.r5.model.ValueSet; 112import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent; 113import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceDesignationComponent; 114import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; 115import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent; 116import org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent; 117import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent; 118import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 119import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionParameterComponent; 120import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent; 121import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; 122import org.hl7.fhir.r5.terminologies.ValueSetUtilities; 123import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpander.UnknownValueSetException; 124import org.hl7.fhir.r5.terminologies.providers.CodeSystemProvider; 125import org.hl7.fhir.r5.terminologies.providers.CodeSystemProviderExtension; 126import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext; 127import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext.TerminologyServiceProtectionException; 128import org.hl7.fhir.r5.terminologies.utilities.ValueSetProcessBase.OpIssueCode; 129import org.hl7.fhir.r5.terminologies.utilities.ValueSetProcessBase.TerminologyOperationDetails; 130import org.hl7.fhir.r5.terminologies.validation.VSCheckerException; 131import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass; 132import org.hl7.fhir.r5.terminologies.utilities.ValueSetProcessBase; 133import org.hl7.fhir.r5.utils.ToolingExtensions; 134import org.hl7.fhir.r5.utils.UserDataNames; 135import org.hl7.fhir.r5.utils.client.EFhirClientException; 136import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 137import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 138import org.hl7.fhir.utilities.Utilities; 139import org.hl7.fhir.utilities.i18n.AcceptLanguageHeader; 140import org.hl7.fhir.utilities.i18n.AcceptLanguageHeader.LanguagePreference; 141import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 142import org.hl7.fhir.utilities.i18n.I18nConstants; 143 144@MarkedToMoveToAdjunctPackage 145public class ValueSetExpander extends ValueSetProcessBase { 146 147 148 public class UnknownValueSetException extends FHIRException { 149 150 protected UnknownValueSetException() { 151 super(); 152 } 153 154 protected UnknownValueSetException(String message, Throwable cause) { 155 super(message, cause); 156 } 157 158 protected UnknownValueSetException(String message) { 159 super(message); 160 } 161 162 protected UnknownValueSetException(Throwable cause) { 163 super(cause); 164 } 165 166 } 167 168 public class Token { 169 private String system; 170 private String code; 171 public Token(String system, String code) { 172 super(); 173 this.system = system; 174 this.code = code; 175 } 176 public String getSystem() { 177 return system; 178 } 179 public String getCode() { 180 return code; 181 } 182 public boolean matches(Coding use) { 183 return system.equals(use.getSystem()) && code.equals(use.getCode()); 184 } 185 public boolean matchesLang(String language) { 186 return system.equals("urn:ietf:bcp:47") && code.equals(language); 187 } 188 } 189 190 private static final boolean REPORT_VERSION_ANYWAY = true; 191 192 private static final String VS_EXP_IMPORT_ERROR_TOO_COSTLY = null; 193 194 private ValueSet focus; 195 private List<String> allErrors = new ArrayList<>(); 196 private int maxExpansionSize = 1000; 197 private WorkingContext dwc = new WorkingContext(); 198 199 private boolean checkCodesWhenExpanding; 200 private boolean includeAbstract = true; 201 private boolean debug; 202 private Set<String> sources = new HashSet<>(); 203 204 private AcceptLanguageHeader langs; 205 private List<Token> designations = new ArrayList<>(); 206 207 public ValueSetExpander(IWorkerContext context, TerminologyOperationContext opContext) { 208 super(context, opContext); 209 } 210 211 public ValueSetExpander(IWorkerContext context, TerminologyOperationContext opContext, List<String> allErrors) { 212 super(context, opContext); 213 this.allErrors = allErrors; 214 } 215 216 public void setMaxExpansionSize(int theMaxExpansionSize) { 217 maxExpansionSize = theMaxExpansionSize; 218 } 219 220 private ValueSetExpansionContainsComponent addCode(WorkingContext wc, String system, String code, String display, String dispLang, ValueSetExpansionContainsComponent parent, List<ConceptDefinitionDesignationComponent> designations, Parameters expParams, 221 boolean isAbstract, boolean inactive, List<ValueSet> filters, boolean noInactive, boolean deprecated, List<ValueSetExpansionPropertyComponent> vsProp, 222 List<ConceptPropertyComponent> csProps, CodeSystem cs, List<org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent> expProps, List<Extension> csExtList, List<Extension> vsExtList, ValueSetExpansionComponent exp) throws ETooCostly { 223 opContext.deadCheck("addCode"+code); 224 225 if (filters != null && !filters.isEmpty() && !filterContainsCode(filters, system, code, exp)) 226 return null; 227 if (noInactive && inactive) { 228 return null; 229 } 230 231 ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent(); 232 n.setSystem(system); 233 n.setCode(code); 234 if (isAbstract) { 235 n.setAbstract(true); 236 } 237 if (inactive) { 238 n.setInactive(true); 239 } 240 if (deprecated) { 241 ValueSetUtilities.setDeprecated(vsProp, n); 242 } 243 if (ExtensionsUtils.hasExtension(csExtList, "http://hl7.org/fhir/StructureDefinition/codesystem-label")) { 244 ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#label", "label", ExtensionsUtils.getExtensionValue(csExtList, "http://hl7.org/fhir/StructureDefinition/codesystem-label")); 245 } 246 if (ExtensionsUtils.hasExtension(vsExtList, "http://hl7.org/fhir/StructureDefinition/valueset-label")) { 247 ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#label", "label", ExtensionsUtils.getExtensionValue(vsExtList, "http://hl7.org/fhir/StructureDefinition/valueset-label")); 248 } 249 if (ExtensionsUtils.hasExtension(csExtList, "http://hl7.org/fhir/StructureDefinition/codesystem-conceptOrder")) { 250 ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#order", "order", convertToDecimal(ExtensionsUtils.getExtensionValue(csExtList, "http://hl7.org/fhir/StructureDefinition/codesystem-conceptOrder"))); 251 } 252 if (ExtensionsUtils.hasExtension(vsExtList, "http://hl7.org/fhir/StructureDefinition/valueset-conceptOrder")) { 253 ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#order", "order", convertToDecimal(ExtensionsUtils.getExtensionValue(vsExtList, "http://hl7.org/fhir/StructureDefinition/valueset-conceptOrder"))); 254 } 255 if (ExtensionsUtils.hasExtension(csExtList, "http://hl7.org/fhir/StructureDefinition/itemWeight")) { 256 ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#itemWeight", "weight", ExtensionsUtils.getExtensionValue(csExtList, "http://hl7.org/fhir/StructureDefinition/itemWeight")); 257 } 258 if (ExtensionsUtils.hasExtension(vsExtList, "http://hl7.org/fhir/StructureDefinition/itemWeight")) { 259 ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#itemWeight", "weight", ExtensionsUtils.getExtensionValue(vsExtList, "http://hl7.org/fhir/StructureDefinition/itemWeight")); 260 } 261 ExtensionsUtils.copyExtensions(csExtList, n.getExtension(), 262 "http://hl7.org/fhir/StructureDefinition/coding-sctdescid", 263 "http://hl7.org/fhir/StructureDefinition/rendering-style", 264 "http://hl7.org/fhir/StructureDefinition/rendering-xhtml", 265 "http://hl7.org/fhir/StructureDefinition/codesystem-alternate"); 266 267 ExtensionsUtils.copyExtensions(vsExtList, n.getExtension(), 268 "http://hl7.org/fhir/StructureDefinition/valueset-supplement", 269 "http://hl7.org/fhir/StructureDefinition/valueset-deprecated", 270 "http://hl7.org/fhir/StructureDefinition/valueset-concept-definition", 271 "http://hl7.org/fhir/StructureDefinition/coding-sctdescid", 272 "http://hl7.org/fhir/StructureDefinition/rendering-style", 273 "http://hl7.org/fhir/StructureDefinition/rendering-xhtml"); 274 275 // display and designations 276 ConceptDefinitionDesignationComponent pref = null; 277 if (langs == null) { 278 n.setDisplay(display); 279 } else { 280 if (designations == null) { 281 designations = new ArrayList<>(); 282 } 283 designations.add(new ConceptDefinitionDesignationComponent().setLanguage(dispLang).setValue(display).setUse(new Coding().setSystem("http://terminology.hl7.org/CodeSystem/designation-usage").setCode("display"))); 284 pref = findMatchingDesignation(designations); 285 if (pref != null) { 286 n.setDisplay(pref.getValue()); 287 } 288 } 289 if (!n.hasDisplay() && display != null && langs != null && (langs.matches(dispLang) || Utilities.existsInList(langs.getSource(), "en", "en-US"))) { 290 n.setDisplay(display); 291 } 292 293 if (expParams.getParameterBool("includeDesignations") && designations != null) { 294 295 for (ConceptDefinitionDesignationComponent t : designations) { 296 if (t != pref && (t.hasLanguage() || t.hasUse()) && t.getValue() != null && passesDesignationFilter(t)) { 297 ConceptReferenceDesignationComponent d = n.addDesignation(); 298 if (t.getLanguage() != null) { 299 d.setLanguage(t.getLanguage().trim()); 300 } 301 if (t.getValue() != null) { 302 d.setValue(t.getValue().trim()); 303 } 304 if (t.getUse() != null) { 305 d.setUse(t.getUse()); 306 } 307 for (Extension ext : t.getExtension()) { 308 if (Utilities.existsInList(ext.getUrl(), "http://hl7.org/fhir/StructureDefinition/coding-sctdescid")) { 309 d.addExtension(ext); 310 } 311 } 312 } 313 } 314 } 315 for (ParametersParameterComponent p : expParams.getParameter()) { 316 if ("property".equals(p.getName())) { 317 if (csProps != null && p.hasValue()) { 318 for (ConceptPropertyComponent cp : csProps) { 319 if (p.getValue().primitiveValue().equals(cp.getCode())) { 320 PropertyComponent pd = cs.getProperty(cp.getCode()); 321 String url = pd == null ? null : pd.getUri(); 322 if (url == null) { 323 if ("definition".equals(cp.getCode())) { 324 url = "http://hl7.org/fhir/concept-properties#definition"; 325 } else { 326 // ?? 327 } 328 } 329 ValueSetUtilities.addProperty(focus, n, url, cp.getCode(), cp.getValue()).copyExtensions(cp, "http://hl7.org/fhir/StructureDefinition/alternate-code-use", "http://hl7.org/fhir/StructureDefinition/alternate-code-status"); 330 } 331 } 332 } 333 if (expProps != null && p.hasValue()) { 334 for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent cp : expProps) { 335 if (p.getValue().primitiveValue().equals(cp.getCode())) { 336 String url = null; 337 for (ValueSetExpansionPropertyComponent t : vsProp) { 338 if (t.hasCode() && t.getCode().equals(cp.getCode())) { 339 url = t.getUri(); 340 } 341 } 342 if (url == null) { 343 if ("definition".equals(cp.getCode())) { 344 url = "http://hl7.org/fhir/concept-properties#definition"; 345 } else { 346 // TODO: try looking it up from the code system 347 } 348 } 349 ValueSetUtilities.addProperty(focus, n, url, cp.getCode(), cp.getValue()).copyExtensions(cp, "http://hl7.org/fhir/StructureDefinition/alternate-code-use", "http://hl7.org/fhir/StructureDefinition/alternate-code-status"); 350 } 351 } 352 } 353 } 354 } 355 356 String s = key(n); 357 if (wc.getExcludeKeys().contains(s)) { 358 return null; 359 } else if (wc.getMap().containsKey(s)) { 360 wc.setCanBeHierarchy(false); 361 } else { 362 wc.getCodes().add(n); 363 wc.getMap().put(s, n); 364// if (wc == dwc && wc.getTotal() > maxExpansionSize) { 365// if (wc.getOffset()+wc.getCount() > 0 && wc.getTotal() > wc.getOffset()+wc.getCount()) { 366// wc.setTotal(-1); 367// throw new EFinished(); 368// } 369// throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY, focus.getUrl(), ">" + Integer.toString(maxExpansionSize))); 370// } 371 } 372 if (wc.isCanBeHierarchy() && parent != null) { 373 parent.getContains().add(n); 374 } else if (!wc.getRootMap().containsKey(s)) { 375 wc.getRootMap().put(s, n); 376 wc.getRoots().add(n); 377 } 378 return n; 379 } 380 381 private String findParamValue(List<ValueSetExpansionParameterComponent> list, String name) { 382 for (ValueSetExpansionParameterComponent p : list) { 383 if (name.equals(p.getName())) { 384 return p.getValue().primitiveValue(); 385 } 386 } 387 return null; 388 } 389 390 private DataType convertToDecimal(DataType v) { 391 if (v == null) { 392 return null; 393 } 394 if (v instanceof DecimalType) { 395 return v; 396 } 397 if (v instanceof IntegerType) { 398 return new DecimalType(((IntegerType) v).asStringValue()); 399 } 400 return null; 401 } 402 403 private boolean passesDesignationFilter(ConceptDefinitionDesignationComponent d) { 404 if (designations.isEmpty()) { 405 return true; 406 } 407 for (Token t : designations) { 408 if ((d.hasUse() && t.matches(d.getUse())) || t.matchesLang(d.getLanguage())) { 409 return true; 410 } 411 for (Coding c : d.getAdditionalUse()) { 412 if (t.matches(c)) { 413 return true; 414 } 415 } 416 } 417 return false; 418 } 419 420 private ConceptDefinitionDesignationComponent findMatchingDesignation(List<ConceptDefinitionDesignationComponent> designations) { 421 if (langs == null) { 422 return null; 423 } 424 // we have a list of languages in priority order 425 // we have a list of designations in no order 426 // language exact match is preferred 427 // display is always preferred 428 429 for (LanguagePreference lang : langs.getLangs()) { 430 if (lang.getValue() > 0) { 431 for (ConceptDefinitionDesignationComponent cd : designations) { 432 if (isDisplay(cd, false) && LanguageUtils.langsMatchExact(cd.getLanguage(), lang.getLang())) { 433 return cd; 434 } 435 } 436 for (ConceptDefinitionDesignationComponent cd : designations) { 437 if (isDisplay(cd, false) && LanguageUtils.langsMatch(lang.getLang(), cd.getLanguage())) { 438 return cd; 439 } 440 } 441 for (ConceptDefinitionDesignationComponent cd : designations) { 442 if (isDisplay(cd, false) && LanguageUtils.langsMatch(cd.getLanguage(), lang.getLang())) { 443 return cd; 444 } 445 } 446 for (ConceptDefinitionDesignationComponent cd : designations) { 447 if (isDisplay(cd, true) && LanguageUtils.langsMatchExact(cd.getLanguage(), lang.getLang())) { 448 return cd; 449 } 450 } 451 for (ConceptDefinitionDesignationComponent cd : designations) { 452 if (isDisplay(cd, true) && LanguageUtils.langsMatch(cd.getLanguage(), lang.getLang())) { 453 return cd; 454 } 455 } 456 for (ConceptDefinitionDesignationComponent cd : designations) { 457 if (isDisplay(cd, true) && LanguageUtils.langsMatch(lang.getLang(), cd.getLanguage())) { 458 return cd; 459 } 460 } 461 for (ConceptDefinitionDesignationComponent cd : designations) { 462 if (LanguageUtils.langsMatchExact(cd.getLanguage(), lang.getLang())) { 463 return cd; 464 } 465 } 466 for (ConceptDefinitionDesignationComponent cd : designations) { 467 if (LanguageUtils.langsMatch(cd.getLanguage(), lang.getLang())) { 468 return cd; 469 } 470 } 471 for (ConceptDefinitionDesignationComponent cd : designations) { 472 if (LanguageUtils.langsMatch(lang.getLang(), cd.getLanguage())) { 473 return cd; 474 } 475 } 476 } 477 } 478 return null; 479 } 480 481 private boolean isDisplay(ConceptDefinitionDesignationComponent cd, boolean def) { 482 return (def && !cd.hasUse()) || (cd.hasUse() && cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display")); 483 } 484 485 private boolean filterContainsCode(List<ValueSet> filters, String system, String code, ValueSetExpansionComponent exp) { 486 for (ValueSet vse : filters) { 487 checkCanonical(exp, vse, focus); 488 if (expansionContainsCode(vse.getExpansion().getContains(), system, code)) 489 return true; 490 } 491 return false; 492 } 493 494 private boolean expansionContainsCode(List<ValueSetExpansionContainsComponent> contains, String system, String code) { 495 for (ValueSetExpansionContainsComponent cc : contains) { 496 if (system.equals(cc.getSystem()) && code.equals(cc.getCode())) 497 return true; 498 if (expansionContainsCode(cc.getContains(), system, code)) 499 return true; 500 } 501 return false; 502 } 503 504 private ConceptDefinitionDesignationComponent getMatchingLang(List<ConceptDefinitionDesignationComponent> list, AcceptLanguageHeader langs) { 505 for (ConceptDefinitionDesignationComponent t : list) { 506 if (LanguageUtils.langsMatchExact(langs, t.getLanguage())) { 507 return t; 508 } 509 } 510 for (ConceptDefinitionDesignationComponent t : list) { 511 if (LanguageUtils.langsMatch(langs, t.getLanguage())) { 512 return t; 513 } 514 } 515 return null; 516 } 517 518 private void addCodeAndDescendents(WorkingContext wc, ValueSetExpansionContainsComponent focus, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, ValueSet vsSrc, ValueSetExpansionComponent exp, String langDisplay) throws FHIRException, ETooCostly { 519 opContext.deadCheck("addCodeAndDescendents"); 520 focus.checkNoModifiers("Expansion.contains", "expanding"); 521 ValueSetExpansionContainsComponent np = null; 522 for (String code : getCodesForConcept(focus, expParams)) { 523 ValueSetExpansionContainsComponent t = addCode(wc, focus.getSystem(), code, focus.getDisplay(), langDisplay, parent, 524 convert(focus.getDesignation()), expParams, focus.getAbstract(), focus.getInactive(), filters, noInactive, false, vsProps, makeCSProps(focus.getExtensionString(ToolingExtensions.EXT_DEFINITION), null), null, focus.getProperty(), null, focus.getExtension(), exp); 525 if (np == null) { 526 np = t; 527 } 528 } 529 for (ValueSetExpansionContainsComponent c : focus.getContains()) 530 addCodeAndDescendents(wc, c, np, expParams, filters, noInactive, vsProps, vsSrc, exp, langDisplay); 531 } 532 533 private String getLang(ValueSet vsSrc) { 534 if (vsSrc.hasLanguage()) { 535 return vsSrc.getLanguage(); 536 } 537 for (ValueSetExpansionParameterComponent p : vsSrc.getExpansion().getParameter()) { 538 if ("displayLanguage".equals(p.getName())) { 539 return p.getValue().primitiveValue(); 540 } 541 } 542 return null; 543 } 544 545 private List<ConceptPropertyComponent> makeCSProps(String definition, List<ConceptPropertyComponent> list) { 546 List<ConceptPropertyComponent> res = new ArrayList<>(); 547 if (!Utilities.noString(definition)) { 548 res.add(new ConceptPropertyComponent("definition", new StringType(definition))); 549 } 550 if (list != null) { 551 res.addAll(list); 552 } 553 return res; 554 } 555 556 private List<String> getCodesForConcept(ValueSetExpansionContainsComponent focus, Parameters expParams) { 557 List<String> codes = new ArrayList<>(); 558 codes.add(focus.getCode()); 559 for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent p : focus.getProperty()) { 560 if ("alternateCode".equals(p.getCode()) && (altCodeParams.passes(p.getExtension())) && p.getValue().isPrimitive()) { 561 codes.add(p.getValue().primitiveValue()); 562 } 563 } 564 return codes; 565 } 566 567 private List<ConceptDefinitionDesignationComponent> convert(List<ConceptReferenceDesignationComponent> designations) { 568 List<ConceptDefinitionDesignationComponent> list = new ArrayList<ConceptDefinitionDesignationComponent>(); 569 for (ConceptReferenceDesignationComponent d : designations) { 570 ConceptDefinitionDesignationComponent n = new ConceptDefinitionDesignationComponent(); 571 n.setLanguage(d.getLanguage()); 572 if (d.hasUse()) { 573 n.setUse(d.getUse()); 574 } 575 n.setValue(d.getValue()); 576 list.add(n); 577 } 578 return list; 579 } 580 581 private void addCodeAndDescendents(WorkingContext wc, CodeSystem cs, String system, ConceptDefinitionComponent def, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters, 582 ConceptDefinitionComponent exclusion, ConceptFilter filterFunc, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, List<WorkingContext> otherFilters, ValueSetExpansionComponent exp) throws FHIRException, ETooCostly { 583 opContext.deadCheck("addCodeAndDescendents"); 584 def.checkNoModifiers("Code in Code System", "expanding"); 585 if (exclusion != null) { 586 if (exclusion.getCode().equals(def.getCode())) 587 return; // excluded. 588 } 589 ValueSetExpansionContainsComponent np = null; 590 boolean abs = CodeSystemUtilities.isNotSelectable(cs, def); 591 boolean inc = CodeSystemUtilities.isInactive(cs, def); 592 boolean dep = CodeSystemUtilities.isDeprecated(cs, def, false); 593 if ((includeAbstract || !abs) && filterFunc.includeConcept(cs, def) && passesOtherFilters(otherFilters, cs, def.getCode())) { 594 for (String code : getCodesForConcept(def, expParams)) { 595 ValueSetExpansionContainsComponent t = addCode(wc, system, code, def.getDisplay(), cs.getLanguage(), parent, def.getDesignation(), expParams, abs, inc, filters, noInactive, dep, vsProps, makeCSProps(def.getDefinition(), def.getProperty()), cs, null, def.getExtension(), null, exp); 596 if (np == null) { 597 np = t; 598 } 599 } 600 } 601 for (ConceptDefinitionComponent c : def.getConcept()) { 602 addCodeAndDescendents(wc, cs, system, c, np, expParams, filters, exclusion, filterFunc, noInactive, vsProps, otherFilters, exp); 603 } 604 if (def.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) { 605 List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) def.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK); 606 for (ConceptDefinitionComponent c : children) 607 addCodeAndDescendents(wc, cs, system, c, np, expParams, filters, exclusion, filterFunc, noInactive, vsProps, otherFilters, exp); 608 } 609 } 610 611 612 private void excludeCodeAndDescendents(WorkingContext wc, CodeSystem cs, String system, ConceptDefinitionComponent def, Parameters expParams, List<ValueSet> filters, 613 ConceptDefinitionComponent exclusion, ConceptFilter filterFunc, List<WorkingContext> otherFilters, ValueSetExpansionComponent exp) throws FHIRException, ETooCostly { 614 opContext.deadCheck("excludeCodeAndDescendents"); 615 def.checkNoModifiers("Code in Code System", "expanding"); 616 if (exclusion != null) { 617 if (exclusion.getCode().equals(def.getCode())) 618 return; // excluded. 619 } 620 boolean abs = CodeSystemUtilities.isNotSelectable(cs, def); 621 if ((includeAbstract || !abs) && filterFunc.includeConcept(cs, def) && passesOtherFilters(otherFilters, cs, def.getCode())) { 622 for (String code : getCodesForConcept(def, expParams)) { 623 if (!(filters != null && !filters.isEmpty() && !filterContainsCode(filters, system, code, exp))) 624 excludeCode(wc, system, code); 625 } 626 } 627 for (ConceptDefinitionComponent c : def.getConcept()) { 628 excludeCodeAndDescendents(wc, cs, system, c, expParams, filters, exclusion, filterFunc, otherFilters, exp); 629 } 630 if (def.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) { 631 List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) def.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK); 632 for (ConceptDefinitionComponent c : children) 633 excludeCodeAndDescendents(wc, cs, system, c, expParams, filters, exclusion, filterFunc, otherFilters, exp); 634 } 635 } 636 637 638 private List<String> getCodesForConcept(ConceptDefinitionComponent focus, Parameters expParams) { 639 List<String> codes = new ArrayList<>(); 640 codes.add(focus.getCode()); 641 for (ConceptPropertyComponent p : focus.getProperty()) { 642 if ("alternateCode".equals(p.getCode()) && (altCodeParams.passes(p.getExtension())) && p.getValue().isPrimitive()) { 643 codes.add(p.getValue().primitiveValue()); 644 } 645 } 646 return codes; 647 } 648 649 private static boolean hasUse(ConceptPropertyComponent p, List<String> uses) { 650 for (Extension ext : p.getExtensionsByUrl(ToolingExtensions.EXT_CS_ALTERNATE_USE)) { 651 if (ext.hasValueCoding() && Utilities.existsInList(ext.getValueCoding().getCode(), uses)) { 652 return true; 653 } 654 } 655 return false; 656 } 657 658 659 660 private void addCodes(ValueSetExpansionComponent expand, List<ValueSetExpansionParameterComponent> params, Parameters expParams, List<ValueSet> filters, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, ValueSet vsSrc, ValueSetExpansionComponent exp) throws ETooCostly, FHIRException { 661 if (expand != null) { 662 if (expand.getContains().size() > maxExpansionSize) 663 throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY, vsSrc.getUrl(), ">" + Integer.toString(expand.getContains().size()))); 664 for (ValueSetExpansionParameterComponent p : expand.getParameter()) { 665 if (!existsInParams(params, p.getName(), p.getValue())) 666 params.add(p); 667 } 668 669 copyImportContains(expand.getContains(), null, expParams, filters, noInactive, vsProps, vsSrc, exp); 670 } 671 } 672 673 private void excludeCode(WorkingContext wc, String theSystem, String theCode) { 674 ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent(); 675 n.setSystem(theSystem); 676 n.setCode(theCode); 677 String s = key(n); 678 wc.getExcludeKeys().add(s); 679 } 680 681 private void excludeCodes(WorkingContext wc, ConceptSetComponent exc, Parameters expParams, ValueSetExpansionComponent exp, ValueSet vs, String vspath) throws FHIRException, FileNotFoundException, ETooCostly, IOException { 682 opContext.deadCheck("excludeCodes"); 683 exc.checkNoModifiers("Compose.exclude", "expanding"); 684 if (exc.hasSystem() && exc.getConcept().size() == 0 && exc.getFilter().size() == 0) { 685 wc.getExcludeSystems().add(exc.getSystem()); 686 } 687 688 for (UriType imp : exc.getValueSet()) { 689 excludeCodes(wc, importValueSetForExclude(wc, imp.getValue(), exp, expParams, false, vs).getExpansion()); 690 } 691 692 if (exc.hasSystem()) { 693 String sv = exc.getSystem()+(exc.hasVersion() ? "#"+exc.getVersion(): ""); 694 if (dwc.getCountIncompleteSystems().contains(sv)) { 695 dwc.setNoTotal(true); 696 } 697 698 CodeSystem cs = context.fetchSupplementedCodeSystem(exc.getSystem()); 699 if ((cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE) && context.supportsSystem(exc.getSystem(), opContext.getOptions().getFhirVersion())) { 700 ValueSetExpansionOutcome vse = context.expandVS(new TerminologyOperationDetails(requiredSupplements), exc, false, false); 701 ValueSet valueset = vse.getValueset(); 702 if (valueset.hasUserData(UserDataNames.VS_EXPANSION_SOURCE)) { 703 sources.add(valueset.getUserString(UserDataNames.VS_EXPANSION_SOURCE)); 704 } 705 if (valueset == null) 706 throw failTSE("Error Expanding ValueSet: "+vse.getError()); 707 excludeCodes(wc, valueset.getExpansion()); 708 return; 709 } 710 711 for (ConceptReferenceComponent c : exc.getConcept()) { 712 excludeCode(wc, exc.getSystem(), c.getCode()); 713 } 714 715 if (exc.getFilter().size() > 0) { 716 if (cs.getContent() == CodeSystemContentMode.FRAGMENT) { 717 addFragmentWarning(exp, cs); 718 } 719 List<WorkingContext> filters = new ArrayList<>(); 720 for (int i = 1; i < exc.getFilter().size(); i++) { 721 WorkingContext wc1 = new WorkingContext(); 722 filters.add(wc1); 723 processFilter(exc, exp, expParams, null, cs, false, exc.getFilter().get(i), wc1, null, true, vspath+".filter["+i+"]"); 724 } 725 ConceptSetFilterComponent fc = exc.getFilter().get(0); 726 WorkingContext wc1 = dwc; 727 processFilter(exc, exp, expParams, null, cs, false, fc, wc1, filters, true, vspath+".filter[0]"); 728 } 729 } 730 } 731 732 private void excludeCodes(WorkingContext wc, ValueSetExpansionComponent expand) { 733 opContext.deadCheck("excludeCodes"); 734 for (ValueSetExpansionContainsComponent c : expand.getContains()) { 735 excludeCode(wc, c.getSystem(), c.getCode()); 736 } 737 } 738 739 private boolean existsInParams(List<ValueSetExpansionParameterComponent> params, String name, DataType value) { 740 for (ValueSetExpansionParameterComponent p : params) { 741 if (p.getName().equals(name) && PrimitiveType.compareDeep(p.getValue(), value, false)) { 742 return true; 743 } 744 } 745 return false; 746 } 747 748 public ValueSetExpansionOutcome expand(ValueSet source, Parameters expParams) { 749 allErrors.clear(); 750 try { 751 opContext.seeContext(source.getVersionedUrl()); 752 753 return expandInternal(source, expParams); 754 } catch (NoTerminologyServiceException e) { 755 // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set 756 // that might fail too, but it might not, later. 757 return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.NOSERVICE, allErrors, false); 758 } catch (CodeSystemProviderExtension e) { 759 // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set 760 // that might fail too, but it might not, later. 761 return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.INTERNAL_ERROR, allErrors, false); 762 } catch (TerminologyServiceProtectionException e) { 763 if (opContext.isOriginal()) { 764 return new ValueSetExpansionOutcome(e.getMessage(), e.getError(), allErrors, false); 765 } else { 766 throw e; 767 } 768 } catch (ETooCostly e) { 769 return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.TOO_COSTLY, allErrors, false); 770 } catch (UnknownValueSetException e) { 771 return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.VALUESET_UNKNOWN, allErrors, false); 772 } catch (VSCheckerException e) { 773 return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.UNKNOWN, allErrors, e.getIssues()); 774 } catch (Exception e) { 775 if (debug) { 776 e.printStackTrace(); 777 } 778 return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.UNKNOWN, allErrors, e instanceof EFhirClientException || e instanceof TerminologyServiceException); 779 } 780 } 781 782 public ValueSetExpansionOutcome expandInternal(ValueSet source, Parameters expParams) throws FHIRException, FileNotFoundException, ETooCostly, IOException, CodeSystemProviderExtension { 783 if (expParams == null) 784 expParams = makeDefaultExpansion(); 785 altCodeParams.seeParameters(expParams); 786 altCodeParams.seeValueSet(source); 787 source.checkNoModifiers("ValueSet", "expanding"); 788 focus = source.copy(); 789 focus.setIdBase(null); 790 focus.setExpansion(new ValueSet.ValueSetExpansionComponent()); 791 focus.getExpansion().setTimestampElement(DateTimeType.now()); 792 focus.getExpansion().setIdentifier(Factory.createUUID()); 793 checkCanonical(focus.getExpansion(), focus, focus); 794 for (Extension ext : focus.getCompose().getExtensionsByUrl("http://hl7.org/fhir/tools/StructureDefinition/valueset-expansion-parameter")) { 795 processParameter(ext.getExtensionString("name"), ext.getExtensionByUrl("value").getValue()); 796 } 797 for (ParametersParameterComponent p : expParams.getParameter()) { 798 processParameter(p.getName(), p.getValue()); 799 } 800 for (Extension s : focus.getExtensionsByUrl(ExtensionConstants.EXT_VSSUPPLEMENT)) { 801 requiredSupplements.add(s.getValue().primitiveValue()); 802 } 803 if (langs == null && focus.hasLanguage()) { 804 langs = new AcceptLanguageHeader(focus.getLanguage(), true); 805 } 806 807 try { 808 if (source.hasCompose()) { 809 // ExtensionsUtils.stripExtensions(focus.getCompose()); - disabled 23/05/2023 GDG - why was this ever thought to be a good idea? 810 handleCompose(source.getCompose(), focus.getExpansion(), expParams, source.getUrl(), focus.getExpansion().getExtension(), source); 811 } 812 } catch (EFinished e) { 813 // nothing - we intended to trap this here 814 } 815 816 if (dwc.getCount() > maxExpansionSize && dwc.getOffsetParam() + dwc.getCountParam() == 0) { 817 if (dwc.isNoTotal()) { 818 throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY, focus.getVersionedUrl(), ">" + MessageFormat.format("{0,number,#}", maxExpansionSize))); 819 } else { 820 throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY_COUNT, focus.getVersionedUrl(), ">" + MessageFormat.format("{0,number,#}", maxExpansionSize), MessageFormat.format("{0,number,#}", dwc.getCount()))); 821 } 822 } else if (dwc.isCanBeHierarchy() && ((dwc.getCountParam() == 0) || dwc.getCountParam() > dwc.getCodes().size())) { 823 for (ValueSetExpansionContainsComponent c : dwc.getRoots()) { 824 focus.getExpansion().getContains().add(c); 825 } 826 } else { 827 int i = 0; 828 int cc = 0; 829 for (ValueSetExpansionContainsComponent c : dwc.getCodes()) { 830 c.getContains().clear(); // make sure any hierarchy is wiped 831 if (dwc.getMap().containsKey(key(c)) && (includeAbstract || !c.getAbstract())) { // we may have added abstract codes earlier while we still thought it might be heirarchical, but later we gave up, so now ignore them 832 if (dwc.getOffsetParam() == 0 || i >= dwc.getOffsetParam()) { 833 focus.getExpansion().getContains().add(c); 834 cc++; 835 if (cc == dwc.getCountParam()) { 836 break; 837 } 838 } 839 i++; 840 } 841 } 842 } 843 844 if (dwc.hasOffsetParam()) { 845 focus.getExpansion().setOffset(dwc.getOffsetParam()); 846 } 847 if (!dwc.isNoTotal()) { 848 focus.getExpansion().setTotal(dwc.getStatedTotal()); 849 } 850 if (!requiredSupplements.isEmpty()) { 851 return new ValueSetExpansionOutcome(context.formatMessagePlural(requiredSupplements.size(), I18nConstants.VALUESET_SUPPLEMENT_MISSING, CommaSeparatedStringBuilder.build(requiredSupplements)), TerminologyServiceErrorClass.BUSINESS_RULE, allErrors, false); 852 } 853 if (!expParams.hasParameter("includeDefinition") || !expParams.getParameterBool("includeDefinition")) { 854 focus.setCompose(null); 855 focus.getExtension().clear(); 856 focus.setPublisher(null); 857 focus.setDescription(null); 858 focus.setPurpose(null); 859 focus.getContact().clear(); 860 focus.setCopyright(null); 861 focus.setText(null); 862 } 863 return new ValueSetExpansionOutcome(focus); 864 } 865 866 private void processParameter(String name, DataType value) { 867 if (Utilities.existsInList(name, "includeDesignations", "excludeNested", "activeOnly", "offset", "count")) { 868 focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name)); 869 focus.getExpansion().addParameter().setName(name).setValue(value); 870 } 871 if ("displayLanguage".equals(name)) { 872 this.langs = new AcceptLanguageHeader(value.primitiveValue(), true); 873 focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name)); 874 focus.getExpansion().addParameter().setName(name).setValue(new CodeType(value.primitiveValue())); 875 } 876 if ("designation".equals(name)) { 877 String[] v = value.primitiveValue().split("\\|"); 878 if (v.length != 2 || !Utilities.isAbsoluteUrl(v[0]) || Utilities.noString(v[1])) { 879 throw new NoTerminologyServiceException("Unable to understand designation parameter "+value.primitiveValue()); 880 } 881 this.designations.add(new Token(v[0], v[1])); 882 focus.getExpansion().addParameter().setName(name).setValue(new StringType(value.primitiveValue())); 883 } 884 if ("offset".equals(name) && value instanceof IntegerType) { 885 focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name)); 886 focus.getExpansion().addParameter().setName(name).setValue(value); 887 dwc.setOffsetParam(((IntegerType) value).getValue()); 888 if (dwc.getOffsetParam() < 0) { 889 dwc.setOffsetParam(0); 890 } 891 } 892 if ("count".equals(name)) { 893 focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name)); 894 focus.getExpansion().addParameter().setName(name).setValue(value); 895 dwc.setCountParam(((IntegerType) value).getValue()); 896 if (dwc.getCountParam() < 0) { 897 dwc.setCountParam(0); 898 } 899 } 900 } 901 902 public ValueSetExpansionOutcome doExpand(ValueSet source, Parameters expParams) throws FHIRException, ETooCostly, FileNotFoundException, IOException, CodeSystemProviderExtension { 903 if (expParams == null) 904 expParams = makeDefaultExpansion(); 905 altCodeParams.seeParameters(expParams); 906 altCodeParams.seeValueSet(source); 907 source.checkNoModifiers("ValueSet", "expanding"); 908 focus = source.copy(); 909 focus.setIdBase(null); 910 focus.setExpansion(new ValueSet.ValueSetExpansionComponent()); 911 focus.getExpansion().setTimestampElement(DateTimeType.now()); 912 focus.getExpansion().setIdentifier(Factory.createUUID()); 913 checkCanonical(focus.getExpansion(), focus, focus); 914 for (Extension ext : focus.getCompose().getExtensionsByUrl("http://hl7.org/fhir/tools/StructureDefinition/valueset-expansion-parameter")) { 915 processParameter(ext.getExtensionString("name"), ext.getExtensionByUrl("value").getValue()); 916 } 917 for (ParametersParameterComponent p : expParams.getParameter()) { 918 processParameter(p.getName(), p.getValue()); 919 } 920 for (Extension s : focus.getExtensionsByUrl(ExtensionConstants.EXT_VSSUPPLEMENT)) { 921 requiredSupplements.add(s.getValue().primitiveValue()); 922 } 923 if (langs == null && focus.hasLanguage()) { 924 langs = new AcceptLanguageHeader(focus.getLanguage(), true); 925 } 926 927 try { 928 if (source.hasCompose()) { 929// ExtensionsUtils.stripExtensions(focus.getCompose()); - disabled 23/05/2023 GDG - why was this ever thought to be a good idea? 930 handleCompose(source.getCompose(), focus.getExpansion(), expParams, source.getUrl(), focus.getExpansion().getExtension(), source); 931 } 932 } catch (EFinished e) { 933 // nothing - we intended to trap this here 934 } 935 936 if (dwc.getCount() > maxExpansionSize && dwc.getOffsetParam() + dwc.getCountParam() == 0) { 937 if (dwc.isNoTotal()) { 938 throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY, focus.getVersionedUrl(), ">" + MessageFormat.format("{0,number,#}", maxExpansionSize))); 939 } else { 940 throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY_COUNT, focus.getVersionedUrl(), ">" + MessageFormat.format("{0,number,#}", maxExpansionSize), MessageFormat.format("{0,number,#}", dwc.getCount()))); 941 } 942 } else if (dwc.isCanBeHierarchy() && ((dwc.getCountParam() == 0) || dwc.getCountParam() > dwc.getCodes().size())) { 943 for (ValueSetExpansionContainsComponent c : dwc.getRoots()) { 944 focus.getExpansion().getContains().add(c); 945 } 946 } else { 947 int i = 0; 948 int cc = 0; 949 for (ValueSetExpansionContainsComponent c : dwc.getCodes()) { 950 c.getContains().clear(); // make sure any hierarchy is wiped 951 if (dwc.getMap().containsKey(key(c)) && (includeAbstract || !c.getAbstract())) { // we may have added abstract codes earlier while we still thought it might be heirarchical, but later we gave up, so now ignore them 952 if (dwc.getOffsetParam() == 0 || i >= dwc.getOffsetParam()) { 953 focus.getExpansion().getContains().add(c); 954 cc++; 955 if (cc == dwc.getCountParam()) { 956 break; 957 } 958 } 959 i++; 960 } 961 } 962 } 963 964 if (dwc.hasOffsetParam()) { 965 focus.getExpansion().setOffset(dwc.getOffsetParam()); 966 } 967 if (!dwc.isNoTotal()) { 968 focus.getExpansion().setTotal(dwc.getStatedTotal()); 969 } 970 if (!requiredSupplements.isEmpty()) { 971 return new ValueSetExpansionOutcome(context.formatMessagePlural(requiredSupplements.size(), I18nConstants.VALUESET_SUPPLEMENT_MISSING, CommaSeparatedStringBuilder.build(requiredSupplements)), TerminologyServiceErrorClass.BUSINESS_RULE, allErrors, false); 972 } 973 if (!expParams.hasParameter("includeDefinition") || !expParams.getParameterBool("includeDefinition")) { 974 focus.setCompose(null); 975 focus.getExtension().clear(); 976 focus.setPublisher(null); 977 focus.setDescription(null); 978 focus.setPurpose(null); 979 focus.getContact().clear(); 980 focus.setCopyright(null); 981 focus.setText(null); 982 } 983 return new ValueSetExpansionOutcome(focus); 984 } 985 986 987 private Parameters makeDefaultExpansion() { 988 Parameters res = new Parameters(); 989 res.addParameter("excludeNested", true); 990 res.addParameter("includeDesignations", false); 991 return res; 992 } 993 994 private ConceptDefinitionComponent getConceptForCode(List<ConceptDefinitionComponent> clist, String code) { 995 for (ConceptDefinitionComponent c : clist) { 996 if (code.equals(c.getCode())) 997 return c; 998 ConceptDefinitionComponent v = getConceptForCode(c.getConcept(), code); 999 if (v != null) 1000 return v; 1001 } 1002 return null; 1003 } 1004 1005 private void handleCompose(ValueSetComposeComponent compose, ValueSetExpansionComponent exp, Parameters expParams, String ctxt, List<Extension> extensions, ValueSet valueSet) 1006 throws ETooCostly, FileNotFoundException, IOException, FHIRException, CodeSystemProviderExtension { 1007 compose.checkNoModifiers("ValueSet.compose", "expanding"); 1008 String vspath = "ValueSet["+valueSet.getVersionedUrl()+"].compose"; 1009 1010 // Exclude comes first because we build up a map of things to exclude 1011 int c = 0; 1012 for (ConceptSetComponent inc : compose.getExclude()) { 1013 excludeCodes(dwc, inc, expParams, exp, valueSet, vspath+".include["+c+"]"); 1014 c++; 1015 } 1016 dwc.setCanBeHierarchy(!expParams.getParameterBool("excludeNested") && dwc.getExcludeKeys().isEmpty() && dwc.getExcludeSystems().isEmpty() && dwc.getOffsetParam() == 0); 1017 includeAbstract = !expParams.getParameterBool("excludeNotForUI"); 1018 boolean first = true; 1019 c = 0; 1020 for (ConceptSetComponent inc : compose.getInclude()) { 1021 if (first == true) 1022 first = false; 1023 else 1024 dwc.setCanBeHierarchy(false); 1025 includeCodes(inc, exp, expParams, dwc.isCanBeHierarchy(), compose.hasInactive() ? !compose.getInactive() : checkNoInActiveFromParam(expParams), extensions, valueSet, vspath+".include["+c+"]"); 1026 c++; 1027 } 1028 } 1029 1030 /** 1031 * returns true if activeOnly = true 1032 * @param expParams 1033 * @return 1034 */ 1035 private boolean checkNoInActiveFromParam(Parameters expParams) { 1036 for (ParametersParameterComponent p : expParams.getParameter()) { 1037 if (p.getName().equals("activeOnly")) { 1038 return p.getValueBooleanType().getValue(); 1039 } 1040 } 1041 return false; 1042 } 1043 1044 private ValueSet importValueSet(WorkingContext wc, String value, ValueSetExpansionComponent exp, Parameters expParams, boolean noInactive, ValueSet valueSet) throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException, FHIRFormatError { 1045 if (value == null) 1046 throw fail(I18nConstants.VS_EXP_IMPORT_NULL, true); 1047 String url = getCu().pinValueSet(value, expParams); 1048 ValueSet vs = context.findTxResource(ValueSet.class, url, valueSet); 1049 if (vs == null) { 1050 boolean pinned = !url.equals(value); 1051 String ver = pinned ? url.substring(value.length()+1) : null; 1052 if (context.fetchResource(CodeSystem.class, url, valueSet) != null) { 1053 throw failNotFound(pinned ? I18nConstants.VS_EXP_IMPORT_CS_PINNED : I18nConstants.VS_EXP_IMPORT_CS, true, value, ver); 1054 } else { 1055 throw failNotFound(pinned ? I18nConstants.VS_EXP_IMPORT_UNK_PINNED : I18nConstants.VS_EXP_IMPORT_UNK, true, value, ver); 1056 } 1057 } 1058 checkCanonical(exp, vs, focus); 1059 if (noInactive) { 1060 expParams = expParams.copy(); 1061 expParams.addParameter("activeOnly", true); 1062 } 1063 ValueSetExpander expander = new ValueSetExpander(context, opContext.copy(), allErrors); 1064 ValueSetExpansionOutcome vso = expander.expand(vs, expParams); 1065 if (vso.getError() != null) { 1066 addErrors(vso.getAllErrors()); 1067 if (vso.getErrorClass() == TerminologyServiceErrorClass.VALUESET_UNKNOWN) { 1068 throw failUnk(I18nConstants.VS_EXP_IMPORT_ERROR, true, vs.getUrl(), vso.getError()); 1069 } else { 1070 throw fail(I18nConstants.VS_EXP_IMPORT_ERROR, true, vs.getUrl(), vso.getError()); 1071 } 1072 } else if (vso.getValueset() == null) { 1073 throw fail(I18nConstants.VS_EXP_IMPORT_FAIL, true, vs.getUrl()); 1074 } 1075 sources.addAll(expander.sources); 1076 if (vs.hasVersion() || REPORT_VERSION_ANYWAY) { 1077 UriType u = new UriType(vs.getUrl() + (vs.hasVersion() ? "|"+vs.getVersion() : "")); 1078 if (!existsInParams(exp.getParameter(), "used-valueset", u)) 1079 exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-valueset").setValue(u)); 1080 } 1081 ValueSetExpansionComponent evs = vso.getValueset().getExpansion(); 1082 for (Extension ex : evs.getExtension()) { 1083 if (ex.getUrl().equals(ToolingExtensions.EXT_EXP_TOOCOSTLY)) { 1084 if (ex.getValue() instanceof BooleanType) { 1085 exp.getExtension().add(new Extension(ToolingExtensions.EXT_EXP_TOOCOSTLY).setValue(new CanonicalType(value))); 1086 } else { 1087 exp.getExtension().add(ex); 1088 } 1089 } 1090 } 1091 if (!evs.hasTotal()) { 1092 // because if there's no total, we can't know if we got everything 1093 dwc.setNoTotal(true); 1094 } 1095 for (ValueSetExpansionParameterComponent p : evs.getParameter()) { 1096 if (!existsInParams(exp.getParameter(), p.getName(), p.getValue())) 1097 exp.getParameter().add(p); 1098 } 1099 if (isValueSetUnionImports(valueSet)) { 1100 copyExpansion(wc, evs.getContains()); 1101 } 1102 wc.setCanBeHierarchy(false); // if we're importing a value set, we have to be combining, so we won't try for a hierarchy 1103 return vso.getValueset(); 1104 } 1105 1106 1107 1108 private ValueSet importValueSetForExclude(WorkingContext wc, String value, ValueSetExpansionComponent exp, Parameters expParams, boolean noInactive, ValueSet valueSet) throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException, FHIRFormatError { 1109 if (value == null) 1110 throw fail(I18nConstants.VS_EXP_IMPORT_NULL_X, true); 1111 String url = getCu().pinValueSet(value, expParams); 1112 ValueSet vs = context.findTxResource(ValueSet.class, url, valueSet); 1113 if (vs == null) { 1114 boolean pinned = !url.equals(value); 1115 String ver = pinned ? url.substring(value.length()+1) : null; 1116 if (context.fetchResource(CodeSystem.class, url, valueSet) != null) { 1117 throw fail(pinned ? I18nConstants.VS_EXP_IMPORT_CS_PINNED_X : I18nConstants.VS_EXP_IMPORT_CS_X, true, value, ver); 1118 } else { 1119 throw fail(pinned ? I18nConstants.VS_EXP_IMPORT_UNK_PINNED_X : I18nConstants.VS_EXP_IMPORT_UNK_X, true, value, ver); 1120 } 1121 } 1122 checkCanonical(exp, vs, focus); 1123 if (noInactive) { 1124 expParams = expParams.copy(); 1125 expParams.addParameter("activeOnly", true); 1126 } 1127 ValueSetExpander expander = new ValueSetExpander(context, opContext.copy(), allErrors); 1128 ValueSetExpansionOutcome vso = expander.expand(vs, expParams); 1129 sources.addAll(expander.sources); 1130 if (vso.getError() != null) { 1131 addErrors(vso.getAllErrors()); 1132 throw fail(I18nConstants.VS_EXP_IMPORT_ERROR_X, true, vs.getUrl(), vso.getError()); 1133 } else if (vso.getValueset() == null) { 1134 throw fail(I18nConstants.VS_EXP_IMPORT_FAIL_X, true, vs.getUrl()); 1135 } 1136 1137 if (vs.hasVersion() || REPORT_VERSION_ANYWAY) { 1138 UriType u = new UriType(vs.getUrl() + (vs.hasVersion() ? "|"+vs.getVersion() : "")); 1139 if (!existsInParams(exp.getParameter(), "used-valueset", u)) 1140 exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-valueset").setValue(u)); 1141 } 1142 for (Extension ex : vso.getValueset().getExpansion().getExtension()) { 1143 if (ex.getUrl().equals(ToolingExtensions.EXT_EXP_TOOCOSTLY)) { 1144 throw fail(VS_EXP_IMPORT_ERROR_TOO_COSTLY, true, vs.getUrl()); 1145 } 1146 } 1147 return vso.getValueset(); 1148 } 1149 1150 protected boolean isValueSetUnionImports(ValueSet valueSet) { 1151 PackageInformation p = valueSet.getSourcePackage(); 1152 if (p != null) { 1153 return p.getDate().before(new GregorianCalendar(2022, Calendar.MARCH, 31).getTime()); 1154 } else { 1155 return false; 1156 } 1157 } 1158 1159 public void copyExpansion(WorkingContext wc,List<ValueSetExpansionContainsComponent> list) { 1160 opContext.deadCheck("copyExpansion"); 1161 for (ValueSetExpansionContainsComponent cc : list) { 1162 ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent(); 1163 n.setSystem(cc.getSystem()); 1164 n.setCode(cc.getCode()); 1165 n.setAbstract(cc.getAbstract()); 1166 n.setInactive(cc.getInactive()); 1167 n.setDisplay(cc.getDisplay()); 1168 n.getDesignation().addAll(cc.getDesignation()); 1169 1170 String s = key(n); 1171 if (!wc.getMap().containsKey(s) && !wc.getExcludeKeys().contains(s)) { 1172 wc.getCodes().add(n); 1173 wc.getMap().put(s, n); 1174 } 1175 copyExpansion(wc, cc.getContains()); 1176 } 1177 } 1178 1179 private void addErrors(List<String> errs) { 1180 for (String s : errs) { 1181 if (!allErrors.contains(s)) { 1182 allErrors.add(s); 1183 } 1184 } 1185 } 1186 1187 private int copyImportContains(List<ValueSetExpansionContainsComponent> list, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filter, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, ValueSet vsSrc, ValueSetExpansionComponent exp) throws FHIRException, ETooCostly { 1188 int count = 0; 1189 opContext.deadCheck("copyImportContains"); 1190 1191 String lang = vsSrc.getLanguage(); 1192 if (lang == null) { 1193 lang = findParamValue(vsSrc.getExpansion().getParameter(), "displayLanguage"); 1194 } 1195 for (ValueSetExpansionContainsComponent c : list) { 1196 c.checkNoModifiers("Imported Expansion in Code System", "expanding"); 1197 ValueSetExpansionContainsComponent np = addCode(dwc, c.getSystem(), c.getCode(), c.getDisplay(), lang, parent, translateDesignations(c), expParams, c.getAbstract(), c.getInactive(), 1198 filter, noInactive, false, vsProps, makeCSProps(c.getExtensionString(ToolingExtensions.EXT_DEFINITION), null), null, c.getProperty(), null, c.getExtension(), exp); 1199 if (np != null) { 1200 count++; 1201 } 1202 count = count + copyImportContains(c.getContains(), np, expParams, filter, noInactive, vsProps, vsSrc, exp); 1203 } 1204 return count; 1205 } 1206 1207 private List<ConceptDefinitionDesignationComponent> translateDesignations(ValueSetExpansionContainsComponent c) { 1208 if (!c.hasDesignation()) { 1209 return null; 1210 } 1211 List<ConceptDefinitionDesignationComponent> list = new ArrayList<>(); 1212 for (ConceptReferenceDesignationComponent d : c.getDesignation()) { 1213 ConceptDefinitionDesignationComponent d2 = new ConceptDefinitionDesignationComponent(); 1214 d2.setLanguage(d.getLanguage()); 1215 d2.setUse(d.getUse()); 1216 d2.setAdditionalUse(d.getAdditionalUse()); 1217 d2.setValue(d.getValue()); 1218 list.add(d2); 1219 } 1220 return list; 1221 } 1222 1223 private void includeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, boolean heirarchical, boolean noInactive, List<Extension> extensions, ValueSet valueSet, String vspath) throws ETooCostly, FileNotFoundException, IOException, FHIRException, CodeSystemProviderExtension { 1224 opContext.deadCheck("includeCodes"); 1225 inc.checkNoModifiers("Compose.include", "expanding"); 1226 List<ValueSet> imports = new ArrayList<ValueSet>(); 1227 for (CanonicalType imp : inc.getValueSet()) { 1228 imports.add(importValueSet(dwc, imp.getValue(), exp, expParams, noInactive, valueSet)); 1229 } 1230 1231 if (!inc.hasSystem()) { 1232 if (imports.isEmpty()) // though this is not supposed to be the case 1233 return; 1234 ValueSet base = imports.get(0); 1235 checkCanonical(exp, base, focus); 1236 imports.remove(0); 1237 base.checkNoModifiers("Imported ValueSet", "expanding"); 1238 copyImportContains(base.getExpansion().getContains(), null, expParams, imports, noInactive, base.getExpansion().getProperty(), base, exp); 1239 } else { 1240 String sv = inc.getSystem()+(inc.hasVersion() ? "#"+inc.getVersion(): ""); 1241 if (dwc.getCountIncompleteSystems().contains(sv)) { 1242 dwc.setNoTotal(true); 1243 } 1244 CodeSystem cs = context.fetchSupplementedCodeSystem(inc.getSystem()); 1245 if (ValueSetUtilities.isServerSide(inc.getSystem()) || (cs == null || (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT))) { 1246 doServerIncludeCodes(inc, heirarchical, exp, imports, expParams, extensions, noInactive, valueSet.getExpansion().getProperty()); 1247 } else { 1248 if (cs.hasUserData(UserDataNames.tx_known_supplements)) { 1249 for (String s : cs.getUserString(UserDataNames.tx_known_supplements).split("\\,")) { 1250 requiredSupplements.remove(s); 1251 } 1252 } 1253 doInternalIncludeCodes(inc, exp, expParams, imports, cs, noInactive, valueSet, vspath); 1254 } 1255 } 1256 } 1257 1258 private void doServerIncludeCodes(ConceptSetComponent inc, boolean heirarchical, ValueSetExpansionComponent exp, List<ValueSet> imports, Parameters expParams, List<Extension> extensions, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps) throws FHIRException, CodeSystemProviderExtension, ETooCostly { 1259 opContext.deadCheck("doServerIncludeCodes"); 1260 CodeSystemProvider csp = CodeSystemProvider.factory(inc.getSystem()); 1261 if (csp != null) { 1262 csp.includeCodes(inc, heirarchical, exp, imports, expParams, extensions, noInactive, vsProps); 1263 return; 1264 } 1265 1266 ValueSetExpansionOutcome vso = context.expandVS(new TerminologyOperationDetails(requiredSupplements), inc, heirarchical, noInactive); 1267 if (vso.getError() != null) { 1268 throw failTSE("Unable to expand imported value set: " + vso.getError()); 1269 } 1270 ValueSet vs = vso.getValueset(); 1271 if (vs.hasUserData(UserDataNames.VS_EXPANSION_SOURCE)) { 1272 sources.add(vs.getUserString(UserDataNames.VS_EXPANSION_SOURCE)); 1273 } 1274 if (vs.hasVersion() || REPORT_VERSION_ANYWAY) { 1275 UriType u = new UriType(vs.getUrl() + (vs.hasVersion() ? "|"+vs.getVersion() : "")); 1276 if (!existsInParams(exp.getParameter(), "used-valueset", u)) { 1277 exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-valueset").setValue(u)); 1278 } 1279 } 1280 if (vs.getExpansion().hasTotal()) { 1281 dwc.incExtraCount(vs.getExpansion().getTotal() - countContains(vs.getExpansion().getContains())); 1282 dwc.getCountIncompleteSystems().add(inc.getSystem()+(inc.hasVersion() ? "#"+inc.getVersion(): "")); 1283 } else { 1284 dwc.setNoTotal(true); 1285 } 1286 for (ValueSetExpansionParameterComponent p : vso.getValueset().getExpansion().getParameter()) { 1287 if (!existsInParams(exp.getParameter(), p.getName(), p.getValue())) { 1288 exp.getParameter().add(p); 1289 } 1290 } 1291 for (Extension ex : vs.getExpansion().getExtension()) { 1292 if (Utilities.existsInList(ex.getUrl(), ToolingExtensions.EXT_EXP_TOOCOSTLY, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed")) { 1293 if (!ExtensionsUtils.hasExtension(extensions, ex.getUrl())) { 1294 extensions.add(ex); 1295 } 1296 } 1297 } 1298 for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) { 1299 addCodeAndDescendents(dwc, cc, null, expParams, imports, noInactive, vsProps, vs, exp, getLang(vs)); 1300 } 1301 } 1302 1303 1304 private int countContains(List<ValueSetExpansionContainsComponent> contains) { 1305 int count = contains.size(); 1306 for (ValueSetExpansionContainsComponent cc : contains) { 1307 if (cc.hasContains()) { 1308 count += countContains(cc.getContains()); 1309 } 1310 } 1311 return count; 1312 } 1313 1314 public void doInternalIncludeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, List<ValueSet> imports, CodeSystem cs, boolean noInactive, Resource vsSrc, String vspath) throws NoTerminologyServiceException, TerminologyServiceException, FHIRException, ETooCostly { 1315 opContext.deadCheck("doInternalIncludeCodes"); 1316 if (cs == null) { 1317 if (context.isNoTerminologyServer()) 1318 throw failTSE("Unable to find code system " + inc.getSystem().toString()); 1319 else 1320 throw failTSE("Unable to find code system " + inc.getSystem().toString()); 1321 } 1322 checkCanonical(exp, cs, focus); 1323 cs.checkNoModifiers("Code System", "expanding"); 1324 if (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT) 1325 throw failTSE("Code system " + inc.getSystem().toString() + " is incomplete"); 1326 if (cs.hasVersion() || REPORT_VERSION_ANYWAY) { 1327 UriType u = new UriType(cs.getUrl() + (cs.hasVersion() ? "|"+cs.getVersion() : "")); 1328 if (!existsInParams(exp.getParameter(), "used-codesystem", u)) 1329 exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-codesystem").setValue(u)); 1330 if (cs.hasUserData(UserDataNames.tx_known_supplements)) { 1331 for (String s : cs.getUserString(UserDataNames.tx_known_supplements).split("\\,")) { 1332 u = new UriType(s); 1333 if (!existsInParams(exp.getParameter(), "used-supplement", u)) { 1334 exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-supplement").setValue(u)); 1335 } 1336 } 1337 } 1338 } 1339 if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) { 1340 // special case - add all the code system 1341 for (ConceptDefinitionComponent def : cs.getConcept()) { 1342 addCodeAndDescendents(dwc, cs, inc.getSystem(), def, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), null, exp); 1343 } 1344 if (cs.getContent() == CodeSystemContentMode.FRAGMENT) { 1345 addFragmentWarning(exp, cs); 1346 } 1347 if (cs.getContent() == CodeSystemContentMode.EXAMPLE) { 1348 addExampleWarning(exp, cs); 1349 } 1350 } 1351 1352 if (!inc.getConcept().isEmpty()) { 1353 dwc.setCanBeHierarchy(false); 1354 for (ConceptReferenceComponent c : inc.getConcept()) { 1355 c.checkNoModifiers("Code in Value Set", "expanding"); 1356 ConceptDefinitionComponent def = CodeSystemUtilities.findCodeOrAltCode(cs.getConcept(), c.getCode(), null); 1357 boolean inactive = false; // default is true if we're a fragment and 1358 boolean isAbstract = false; 1359 if (def == null) { 1360 if (cs.getContent() == CodeSystemContentMode.FRAGMENT) { 1361 addFragmentWarning(exp, cs); 1362 } else if (cs.getContent() == CodeSystemContentMode.EXAMPLE) { 1363 addExampleWarning(exp, cs); 1364 } else { 1365 if (checkCodesWhenExpanding) { 1366 throw failTSE("Unable to find code '" + c.getCode() + "' in code system " + cs.getUrl()); 1367 } 1368 } 1369 } else { 1370 def.checkNoModifiers("Code in Code System", "expanding"); 1371 inactive = CodeSystemUtilities.isInactive(cs, def); 1372 isAbstract = CodeSystemUtilities.isNotSelectable(cs, def); 1373 addCode(dwc, inc.getSystem(), c.getCode(), !Utilities.noString(c.getDisplay()) ? c.getDisplay() : def.getDisplay(), c.hasDisplay() ? vsSrc.getLanguage() : cs.getLanguage(), null, mergeDesignations(def, convertDesignations(c.getDesignation())), 1374 expParams, isAbstract, inactive, imports, noInactive, false, exp.getProperty(), makeCSProps(def.getDefinition(), def.getProperty()), cs, null, def.getExtension(), c.getExtension(), exp); 1375 } 1376 } 1377 } 1378 if (inc.getFilter().size() > 0) { 1379 if (inc.getFilter().size() > 1) { 1380 dwc.setCanBeHierarchy(false); // which will be the case if we get around to supporting this 1381 } 1382 if (cs.getContent() == CodeSystemContentMode.FRAGMENT) { 1383 addFragmentWarning(exp, cs); 1384 } 1385 List<WorkingContext> filters = new ArrayList<>(); 1386 for (int i = 1; i < inc.getFilter().size(); i++) { 1387 WorkingContext wc = new WorkingContext(); 1388 filters.add(wc); 1389 processFilter(inc, exp, expParams, imports, cs, noInactive, inc.getFilter().get(i), wc, null, false, vspath+".filter["+i+"]"); 1390 } 1391 ConceptSetFilterComponent fc = inc.getFilter().get(0); 1392 WorkingContext wc = dwc; 1393 processFilter(inc, exp, expParams, imports, cs, noInactive, fc, wc, filters, false, vspath+".filter[0]"); 1394 } 1395 } 1396 1397 private void processFilter(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, List<ValueSet> imports, CodeSystem cs, boolean noInactive, 1398 ConceptSetFilterComponent fc, WorkingContext wc, List<WorkingContext> filters, boolean exclude, String vspath) 1399 throws ETooCostly { 1400 1401 if (!fc.hasValue() || fc.getValue() == null) { 1402 List<OperationOutcomeIssueComponent> issues = new ArrayList<>(); 1403 issues.addAll(makeIssue(IssueSeverity.ERROR, IssueType.INVALID, vspath+".value", context.formatMessage(I18nConstants.UNABLE_TO_HANDLE_SYSTEM_FILTER_WITH_NO_VALUE, cs.getUrl(), fc.getProperty(), fc.getOp().toCode()), OpIssueCode.VSProcessing, null)); 1404 throw new VSCheckerException(context.formatMessage(I18nConstants.UNABLE_TO_HANDLE_SYSTEM_FILTER_WITH_NO_VALUE, cs.getUrl(), fc.getProperty(), fc.getOp().toCode()), issues, TerminologyServiceErrorClass.INTERNAL_ERROR); 1405 } 1406 opContext.deadCheck("processFilter"); 1407 if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISA) { 1408 // special: all codes in the target code system under the value 1409 ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue()); 1410 if (def == null) 1411 throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'"); 1412 if (exclude) { 1413 excludeCodeAndDescendents(wc, cs, inc.getSystem(), def, null, imports, null, new AllConceptsFilter(allErrors), filters, exp); 1414 } else { 1415 addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp); 1416 } 1417 } else if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISNOTA) { 1418 // special: all codes in the target code system that are not under the value 1419 ConceptDefinitionComponent defEx = getConceptForCode(cs.getConcept(), fc.getValue()); 1420 if (defEx == null) 1421 throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'"); 1422 for (ConceptDefinitionComponent def : cs.getConcept()) { 1423 if (exclude) { 1424 excludeCodeAndDescendents(wc, cs, inc.getSystem(), def, null, imports, defEx, new AllConceptsFilter(allErrors), filters, exp); 1425 } else { 1426 addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, defEx, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp); 1427 } 1428 } 1429 } else if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.DESCENDENTOF) { 1430 // special: all codes in the target code system under the value 1431 ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue()); 1432 if (def == null) 1433 throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'"); 1434 for (ConceptDefinitionComponent c : def.getConcept()) 1435 if (exclude) { 1436 excludeCodeAndDescendents(wc, cs, inc.getSystem(), c, null, imports, null, new AllConceptsFilter(allErrors), filters, exp); 1437 } else { 1438 addCodeAndDescendents(wc, cs, inc.getSystem(), c, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp); 1439 } 1440 if (def.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) { 1441 List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) def.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK); 1442 for (ConceptDefinitionComponent c : children) { 1443 if (exclude) { 1444 excludeCodeAndDescendents(wc, cs, inc.getSystem(), c, null, imports, null, new AllConceptsFilter(allErrors), filters, exp); 1445 } else { 1446 addCodeAndDescendents(wc, cs, inc.getSystem(), c, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp); 1447 } 1448 } 1449 } 1450 1451 } else if ("display".equals(fc.getProperty()) && fc.getOp() == FilterOperator.EQUAL) { 1452 // gg; note: wtf is this: if the filter is display=v, look up the code 'v', and see if it's display is 'v'? 1453 dwc.setCanBeHierarchy(false); 1454 ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue()); 1455 if (def != null) { 1456 if (isNotBlank(def.getDisplay()) && isNotBlank(fc.getValue())) { 1457 if (def.getDisplay().contains(fc.getValue()) && passesOtherFilters(filters, cs, def.getCode())) { 1458 for (String code : getCodesForConcept(def, expParams)) { 1459 opContext.deadCheck("processFilter2"); 1460 if (exclude) { 1461 excludeCode(wc, inc.getSystem(), code); 1462 } else { 1463 addCode(wc, inc.getSystem(), code, def.getDisplay(), cs.getLanguage(), null, def.getDesignation(), expParams, CodeSystemUtilities.isNotSelectable(cs, def), CodeSystemUtilities.isInactive(cs, def), 1464 imports, noInactive, false, exp.getProperty(), makeCSProps(def.getDefinition(), def.getProperty()), cs, null, def.getExtension(), null, exp); 1465 } 1466 } 1467 } 1468 } 1469 } 1470 } else if (CodeSystemUtilities.isDefinedProperty(cs, fc.getProperty())) { 1471 for (ConceptDefinitionComponent def : cs.getConcept()) { 1472 PropertyFilter pf = new PropertyFilter(allErrors, fc, CodeSystemUtilities.getPropertyDefinition(cs, fc.getProperty())); 1473 if (exclude) { 1474 excludeCodeAndDescendents(wc, cs, inc.getSystem(), def, null, imports, null, pf, filters, exp); 1475 } else { 1476 addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, pf, noInactive, exp.getProperty(), filters, exp); 1477 } 1478 } 1479 } else if (isKnownProperty(fc.getProperty(), cs)) { 1480 for (ConceptDefinitionComponent def : cs.getConcept()) { 1481 KnownPropertyFilter pf = new KnownPropertyFilter(allErrors, fc, fc.getProperty()); 1482 if (exclude) { 1483 excludeCodeAndDescendents(wc, cs, inc.getSystem(), def, null, imports, null, pf, filters, exp); 1484 } else { 1485 addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, pf, noInactive, exp.getProperty(), filters, exp); 1486 } 1487 } 1488 } else if ("code".equals(fc.getProperty()) && fc.getOp() == FilterOperator.REGEX) { 1489 for (ConceptDefinitionComponent def : cs.getConcept()) { 1490 if (exclude) { 1491 excludeCodeAndDescendents(wc, cs, inc.getSystem(), def, null, imports, null, new RegexFilter(allErrors, fc.getValue()), filters, exp); 1492 } else { 1493 addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, new RegexFilter(allErrors, fc.getValue()), noInactive, exp.getProperty(), filters, exp); 1494 } 1495 } 1496 } else { 1497 throw fail(I18nConstants.VS_EXP_FILTER_UNK, true, focus.getVersionedUrl(), fc.getProperty(), fc.getOp()); 1498 } 1499 } 1500 1501 private boolean isKnownProperty(String property, CodeSystem cs) { 1502 return Utilities.existsInList(property, "notSelectable"); 1503 } 1504 1505 private List<ConceptDefinitionDesignationComponent> mergeDesignations(ConceptDefinitionComponent def, 1506 List<ConceptDefinitionDesignationComponent> list) { 1507 List<ConceptDefinitionDesignationComponent> res = new ArrayList<>(); 1508 if (def != null) { 1509 res.addAll(def.getDesignation()); 1510 } 1511 res.addAll(list); 1512 return res; 1513 } 1514 1515 1516 1517 private void addFragmentWarning(ValueSetExpansionComponent exp, CodeSystem cs) { 1518 String url = cs.getVersionedUrl(); 1519 for (ValueSetExpansionParameterComponent p : exp.getParameter()) { 1520 if ("fragment".equals(p.getName()) && p.hasValueUriType() && url.equals(p.getValue().primitiveValue())) { 1521 return; 1522 } 1523 } 1524 exp.addParameter().setName("fragment").setValue(new CanonicalType(url)); 1525 } 1526 1527 private void addExampleWarning(ValueSetExpansionComponent exp, CodeSystem cs) { 1528 String url = cs.getVersionedUrl(); 1529 for (ValueSetExpansionParameterComponent p : exp.getParameter()) { 1530 if ("example".equals(p.getName()) && p.hasValueUriType() && url.equals(p.getValue().primitiveValue())) { 1531 return; 1532 } 1533 } 1534 exp.addParameter().setName("example").setValue(new CanonicalType(url)); 1535 } 1536 1537 private List<ConceptDefinitionDesignationComponent> convertDesignations(List<ConceptReferenceDesignationComponent> list) { 1538 List<ConceptDefinitionDesignationComponent> res = new ArrayList<CodeSystem.ConceptDefinitionDesignationComponent>(); 1539 for (ConceptReferenceDesignationComponent t : list) { 1540 ConceptDefinitionDesignationComponent c = new ConceptDefinitionDesignationComponent(); 1541 c.setLanguage(t.getLanguage()); 1542 if (t.hasUse()) { 1543 c.setUse(t.getUse()); 1544 } 1545 c.setValue(t.getValue()); 1546 c.getExtension().addAll(t.getExtension()); 1547 res.add(c); 1548 } 1549 return res; 1550 } 1551 1552 private String key(String uri, String code) { 1553 return "{" + uri + "}" + code; 1554 } 1555 1556 private String key(ValueSetExpansionContainsComponent c) { 1557 return key(c.getSystem(), c.getCode()); 1558 } 1559 1560 private FHIRException fail(String msgId, boolean check, Object... params) { 1561 String msg = context.formatMessage(msgId, params); 1562 allErrors.add(msg); 1563 return new FHIRException(msg); 1564 } 1565 1566 private UnknownValueSetException failUnk(String msgId, boolean check, Object... params) { 1567 String msg = context.formatMessage(msgId, params); 1568 allErrors.add(msg); 1569 return new UnknownValueSetException(msg); 1570 } 1571 1572 private UnknownValueSetException failNotFound(String msgId, boolean check, Object... params) { 1573 String msg = context.formatMessage(msgId, params); 1574 allErrors.add(msg); 1575 return new UnknownValueSetException(msg); 1576 } 1577 1578 private ETooCostly failCostly(String msg) { 1579 allErrors.add(msg); 1580 return new ETooCostly(msg); 1581 } 1582 1583 private TerminologyServiceException failTSE(String msg) { 1584 allErrors.add(msg); 1585 return new TerminologyServiceException(msg); 1586 } 1587 1588 public Collection<? extends String> getAllErrors() { 1589 return allErrors; 1590 } 1591 1592 public boolean isCheckCodesWhenExpanding() { 1593 return checkCodesWhenExpanding; 1594 } 1595 1596 public void setCheckCodesWhenExpanding(boolean checkCodesWhenExpanding) { 1597 this.checkCodesWhenExpanding = checkCodesWhenExpanding; 1598 } 1599 1600 private boolean passesOtherFilters(List<WorkingContext> otherFilters, CodeSystem cs, String code) { 1601 if (otherFilters == null) { 1602 return true; 1603 } 1604 String key = key(cs.getUrl(), code); 1605 for (WorkingContext wc : otherFilters) { 1606 if (!wc.getMap().containsKey(key)) { 1607 return false; 1608 } 1609 } 1610 return true; 1611 } 1612 1613 public boolean isDebug() { 1614 return debug; 1615 } 1616 1617 public ValueSetExpander setDebug(boolean debug) { 1618 this.debug = debug; 1619 return this; 1620 } 1621 1622 public String getSource() { 1623 if (sources.isEmpty()) { 1624 return "internal"; 1625 } else { 1626 return CommaSeparatedStringBuilder.join(", ", Utilities.sorted(sources)); 1627 } 1628 } 1629 1630 1631}