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.List; 074 075import org.hl7.fhir.exceptions.FHIRException; 076import org.hl7.fhir.exceptions.FHIRFormatError; 077import org.hl7.fhir.exceptions.NoTerminologyServiceException; 078import org.hl7.fhir.exceptions.TerminologyServiceException; 079import org.hl7.fhir.r5.context.IWorkerContext; 080import org.hl7.fhir.r5.elementmodel.LanguageUtils; 081import org.hl7.fhir.r5.extensions.ExtensionConstants; 082import org.hl7.fhir.r5.extensions.Extensions; 083import org.hl7.fhir.r5.extensions.ExtensionsUtils; 084import org.hl7.fhir.r5.model.BooleanType; 085import org.hl7.fhir.r5.model.CodeSystem; 086import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode; 087import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; 088import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent; 089import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent; 090import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent; 091import org.hl7.fhir.r5.model.CodeType; 092import org.hl7.fhir.r5.model.Coding; 093import org.hl7.fhir.r5.model.DataType; 094import org.hl7.fhir.r5.model.DateTimeType; 095import org.hl7.fhir.r5.model.DecimalType; 096import org.hl7.fhir.r5.model.Enumerations.FilterOperator; 097import org.hl7.fhir.r5.model.Extension; 098import org.hl7.fhir.r5.model.Factory; 099import org.hl7.fhir.r5.model.IntegerType; 100import org.hl7.fhir.r5.model.PackageInformation; 101import org.hl7.fhir.r5.model.Parameters; 102import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent; 103import org.hl7.fhir.r5.model.PrimitiveType; 104import org.hl7.fhir.r5.model.Resource; 105import org.hl7.fhir.r5.model.StringType; 106import org.hl7.fhir.r5.model.CanonicalType; 107import org.hl7.fhir.r5.model.UriType; 108import org.hl7.fhir.r5.model.ValueSet; 109import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent; 110import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceDesignationComponent; 111import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; 112import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent; 113import org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent; 114import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent; 115import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 116import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionParameterComponent; 117import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent; 118import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; 119import org.hl7.fhir.r5.terminologies.ValueSetUtilities; 120import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpander.Token; 121import org.hl7.fhir.r5.terminologies.providers.CodeSystemProvider; 122import org.hl7.fhir.r5.terminologies.providers.CodeSystemProviderExtension; 123import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext; 124import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext.TerminologyServiceProtectionException; 125import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass; 126import org.hl7.fhir.r5.terminologies.utilities.ValueSetProcessBase; 127import org.hl7.fhir.r5.utils.ToolingExtensions; 128import org.hl7.fhir.r5.utils.client.EFhirClientException; 129import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 130import org.hl7.fhir.utilities.Utilities; 131import org.hl7.fhir.utilities.i18n.AcceptLanguageHeader; 132import org.hl7.fhir.utilities.i18n.AcceptLanguageHeader.LanguagePreference; 133import org.hl7.fhir.utilities.i18n.I18nConstants; 134import org.hl7.fhir.utilities.validation.ValidationOptions; 135 136public class ValueSetExpander extends ValueSetProcessBase { 137 138 139 public class Token { 140 private String system; 141 private String code; 142 public Token(String system, String code) { 143 super(); 144 this.system = system; 145 this.code = code; 146 } 147 public String getSystem() { 148 return system; 149 } 150 public String getCode() { 151 return code; 152 } 153 public boolean matches(Coding use) { 154 return system.equals(use.getSystem()) && code.equals(use.getCode()); 155 } 156 public boolean matchesLang(String language) { 157 return system.equals("urn:ietf:bcp:47") && code.equals(language); 158 } 159 } 160 161 private static final boolean REPORT_VERSION_ANYWAY = true; 162 163 private ValueSet focus; 164 private List<String> allErrors = new ArrayList<>(); 165 private int maxExpansionSize = 1000; 166 private WorkingContext dwc = new WorkingContext(); 167 168 private boolean checkCodesWhenExpanding; 169 private boolean includeAbstract = true; 170 private boolean debug; 171 172 private AcceptLanguageHeader langs; 173 private List<Token> designations = new ArrayList<>(); 174 175 public ValueSetExpander(IWorkerContext context, TerminologyOperationContext opContext) { 176 super(context, opContext); 177 } 178 179 public ValueSetExpander(IWorkerContext context, TerminologyOperationContext opContext, List<String> allErrors) { 180 super(context, opContext); 181 this.allErrors = allErrors; 182 } 183 184 public void setMaxExpansionSize(int theMaxExpansionSize) { 185 maxExpansionSize = theMaxExpansionSize; 186 } 187 188 private ValueSetExpansionContainsComponent addCode(WorkingContext wc, String system, String code, String display, String dispLang, ValueSetExpansionContainsComponent parent, List<ConceptDefinitionDesignationComponent> designations, Parameters expParams, 189 boolean isAbstract, boolean inactive, List<ValueSet> filters, boolean noInactive, boolean deprecated, List<ValueSetExpansionPropertyComponent> vsProp, 190 List<ConceptPropertyComponent> csProps, CodeSystem cs, List<org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent> expProps, List<Extension> csExtList, List<Extension> vsExtList, ValueSetExpansionComponent exp, boolean countToTotal) throws ETooCostly { 191 opContext.deadCheck(); 192 193 if (filters != null && !filters.isEmpty() && !filterContainsCode(filters, system, code, exp)) 194 return null; 195 if (noInactive && inactive) { 196 return null; 197 } 198 199 ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent(); 200 n.setSystem(system); 201 n.setCode(code); 202 if (isAbstract) { 203 n.setAbstract(true); 204 } 205 if (inactive) { 206 n.setInactive(true); 207 } 208 if (deprecated) { 209 ValueSetUtilities.setDeprecated(vsProp, n); 210 } 211 if (ExtensionsUtils.hasExtension(csExtList, "http://hl7.org/fhir/StructureDefinition/codesystem-label")) { 212 ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#label", "label", ExtensionsUtils.getExtensionValue(csExtList, "http://hl7.org/fhir/StructureDefinition/codesystem-label")); 213 } 214 if (ExtensionsUtils.hasExtension(vsExtList, "http://hl7.org/fhir/StructureDefinition/valueset-label")) { 215 ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#label", "label", ExtensionsUtils.getExtensionValue(vsExtList, "http://hl7.org/fhir/StructureDefinition/valueset-label")); 216 } 217 if (ExtensionsUtils.hasExtension(csExtList, "http://hl7.org/fhir/StructureDefinition/codesystem-conceptOrder")) { 218 ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#order", "order", convertToDecimal(ExtensionsUtils.getExtensionValue(csExtList, "http://hl7.org/fhir/StructureDefinition/codesystem-conceptOrder"))); 219 } 220 if (ExtensionsUtils.hasExtension(vsExtList, "http://hl7.org/fhir/StructureDefinition/valueset-conceptOrder")) { 221 ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#order", "order", convertToDecimal(ExtensionsUtils.getExtensionValue(vsExtList, "http://hl7.org/fhir/StructureDefinition/valueset-conceptOrder"))); 222 } 223 if (ExtensionsUtils.hasExtension(csExtList, "http://hl7.org/fhir/StructureDefinition/itemWeight")) { 224 ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#itemWeight", "weight", ExtensionsUtils.getExtensionValue(csExtList, "http://hl7.org/fhir/StructureDefinition/itemWeight")); 225 } 226 if (ExtensionsUtils.hasExtension(vsExtList, "http://hl7.org/fhir/StructureDefinition/itemWeight")) { 227 ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#itemWeight", "weight", ExtensionsUtils.getExtensionValue(vsExtList, "http://hl7.org/fhir/StructureDefinition/itemWeight")); 228 } 229 ExtensionsUtils.copyExtensions(csExtList, n.getExtension(), 230 "http://hl7.org/fhir/StructureDefinition/coding-sctdescid", 231 "http://hl7.org/fhir/StructureDefinition/rendering-style", 232 "http://hl7.org/fhir/StructureDefinition/rendering-xhtml", 233 "http://hl7.org/fhir/StructureDefinition/codesystem-alternate"); 234 235 ExtensionsUtils.copyExtensions(vsExtList, n.getExtension(), 236 "http://hl7.org/fhir/StructureDefinition/valueset-supplement", 237 "http://hl7.org/fhir/StructureDefinition/valueset-deprecated", 238 "http://hl7.org/fhir/StructureDefinition/valueset-concept-definition", 239 "http://hl7.org/fhir/StructureDefinition/coding-sctdescid", 240 "http://hl7.org/fhir/StructureDefinition/rendering-style", 241 "http://hl7.org/fhir/StructureDefinition/rendering-xhtml"); 242 243 // display and designations 244 ConceptDefinitionDesignationComponent pref = null; 245 if (langs == null) { 246 n.setDisplay(display); 247 } else { 248 if (designations == null) { 249 designations = new ArrayList<>(); 250 } 251 designations.add(new ConceptDefinitionDesignationComponent().setLanguage(dispLang).setValue(display).setUse(new Coding().setSystem("http://terminology.hl7.org/CodeSystem/designation-usage").setCode("display"))); 252 pref = findMatchingDesignation(designations); 253 if (pref != null) { 254 n.setDisplay(pref.getValue()); 255 } 256 } 257 258 if (expParams.getParameterBool("includeDesignations") && designations != null) { 259 260 for (ConceptDefinitionDesignationComponent t : designations) { 261 if (t != pref && (t.hasLanguage() || t.hasUse()) && t.getValue() != null && passesDesignationFilter(t)) { 262 ConceptReferenceDesignationComponent d = n.addDesignation(); 263 if (t.getLanguage() != null) { 264 d.setLanguage(t.getLanguage().trim()); 265 } 266 if (t.getValue() != null) { 267 d.setValue(t.getValue().trim()); 268 } 269 if (t.getUse() != null) { 270 d.setUse(t.getUse()); 271 } 272 for (Extension ext : t.getExtension()) { 273 if (Utilities.existsInList(ext.getUrl(), "http://hl7.org/fhir/StructureDefinition/coding-sctdescid")) { 274 d.addExtension(ext); 275 } 276 } 277 } 278 } 279 } 280 for (ParametersParameterComponent p : expParams.getParameter()) { 281 if ("property".equals(p.getName())) { 282 if (csProps != null && p.hasValue()) { 283 for (ConceptPropertyComponent cp : csProps) { 284 if (p.getValue().primitiveValue().equals(cp.getCode())) { 285 PropertyComponent pd = cs.getProperty(cp.getCode()); 286 String url = pd == null ? null : pd.getUri(); 287 if (url == null) { 288 if ("definition".equals(cp.getCode())) { 289 url = "http://hl7.org/fhir/concept-properties#definition"; 290 } else { 291 // ?? 292 } 293 } 294 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"); 295 } 296 } 297 } 298 if (expProps != null && p.hasValue()) { 299 for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent cp : expProps) { 300 if (p.getValue().primitiveValue().equals(cp.getCode())) { 301 String url = null; 302 for (ValueSetExpansionPropertyComponent t : vsProp) { 303 if (t.hasCode() && t.getCode().equals(cp.getCode())) { 304 url = t.getUri(); 305 } 306 } 307 if (url == null) { 308 if ("definition".equals(cp.getCode())) { 309 url = "http://hl7.org/fhir/concept-properties#definition"; 310 } else { 311 // TODO: try looking it up from the code system 312 } 313 } 314 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"); 315 } 316 } 317 } 318 } 319 } 320 321 String s = key(n); 322 if (wc.getMap().containsKey(s) || wc.getExcludeKeys().contains(s)) { 323 wc.setCanBeHierarchy(false); 324 } else { 325 wc.getCodes().add(n); 326 wc.getMap().put(s, n); 327 if (countToTotal) { 328 wc.incTotal(); 329 } 330// if (wc == dwc && wc.getTotal() > maxExpansionSize) { 331// if (wc.getOffset()+wc.getCount() > 0 && wc.getTotal() > wc.getOffset()+wc.getCount()) { 332// wc.setTotal(-1); 333// throw new EFinished(); 334// } 335// throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY, focus.getUrl(), ">" + Integer.toString(maxExpansionSize))); 336// } 337 } 338 if (wc.isCanBeHierarchy() && parent != null) { 339 parent.getContains().add(n); 340 } else if (!wc.getRootMap().containsKey(s)) { 341 wc.getRootMap().put(s, n); 342 wc.getRoots().add(n); 343 } 344 return n; 345 } 346 347 private DataType convertToDecimal(DataType v) { 348 if (v == null) { 349 return null; 350 } 351 if (v instanceof DecimalType) { 352 return v; 353 } 354 if (v instanceof IntegerType) { 355 return new DecimalType(((IntegerType) v).asStringValue()); 356 } 357 return null; 358 } 359 360 private boolean passesDesignationFilter(ConceptDefinitionDesignationComponent d) { 361 if (designations.isEmpty()) { 362 return true; 363 } 364 for (Token t : designations) { 365 if (t.matches(d.getUse()) || t.matchesLang(d.getLanguage())) { 366 return true; 367 } 368 for (Coding c : d.getAdditionalUse()) { 369 if (t.matches(c)) { 370 return true; 371 } 372 } 373 } 374 return false; 375 } 376 377 private ConceptDefinitionDesignationComponent findMatchingDesignation(List<ConceptDefinitionDesignationComponent> designations) { 378 if (langs == null) { 379 return null; 380 } 381 // we have a list of languages in priority order 382 // we have a list of designations in no order 383 // language exact match is preferred 384 // display is always preferred 385 386 for (LanguagePreference lang : langs.getLangs()) { 387 if (lang.getValue() > 0) { 388 for (ConceptDefinitionDesignationComponent cd : designations) { 389 if (isDisplay(cd) && LanguageUtils.langsMatchExact(cd.getLanguage(), lang.getLang())) { 390 return cd; 391 } 392 } 393 for (ConceptDefinitionDesignationComponent cd : designations) { 394 if (isDisplay(cd) && LanguageUtils.langsMatch(cd.getLanguage(), lang.getLang())) { 395 return cd; 396 } 397 } 398 for (ConceptDefinitionDesignationComponent cd : designations) { 399 if (LanguageUtils.langsMatchExact(cd.getLanguage(), lang.getLang())) { 400 return cd; 401 } 402 } 403 for (ConceptDefinitionDesignationComponent cd : designations) { 404 if (LanguageUtils.langsMatch(cd.getLanguage(), lang.getLang())) { 405 return cd; 406 } 407 } 408 } 409 } 410 return null; 411 } 412 413 private boolean isDisplay(ConceptDefinitionDesignationComponent cd) { 414 return cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display"); 415 } 416 417 private boolean filterContainsCode(List<ValueSet> filters, String system, String code, ValueSetExpansionComponent exp) { 418 for (ValueSet vse : filters) { 419 checkCanonical(exp, vse, focus); 420 if (expansionContainsCode(vse.getExpansion().getContains(), system, code)) 421 return true; 422 } 423 return false; 424 } 425 426 private boolean expansionContainsCode(List<ValueSetExpansionContainsComponent> contains, String system, String code) { 427 for (ValueSetExpansionContainsComponent cc : contains) { 428 if (system.equals(cc.getSystem()) && code.equals(cc.getCode())) 429 return true; 430 if (expansionContainsCode(cc.getContains(), system, code)) 431 return true; 432 } 433 return false; 434 } 435 436 private ConceptDefinitionDesignationComponent getMatchingLang(List<ConceptDefinitionDesignationComponent> list, AcceptLanguageHeader langs) { 437 for (ConceptDefinitionDesignationComponent t : list) { 438 if (LanguageUtils.langsMatchExact(langs, t.getLanguage())) { 439 return t; 440 } 441 } 442 for (ConceptDefinitionDesignationComponent t : list) { 443 if (LanguageUtils.langsMatch(langs, t.getLanguage())) { 444 return t; 445 } 446 } 447 return null; 448 } 449 450 private void addCodeAndDescendents(WorkingContext wc, ValueSetExpansionContainsComponent focus, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, ValueSet vsSrc, ValueSetExpansionComponent exp, boolean countToTotal) throws FHIRException, ETooCostly { 451 opContext.deadCheck(); 452 focus.checkNoModifiers("Expansion.contains", "expanding"); 453 ValueSetExpansionContainsComponent np = null; 454 for (String code : getCodesForConcept(focus, expParams)) { 455 ValueSetExpansionContainsComponent t = addCode(wc, focus.getSystem(), code, focus.getDisplay(), vsSrc.getLanguage(), parent, 456 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, countToTotal); 457 if (np == null) { 458 np = t; 459 } 460 } 461 for (ValueSetExpansionContainsComponent c : focus.getContains()) 462 addCodeAndDescendents(wc, c, np, expParams, filters, noInactive, vsProps, vsSrc, exp, countToTotal); 463 } 464 465 private List<ConceptPropertyComponent> makeCSProps(String definition, List<ConceptPropertyComponent> list) { 466 List<ConceptPropertyComponent> res = new ArrayList<>(); 467 if (!Utilities.noString(definition)) { 468 res.add(new ConceptPropertyComponent("definition", new StringType(definition))); 469 } 470 if (list != null) { 471 res.addAll(list); 472 } 473 return res; 474 } 475 476 private List<String> getCodesForConcept(ValueSetExpansionContainsComponent focus, Parameters expParams) { 477 List<String> codes = new ArrayList<>(); 478 codes.add(focus.getCode()); 479 for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent p : focus.getProperty()) { 480 if ("alternateCode".equals(p.getCode()) && (altCodeParams.passes(p.getExtension())) && p.getValue().isPrimitive()) { 481 codes.add(p.getValue().primitiveValue()); 482 } 483 } 484 return codes; 485 } 486 487 private List<ConceptDefinitionDesignationComponent> convert(List<ConceptReferenceDesignationComponent> designations) { 488 List<ConceptDefinitionDesignationComponent> list = new ArrayList<ConceptDefinitionDesignationComponent>(); 489 for (ConceptReferenceDesignationComponent d : designations) { 490 ConceptDefinitionDesignationComponent n = new ConceptDefinitionDesignationComponent(); 491 n.setLanguage(d.getLanguage()); 492 n.setUse(d.getUse()); 493 n.setValue(d.getValue()); 494 list.add(n); 495 } 496 return list; 497 } 498 499 private void addCodeAndDescendents(WorkingContext wc, CodeSystem cs, String system, ConceptDefinitionComponent def, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters, 500 ConceptDefinitionComponent exclusion, ConceptFilter filterFunc, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, List<WorkingContext> otherFilters, ValueSetExpansionComponent exp) throws FHIRException, ETooCostly { 501 opContext.deadCheck(); 502 def.checkNoModifiers("Code in Code System", "expanding"); 503 if (exclusion != null) { 504 if (exclusion.getCode().equals(def.getCode())) 505 return; // excluded. 506 } 507 ValueSetExpansionContainsComponent np = null; 508 boolean abs = CodeSystemUtilities.isNotSelectable(cs, def); 509 boolean inc = CodeSystemUtilities.isInactive(cs, def); 510 boolean dep = CodeSystemUtilities.isDeprecated(cs, def, false); 511 if ((includeAbstract || !abs) && filterFunc.includeConcept(cs, def) && passesOtherFilters(otherFilters, cs, def.getCode())) { 512 for (String code : getCodesForConcept(def, expParams)) { 513 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, true); 514 if (np == null) { 515 np = t; 516 } 517 } 518 } 519 for (ConceptDefinitionComponent c : def.getConcept()) { 520 addCodeAndDescendents(wc, cs, system, c, np, expParams, filters, exclusion, filterFunc, noInactive, vsProps, otherFilters, exp); 521 } 522 if (def.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) { 523 List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) def.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK); 524 for (ConceptDefinitionComponent c : children) 525 addCodeAndDescendents(wc, cs, system, c, np, expParams, filters, exclusion, filterFunc, noInactive, vsProps, otherFilters, exp); 526 } 527 } 528 529 530 private void excludeCodeAndDescendents(WorkingContext wc, CodeSystem cs, String system, ConceptDefinitionComponent def, Parameters expParams, List<ValueSet> filters, 531 ConceptDefinitionComponent exclusion, ConceptFilter filterFunc, List<WorkingContext> otherFilters, ValueSetExpansionComponent exp) throws FHIRException, ETooCostly { 532 opContext.deadCheck(); 533 def.checkNoModifiers("Code in Code System", "expanding"); 534 if (exclusion != null) { 535 if (exclusion.getCode().equals(def.getCode())) 536 return; // excluded. 537 } 538 boolean abs = CodeSystemUtilities.isNotSelectable(cs, def); 539 if ((includeAbstract || !abs) && filterFunc.includeConcept(cs, def) && passesOtherFilters(otherFilters, cs, def.getCode())) { 540 for (String code : getCodesForConcept(def, expParams)) { 541 if (!(filters != null && !filters.isEmpty() && !filterContainsCode(filters, system, code, exp))) 542 excludeCode(wc, system, code); 543 } 544 } 545 for (ConceptDefinitionComponent c : def.getConcept()) { 546 excludeCodeAndDescendents(wc, cs, system, c, expParams, filters, exclusion, filterFunc, otherFilters, exp); 547 } 548 if (def.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) { 549 List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) def.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK); 550 for (ConceptDefinitionComponent c : children) 551 excludeCodeAndDescendents(wc, cs, system, c, expParams, filters, exclusion, filterFunc, otherFilters, exp); 552 } 553 } 554 555 556 private List<String> getCodesForConcept(ConceptDefinitionComponent focus, Parameters expParams) { 557 List<String> codes = new ArrayList<>(); 558 codes.add(focus.getCode()); 559 for (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 static boolean hasUse(ConceptPropertyComponent p, List<String> uses) { 568 for (Extension ext : p.getExtensionsByUrl(ToolingExtensions.EXT_CS_ALTERNATE_USE)) { 569 if (ext.hasValueCoding() && Utilities.existsInList(ext.getValueCoding().getCode(), uses)) { 570 return true; 571 } 572 } 573 return false; 574 } 575 576 577 578 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 { 579 if (expand != null) { 580 if (expand.getContains().size() > maxExpansionSize) 581 throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY, vsSrc.getUrl(), ">" + Integer.toString(expand.getContains().size()))); 582 for (ValueSetExpansionParameterComponent p : expand.getParameter()) { 583 if (!existsInParams(params, p.getName(), p.getValue())) 584 params.add(p); 585 } 586 587 copyImportContains(expand.getContains(), null, expParams, filters, noInactive, vsProps, vsSrc, exp); 588 } 589 } 590 591 private void excludeCode(WorkingContext wc, String theSystem, String theCode) { 592 ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent(); 593 n.setSystem(theSystem); 594 n.setCode(theCode); 595 String s = key(n); 596 wc.getExcludeKeys().add(s); 597 } 598 599 private void excludeCodes(WorkingContext wc, ConceptSetComponent exc, Parameters expParams, ValueSetExpansionComponent exp, ValueSet vs) throws FHIRException, FileNotFoundException, ETooCostly, IOException { 600 opContext.deadCheck(); 601 exc.checkNoModifiers("Compose.exclude", "expanding"); 602 if (exc.hasSystem() && exc.getConcept().size() == 0 && exc.getFilter().size() == 0) { 603 wc.getExcludeSystems().add(exc.getSystem()); 604 } 605 606 for (UriType imp : exc.getValueSet()) { 607 excludeCodes(wc, importValueSetForExclude(wc, imp.getValue(), exp, expParams, false, vs).getExpansion()); 608 } 609 610 CodeSystem cs = context.fetchSupplementedCodeSystem(exc.getSystem()); 611 if ((cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE) && context.supportsSystem(exc.getSystem(), opContext.getOptions().getFhirVersion())) { 612 ValueSetExpansionOutcome vse = context.expandVS(exc, false, false); 613 ValueSet valueset = vse.getValueset(); 614 if (valueset == null) 615 throw failTSE("Error Expanding ValueSet: "+vse.getError()); 616 excludeCodes(wc, valueset.getExpansion()); 617 return; 618 } 619 620 for (ConceptReferenceComponent c : exc.getConcept()) { 621 excludeCode(wc, exc.getSystem(), c.getCode()); 622 } 623 624 if (exc.getFilter().size() > 0) { 625 if (cs.getContent() == CodeSystemContentMode.FRAGMENT) { 626 addFragmentWarning(exp, cs); 627 } 628 List<WorkingContext> filters = new ArrayList<>(); 629 for (int i = 1; i < exc.getFilter().size(); i++) { 630 WorkingContext wc1 = new WorkingContext(); 631 filters.add(wc1); 632 processFilter(exc, exp, expParams, null, cs, false, exc.getFilter().get(i), wc1, null, true); 633 } 634 ConceptSetFilterComponent fc = exc.getFilter().get(0); 635 WorkingContext wc1 = dwc; 636 processFilter(exc, exp, expParams, null, cs, false, fc, wc1, filters, true); 637 } 638 } 639 640 private void excludeCodes(WorkingContext wc, ValueSetExpansionComponent expand) { 641 opContext.deadCheck(); 642 for (ValueSetExpansionContainsComponent c : expand.getContains()) { 643 excludeCode(wc, c.getSystem(), c.getCode()); 644 } 645 } 646 647 private boolean existsInParams(List<ValueSetExpansionParameterComponent> params, String name, DataType value) { 648 for (ValueSetExpansionParameterComponent p : params) { 649 if (p.getName().equals(name) && PrimitiveType.compareDeep(p.getValue(), value, false)) { 650 return true; 651 } 652 } 653 return false; 654 } 655 656 public ValueSetExpansionOutcome expand(ValueSet source, Parameters expParams) { 657 658 allErrors.clear(); 659 try { 660 opContext.seeContext(source.getVersionedUrl()); 661 662 return expandInternal(source, expParams); 663 } catch (NoTerminologyServiceException e) { 664 // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set 665 // that might fail too, but it might not, later. 666 return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.NOSERVICE, allErrors, false); 667 } catch (CodeSystemProviderExtension e) { 668 // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set 669 // that might fail too, but it might not, later. 670 return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.INTERNAL_ERROR, allErrors, false); 671 } catch (TerminologyServiceProtectionException e) { 672 if (opContext.isOriginal()) { 673 return new ValueSetExpansionOutcome(e.getMessage(), e.getError(), allErrors, false); 674 } else { 675 throw e; 676 } 677 } catch (ETooCostly e) { 678 return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.TOO_COSTLY, allErrors, false); 679 } catch (Exception e) { 680 if (debug) { 681 e.printStackTrace(); 682 } 683 return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.UNKNOWN, allErrors, e instanceof EFhirClientException || e instanceof TerminologyServiceException); 684 } 685 } 686 687 public ValueSetExpansionOutcome expandInternal(ValueSet source, Parameters expParams) throws FHIRException, FileNotFoundException, ETooCostly, IOException, CodeSystemProviderExtension { 688 return doExpand(source, expParams); 689 } 690 691 private void processParameter(String name, DataType value) { 692 if (Utilities.existsInList(name, "includeDesignations", "excludeNested", "activeOnly", "offset", "count")) { 693 focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name)); 694 focus.getExpansion().addParameter().setName(name).setValue(value); 695 } 696 if ("displayLanguage".equals(name)) { 697 this.langs = new AcceptLanguageHeader(value.primitiveValue(), true); 698 focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name)); 699 focus.getExpansion().addParameter().setName(name).setValue(new CodeType(value.primitiveValue())); 700 } 701 if ("designation".equals(name)) { 702 String[] v = value.primitiveValue().split("\\|"); 703 if (v.length != 2 || !Utilities.isAbsoluteUrl(v[0]) || Utilities.noString(v[1])) { 704 throw new NoTerminologyServiceException("Unable to understand designation parameter "+value.primitiveValue()); 705 } 706 this.designations.add(new Token(v[0], v[1])); 707 focus.getExpansion().addParameter().setName(name).setValue(new StringType(value.primitiveValue())); 708 } 709 if ("offset".equals(name) && value instanceof IntegerType) { 710 focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name)); 711 focus.getExpansion().addParameter().setName(name).setValue(value); 712 dwc.setOffsetParam(((IntegerType) value).getValue()); 713 if (dwc.getOffsetParam() < 0) { 714 dwc.setOffsetParam(0); 715 } 716 } 717 if ("count".equals(name)) { 718 focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name)); 719 focus.getExpansion().addParameter().setName(name).setValue(value); 720 dwc.setCountParam(((IntegerType) value).getValue()); 721 if (dwc.getCountParam() < 0) { 722 dwc.setCountParam(0); 723 } 724 } 725 } 726 727 public ValueSetExpansionOutcome doExpand(ValueSet source, Parameters expParams) throws FHIRException, ETooCostly, FileNotFoundException, IOException, CodeSystemProviderExtension { 728 if (expParams == null) 729 expParams = makeDefaultExpansion(); 730 altCodeParams.seeParameters(expParams); 731 altCodeParams.seeValueSet(source); 732 733 source.checkNoModifiers("ValueSet", "expanding"); 734 focus = source.copy(); 735 focus.setIdBase(null); 736 focus.setExpansion(new ValueSet.ValueSetExpansionComponent()); 737 focus.getExpansion().setTimestampElement(DateTimeType.now()); 738 focus.getExpansion().setIdentifier(Factory.createUUID()); 739 checkCanonical(focus.getExpansion(), focus, focus); 740 for (Extension ext : focus.getCompose().getExtensionsByUrl("http://hl7.org/fhir/tools/StructureDefinion/valueset-expansion-param")) { 741 processParameter(ext.getExtensionString("name"), ext.getExtensionByUrl("value").getValue()); 742 } 743 for (ParametersParameterComponent p : expParams.getParameter()) { 744 processParameter(p.getName(), p.getValue()); 745 } 746 for (Extension s : focus.getExtensionsByUrl(ExtensionConstants.EXT_VSSUPPLEMENT)) { 747 requiredSupplements.add(s.getValue().primitiveValue()); 748 } 749 if (langs == null && focus.hasLanguage()) { 750 langs = new AcceptLanguageHeader(focus.getLanguage(), true); 751 } 752 753 try { 754 if (source.hasCompose()) { 755// ExtensionsUtils.stripExtensions(focus.getCompose()); - disabled 23/05/2023 GDG - why was this ever thought to be a good idea? 756 handleCompose(source.getCompose(), focus.getExpansion(), expParams, source.getUrl(), focus.getExpansion().getExtension(), source); 757 } 758 } catch (EFinished e) { 759 // nothing - we intended to trap this here 760 } 761 762 if (dwc.getTotal() > maxExpansionSize && dwc.getOffsetParam() + dwc.getCountParam() == 0) { 763 if (dwc.isNoTotal()) { 764 throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY, focus.getVersionedUrl(), ">" + MessageFormat.format("{0,number,#}", maxExpansionSize))); 765 } else { 766 throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY_COUNT, focus.getVersionedUrl(), ">" + MessageFormat.format("{0,number,#}", maxExpansionSize), MessageFormat.format("{0,number,#}", dwc.getTotal()))); 767 } 768 } else if (dwc.isCanBeHierarchy() && ((dwc.getCountParam() == 0) || dwc.getCountParam() > dwc.getCodes().size())) { 769 for (ValueSetExpansionContainsComponent c : dwc.getRoots()) { 770 focus.getExpansion().getContains().add(c); 771 } 772 } else { 773 int i = 0; 774 int cc = 0; 775 for (ValueSetExpansionContainsComponent c : dwc.getCodes()) { 776 c.getContains().clear(); // make sure any hierarchy is wiped 777 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 778 if (dwc.getOffsetParam() == 0 || i >= dwc.getOffsetParam()) { 779 focus.getExpansion().getContains().add(c); 780 cc++; 781 if (cc == dwc.getCountParam()) { 782 break; 783 } 784 } 785 i++; 786 } 787 } 788 } 789 790 if (dwc.hasOffsetParam()) { 791 focus.getExpansion().setOffset(dwc.getOffsetParam()); 792 } 793 if (!dwc.isNoTotal()) { 794 focus.getExpansion().setTotal(dwc.getTotal()); 795 } 796 if (!requiredSupplements.isEmpty()) { 797 return new ValueSetExpansionOutcome(context.formatMessagePlural(requiredSupplements.size(), I18nConstants.VALUESET_SUPPLEMENT_MISSING, CommaSeparatedStringBuilder.build(requiredSupplements)), TerminologyServiceErrorClass.BUSINESS_RULE, allErrors, false); 798 } 799 if (!expParams.hasParameter("includeDefinition") || !expParams.getParameterBool("includeDefinition")) { 800 focus.setCompose(null); 801 focus.getExtension().clear(); 802 focus.setPublisher(null); 803 focus.setDescription(null); 804 focus.setPurpose(null); 805 focus.getContact().clear(); 806 focus.setCopyright(null); 807 focus.setText(null); 808 } 809 return new ValueSetExpansionOutcome(focus); 810 } 811 812 813 private Parameters makeDefaultExpansion() { 814 Parameters res = new Parameters(); 815 res.addParameter("excludeNested", true); 816 res.addParameter("includeDesignations", false); 817 return res; 818 } 819 820 private ConceptDefinitionComponent getConceptForCode(List<ConceptDefinitionComponent> clist, String code) { 821 for (ConceptDefinitionComponent c : clist) { 822 if (code.equals(c.getCode())) 823 return c; 824 ConceptDefinitionComponent v = getConceptForCode(c.getConcept(), code); 825 if (v != null) 826 return v; 827 } 828 return null; 829 } 830 831 private void handleCompose(ValueSetComposeComponent compose, ValueSetExpansionComponent exp, Parameters expParams, String ctxt, List<Extension> extensions, ValueSet valueSet) 832 throws ETooCostly, FileNotFoundException, IOException, FHIRException, CodeSystemProviderExtension { 833 compose.checkNoModifiers("ValueSet.compose", "expanding"); 834 // Exclude comes first because we build up a map of things to exclude 835 for (ConceptSetComponent inc : compose.getExclude()) 836 excludeCodes(dwc, inc, expParams, exp, valueSet); 837 dwc.setCanBeHierarchy(!expParams.getParameterBool("excludeNested") && dwc.getExcludeKeys().isEmpty() && dwc.getExcludeSystems().isEmpty() && dwc.getOffsetParam() == 0); 838 includeAbstract = !expParams.getParameterBool("excludeNotForUI"); 839 boolean first = true; 840 for (ConceptSetComponent inc : compose.getInclude()) { 841 if (first == true) 842 first = false; 843 else 844 dwc.setCanBeHierarchy(false); 845 includeCodes(inc, exp, expParams, dwc.isCanBeHierarchy(), compose.hasInactive() ? !compose.getInactive() : checkNoInActiveFromParam(expParams), extensions, valueSet); 846 } 847 } 848 849 /** 850 * returns true if activeOnly = true 851 * @param expParams 852 * @return 853 */ 854 private boolean checkNoInActiveFromParam(Parameters expParams) { 855 for (ParametersParameterComponent p : expParams.getParameter()) { 856 if (p.getName().equals("activeOnly")) { 857 return p.getValueBooleanType().getValue(); 858 } 859 } 860 return false; 861 } 862 863 private ValueSet importValueSet(WorkingContext wc, String value, ValueSetExpansionComponent exp, Parameters expParams, boolean noInactive, ValueSet valueSet) throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException, FHIRFormatError { 864 if (value == null) 865 throw fail("unable to find value set with no identity"); 866 ValueSet vs = context.findTxResource(ValueSet.class, value, valueSet); 867 if (vs == null) { 868 if (context.fetchResource(CodeSystem.class, value, valueSet) != null) { 869 throw fail("Cannot include value set "+value+" because it's actually a code system"); 870 } else { 871 throw fail("Unable to find imported value set " + value); 872 } 873 } 874 checkCanonical(exp, vs, focus); 875 if (noInactive) { 876 expParams = expParams.copy(); 877 expParams.addParameter("activeOnly", true); 878 } 879 ValueSetExpansionOutcome vso = new ValueSetExpander(context, opContext.copy(), allErrors).expand(vs, expParams); 880 if (vso.getError() != null) { 881 addErrors(vso.getAllErrors()); 882 throw fail("Unable to expand imported value set "+vs.getUrl()+": " + vso.getError()); 883 } else if (vso.getValueset() == null) { 884 throw fail("Unable to expand imported value set "+vs.getUrl()+" but no error"); 885 } 886 if (vs.hasVersion() || REPORT_VERSION_ANYWAY) { 887 UriType u = new UriType(vs.getUrl() + (vs.hasVersion() ? "|"+vs.getVersion() : "")); 888 if (!existsInParams(exp.getParameter(), "used-valueset", u)) 889 exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-valueset").setValue(u)); 890 } 891 ValueSetExpansionComponent evs = vso.getValueset().getExpansion(); 892 for (Extension ex : evs.getExtension()) { 893 if (ex.getUrl().equals(ToolingExtensions.EXT_EXP_TOOCOSTLY)) { 894 if (ex.getValue() instanceof BooleanType) { 895 exp.getExtension().add(new Extension(ToolingExtensions.EXT_EXP_TOOCOSTLY).setValue(new CanonicalType(value))); 896 } else { 897 exp.getExtension().add(ex); 898 } 899 } 900 } 901 if (evs.hasTotal()) { 902 dwc.incTotal(evs.getTotal()); 903 } else { 904 dwc.setNoTotal(true); 905 } 906 for (ValueSetExpansionParameterComponent p : evs.getParameter()) { 907 if (!existsInParams(exp.getParameter(), p.getName(), p.getValue())) 908 exp.getParameter().add(p); 909 } 910 if (isValueSetUnionImports(valueSet)) { 911 copyExpansion(wc, evs.getContains()); 912 } 913 wc.setCanBeHierarchy(false); // if we're importing a value set, we have to be combining, so we won't try for a hierarchy 914 return vso.getValueset(); 915 } 916 917 918 private ValueSet importValueSetForExclude(WorkingContext wc, String value, ValueSetExpansionComponent exp, Parameters expParams, boolean noInactive, ValueSet valueSet) throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException, FHIRFormatError { 919 if (value == null) 920 throw fail("unable to find value set with no identity"); 921 ValueSet vs = context.findTxResource(ValueSet.class, value, valueSet); 922 if (vs == null) { 923 if (context.fetchResource(CodeSystem.class, value, valueSet) != null) { 924 throw fail("Cannot include value set "+value+" because it's actually a code system"); 925 } else { 926 throw fail("Unable to find imported value set " + value); 927 } 928 } 929 checkCanonical(exp, vs, focus); 930 if (noInactive) { 931 expParams = expParams.copy(); 932 expParams.addParameter("activeOnly", true); 933 } 934 ValueSetExpansionOutcome vso = new ValueSetExpander(context, opContext.copy(), allErrors).expand(vs, expParams); 935 if (vso.getError() != null) { 936 addErrors(vso.getAllErrors()); 937 throw fail("Unable to expand imported value set "+vs.getUrl()+": " + vso.getError()); 938 } 939 if (vs.hasVersion() || REPORT_VERSION_ANYWAY) { 940 UriType u = new UriType(vs.getUrl() + (vs.hasVersion() ? "|"+vs.getVersion() : "")); 941 if (!existsInParams(exp.getParameter(), "used-valueset", u)) 942 exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-valueset").setValue(u)); 943 } 944 for (Extension ex : vso.getValueset().getExpansion().getExtension()) { 945 if (ex.getUrl().equals(ToolingExtensions.EXT_EXP_TOOCOSTLY)) { 946 throw fail("Unable to expand imported value set "+vs.getUrl()+" for exclude: too costly"); 947 } 948 } 949 return vso.getValueset(); 950 } 951 952 protected boolean isValueSetUnionImports(ValueSet valueSet) { 953 PackageInformation p = valueSet.getSourcePackage(); 954 if (p != null) { 955 return p.getDate().before(new GregorianCalendar(2022, Calendar.MARCH, 31).getTime()); 956 } else { 957 return false; 958 } 959 } 960 961 public void copyExpansion(WorkingContext wc,List<ValueSetExpansionContainsComponent> list) { 962 opContext.deadCheck(); 963 for (ValueSetExpansionContainsComponent cc : list) { 964 ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent(); 965 n.setSystem(cc.getSystem()); 966 n.setCode(cc.getCode()); 967 n.setAbstract(cc.getAbstract()); 968 n.setInactive(cc.getInactive()); 969 n.setDisplay(cc.getDisplay()); 970 n.getDesignation().addAll(cc.getDesignation()); 971 972 String s = key(n); 973 if (!wc.getMap().containsKey(s) && !wc.getExcludeKeys().contains(s)) { 974 wc.getCodes().add(n); 975 wc.getMap().put(s, n); 976 wc.incTotal(); 977 } 978 copyExpansion(wc, cc.getContains()); 979 } 980 } 981 982 private void addErrors(List<String> errs) { 983 for (String s : errs) { 984 if (!allErrors.contains(s)) { 985 allErrors.add(s); 986 } 987 } 988 } 989 990 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 { 991 int count = 0; 992 opContext.deadCheck(); 993 for (ValueSetExpansionContainsComponent c : list) { 994 c.checkNoModifiers("Imported Expansion in Code System", "expanding"); 995 ValueSetExpansionContainsComponent np = addCode(dwc, c.getSystem(), c.getCode(), c.getDisplay(), vsSrc.getLanguage(), parent, null, expParams, c.getAbstract(), c.getInactive(), 996 filter, noInactive, false, vsProps, makeCSProps(c.getExtensionString(ToolingExtensions.EXT_DEFINITION), null), null, c.getProperty(), null, c.getExtension(), exp, false); 997 if (np != null) { 998 count++; 999 } 1000 count = count + copyImportContains(c.getContains(), np, expParams, filter, noInactive, vsProps, vsSrc, exp); 1001 } 1002 return count; 1003 } 1004 1005 private void includeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, boolean heirarchical, boolean noInactive, List<Extension> extensions, ValueSet valueSet) throws ETooCostly, FileNotFoundException, IOException, FHIRException, CodeSystemProviderExtension { 1006 opContext.deadCheck(); 1007 inc.checkNoModifiers("Compose.include", "expanding"); 1008 List<ValueSet> imports = new ArrayList<ValueSet>(); 1009 for (CanonicalType imp : inc.getValueSet()) { 1010 imports.add(importValueSet(dwc, imp.getValue(), exp, expParams, noInactive, valueSet)); 1011 } 1012 1013 if (!inc.hasSystem()) { 1014 if (imports.isEmpty()) // though this is not supposed to be the case 1015 return; 1016 dwc.resetTotal(); 1017 ValueSet base = imports.get(0); 1018 checkCanonical(exp, base, focus); 1019 imports.remove(0); 1020 base.checkNoModifiers("Imported ValueSet", "expanding"); 1021 dwc.incTotal(copyImportContains(base.getExpansion().getContains(), null, expParams, imports, noInactive, base.getExpansion().getProperty(), base, exp)); 1022 } else { 1023 CodeSystem cs = context.fetchSupplementedCodeSystem(inc.getSystem()); 1024 if (ValueSetUtilities.isServerSide(inc.getSystem()) || (cs == null || (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT))) { 1025 doServerIncludeCodes(inc, heirarchical, exp, imports, expParams, extensions, noInactive, valueSet.getExpansion().getProperty()); 1026 } else { 1027 if (cs.hasUserData("supplements.installed")) { 1028 for (String s : cs.getUserString("supplements.installed").split("\\,")) { 1029 requiredSupplements.remove(s); 1030 } 1031 } 1032 doInternalIncludeCodes(inc, exp, expParams, imports, cs, noInactive, valueSet); 1033 } 1034 } 1035 } 1036 1037 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 { 1038 opContext.deadCheck(); 1039 CodeSystemProvider csp = CodeSystemProvider.factory(inc.getSystem()); 1040 if (csp != null) { 1041 csp.includeCodes(inc, heirarchical, exp, imports, expParams, extensions, noInactive, vsProps); 1042 return; 1043 } 1044 1045 ValueSetExpansionOutcome vso = context.expandVS(inc, heirarchical, noInactive); 1046 if (vso.getError() != null) { 1047 throw failTSE("Unable to expand imported value set: " + vso.getError()); 1048 } 1049 ValueSet vs = vso.getValueset(); 1050 if (vs.hasVersion() || REPORT_VERSION_ANYWAY) { 1051 UriType u = new UriType(vs.getUrl() + (vs.hasVersion() ? "|"+vs.getVersion() : "")); 1052 if (!existsInParams(exp.getParameter(), "used-valueset", u)) { 1053 exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-valueset").setValue(u)); 1054 } 1055 } 1056 if (vs.getExpansion().hasTotal()) { 1057 dwc.incTotal(vs.getExpansion().getTotal()); 1058 } else { 1059 dwc.setNoTotal(true); 1060 } 1061 for (ValueSetExpansionParameterComponent p : vso.getValueset().getExpansion().getParameter()) { 1062 if (!existsInParams(exp.getParameter(), p.getName(), p.getValue())) { 1063 exp.getParameter().add(p); 1064 } 1065 } 1066 for (Extension ex : vs.getExpansion().getExtension()) { 1067 if (Utilities.existsInList(ex.getUrl(), ToolingExtensions.EXT_EXP_TOOCOSTLY, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed")) { 1068 if (!ExtensionsUtils.hasExtension(extensions, ex.getUrl())) { 1069 extensions.add(ex); 1070 } 1071 } 1072 } 1073 for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) { 1074 addCodeAndDescendents(dwc, cc, null, expParams, imports, noInactive, vsProps, vs, exp, !vs.getExpansion().hasTotal()); 1075 } 1076 } 1077 1078 1079 public void doInternalIncludeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, List<ValueSet> imports, CodeSystem cs, boolean noInactive, Resource vsSrc) throws NoTerminologyServiceException, TerminologyServiceException, FHIRException, ETooCostly { 1080 opContext.deadCheck(); 1081 if (cs == null) { 1082 if (context.isNoTerminologyServer()) 1083 throw failTSE("Unable to find code system " + inc.getSystem().toString()); 1084 else 1085 throw failTSE("Unable to find code system " + inc.getSystem().toString()); 1086 } 1087 checkCanonical(exp, cs, focus); 1088 cs.checkNoModifiers("Code System", "expanding"); 1089 if (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT) 1090 throw failTSE("Code system " + inc.getSystem().toString() + " is incomplete"); 1091 if (cs.hasVersion() || REPORT_VERSION_ANYWAY) { 1092 UriType u = new UriType(cs.getUrl() + (cs.hasVersion() ? "|"+cs.getVersion() : "")); 1093 if (!existsInParams(exp.getParameter(), "used-codesystem", u)) 1094 exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-codesystem").setValue(u)); 1095 if (cs.hasUserData("supplements.installed")) { 1096 for (String s : cs.getUserString("supplements.installed").split("\\,")) { 1097 u = new UriType(s); 1098 if (!existsInParams(exp.getParameter(), "used-supplement", u)) { 1099 exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-supplement").setValue(u)); 1100 } 1101 } 1102 } 1103 } 1104 if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) { 1105 // special case - add all the code system 1106 for (ConceptDefinitionComponent def : cs.getConcept()) { 1107 addCodeAndDescendents(dwc, cs, inc.getSystem(), def, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), null, exp); 1108 } 1109 if (cs.getContent() == CodeSystemContentMode.FRAGMENT) { 1110 addFragmentWarning(exp, cs); 1111 } 1112 if (cs.getContent() == CodeSystemContentMode.EXAMPLE) { 1113 addExampleWarning(exp, cs); 1114 } 1115 } 1116 1117 if (!inc.getConcept().isEmpty()) { 1118 dwc.setCanBeHierarchy(false); 1119 for (ConceptReferenceComponent c : inc.getConcept()) { 1120 c.checkNoModifiers("Code in Value Set", "expanding"); 1121 ConceptDefinitionComponent def = CodeSystemUtilities.findCodeOrAltCode(cs.getConcept(), c.getCode(), null); 1122 boolean inactive = false; // default is true if we're a fragment and 1123 boolean isAbstract = false; 1124 if (def == null) { 1125 if (cs.getContent() == CodeSystemContentMode.FRAGMENT) { 1126 addFragmentWarning(exp, cs); 1127 } else if (cs.getContent() == CodeSystemContentMode.EXAMPLE) { 1128 addExampleWarning(exp, cs); 1129 } else { 1130 if (checkCodesWhenExpanding) { 1131 throw failTSE("Unable to find code '" + c.getCode() + "' in code system " + cs.getUrl()); 1132 } 1133 } 1134 } else { 1135 def.checkNoModifiers("Code in Code System", "expanding"); 1136 inactive = CodeSystemUtilities.isInactive(cs, def); 1137 isAbstract = CodeSystemUtilities.isNotSelectable(cs, def); 1138 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())), 1139 expParams, isAbstract, inactive, imports, noInactive, false, exp.getProperty(), makeCSProps(def.getDefinition(), def.getProperty()), cs, null, def.getExtension(), c.getExtension(), exp, true); 1140 } 1141 } 1142 } 1143 if (inc.getFilter().size() > 0) { 1144 if (inc.getFilter().size() > 1) { 1145 dwc.setCanBeHierarchy(false); // which will be the case if we get around to supporting this 1146 } 1147 if (cs.getContent() == CodeSystemContentMode.FRAGMENT) { 1148 addFragmentWarning(exp, cs); 1149 } 1150 List<WorkingContext> filters = new ArrayList<>(); 1151 for (int i = 1; i < inc.getFilter().size(); i++) { 1152 WorkingContext wc = new WorkingContext(); 1153 filters.add(wc); 1154 processFilter(inc, exp, expParams, imports, cs, noInactive, inc.getFilter().get(i), wc, null, false); 1155 } 1156 ConceptSetFilterComponent fc = inc.getFilter().get(0); 1157 WorkingContext wc = dwc; 1158 processFilter(inc, exp, expParams, imports, cs, noInactive, fc, wc, filters, false); 1159 } 1160 } 1161 1162 private void processFilter(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, List<ValueSet> imports, CodeSystem cs, boolean noInactive, 1163 ConceptSetFilterComponent fc, WorkingContext wc, List<WorkingContext> filters, boolean exclude) 1164 throws ETooCostly { 1165 opContext.deadCheck(); 1166 if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISA) { 1167 // special: all codes in the target code system under the value 1168 ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue()); 1169 if (def == null) 1170 throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'"); 1171 if (exclude) { 1172 excludeCodeAndDescendents(wc, cs, inc.getSystem(), def, null, imports, null, new AllConceptsFilter(allErrors), filters, exp); 1173 } else { 1174 addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp); 1175 } 1176 } else if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISNOTA) { 1177 // special: all codes in the target code system that are not under the value 1178 ConceptDefinitionComponent defEx = getConceptForCode(cs.getConcept(), fc.getValue()); 1179 if (defEx == null) 1180 throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'"); 1181 for (ConceptDefinitionComponent def : cs.getConcept()) { 1182 if (exclude) { 1183 excludeCodeAndDescendents(wc, cs, inc.getSystem(), def, null, imports, defEx, new AllConceptsFilter(allErrors), filters, exp); 1184 } else { 1185 addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, defEx, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp); 1186 } 1187 } 1188 } else if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.DESCENDENTOF) { 1189 // special: all codes in the target code system under the value 1190 ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue()); 1191 if (def == null) 1192 throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'"); 1193 for (ConceptDefinitionComponent c : def.getConcept()) 1194 if (exclude) { 1195 excludeCodeAndDescendents(wc, cs, inc.getSystem(), c, null, imports, null, new AllConceptsFilter(allErrors), filters, exp); 1196 } else { 1197 addCodeAndDescendents(wc, cs, inc.getSystem(), c, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp); 1198 } 1199 if (def.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) { 1200 List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) def.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK); 1201 for (ConceptDefinitionComponent c : children) { 1202 if (exclude) { 1203 excludeCodeAndDescendents(wc, cs, inc.getSystem(), c, null, imports, null, new AllConceptsFilter(allErrors), filters, exp); 1204 } else { 1205 addCodeAndDescendents(wc, cs, inc.getSystem(), c, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp); 1206 } 1207 } 1208 } 1209 1210 } else if ("display".equals(fc.getProperty()) && fc.getOp() == FilterOperator.EQUAL) { 1211 // gg; note: wtf is this: if the filter is display=v, look up the code 'v', and see if it's display is 'v'? 1212 dwc.setCanBeHierarchy(false); 1213 ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue()); 1214 if (def != null) { 1215 if (isNotBlank(def.getDisplay()) && isNotBlank(fc.getValue())) { 1216 if (def.getDisplay().contains(fc.getValue()) && passesOtherFilters(filters, cs, def.getCode())) { 1217 for (String code : getCodesForConcept(def, expParams)) { 1218 opContext.deadCheck(); 1219 if (exclude) { 1220 excludeCode(wc, inc.getSystem(), code); 1221 } else { 1222 addCode(wc, inc.getSystem(), code, def.getDisplay(), cs.getLanguage(), null, def.getDesignation(), expParams, CodeSystemUtilities.isNotSelectable(cs, def), CodeSystemUtilities.isInactive(cs, def), 1223 imports, noInactive, false, exp.getProperty(), makeCSProps(def.getDefinition(), def.getProperty()), cs, null, def.getExtension(), null, exp, true); 1224 } 1225 } 1226 } 1227 } 1228 } 1229 } else if (CodeSystemUtilities.isDefinedProperty(cs, fc.getProperty())) { 1230 for (ConceptDefinitionComponent def : cs.getConcept()) { 1231 PropertyFilter pf = new PropertyFilter(allErrors, fc, CodeSystemUtilities.getPropertyDefinition(cs, fc.getProperty())); 1232 if (exclude) { 1233 excludeCodeAndDescendents(wc, cs, inc.getSystem(), def, null, imports, null, pf, filters, exp); 1234 } else { 1235 addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, pf, noInactive, exp.getProperty(), filters, exp); 1236 } 1237 } 1238 } else if (isKnownProperty(fc.getProperty(), cs)) { 1239 for (ConceptDefinitionComponent def : cs.getConcept()) { 1240 KnownPropertyFilter pf = new KnownPropertyFilter(allErrors, fc, fc.getProperty()); 1241 if (exclude) { 1242 excludeCodeAndDescendents(wc, cs, inc.getSystem(), def, null, imports, null, pf, filters, exp); 1243 } else { 1244 addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, pf, noInactive, exp.getProperty(), filters, exp); 1245 } 1246 } 1247 } else if ("code".equals(fc.getProperty()) && fc.getOp() == FilterOperator.REGEX) { 1248 for (ConceptDefinitionComponent def : cs.getConcept()) { 1249 if (exclude) { 1250 excludeCodeAndDescendents(wc, cs, inc.getSystem(), def, null, imports, null, new RegexFilter(allErrors, fc.getValue()), filters, exp); 1251 } else { 1252 addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, new RegexFilter(allErrors, fc.getValue()), noInactive, exp.getProperty(), filters, exp); 1253 } 1254 } 1255 } else { 1256 throw fail("Filter by property[" + fc.getProperty() + "] and op[" + fc.getOp() + "] is not supported yet"); 1257 } 1258 } 1259 1260 private boolean isKnownProperty(String property, CodeSystem cs) { 1261 return Utilities.existsInList(property, "notSelectable"); 1262 } 1263 1264 private List<ConceptDefinitionDesignationComponent> mergeDesignations(ConceptDefinitionComponent def, 1265 List<ConceptDefinitionDesignationComponent> list) { 1266 List<ConceptDefinitionDesignationComponent> res = new ArrayList<>(); 1267 if (def != null) { 1268 res.addAll(def.getDesignation()); 1269 } 1270 res.addAll(list); 1271 return res; 1272 } 1273 1274 1275 1276 private void addFragmentWarning(ValueSetExpansionComponent exp, CodeSystem cs) { 1277 String url = cs.getVersionedUrl(); 1278 for (ValueSetExpansionParameterComponent p : exp.getParameter()) { 1279 if ("fragment".equals(p.getName()) && p.hasValueUriType() && url.equals(p.getValue().primitiveValue())) { 1280 return; 1281 } 1282 } 1283 exp.addParameter().setName("fragment").setValue(new CanonicalType(url)); 1284 } 1285 1286 private void addExampleWarning(ValueSetExpansionComponent exp, CodeSystem cs) { 1287 String url = cs.getVersionedUrl(); 1288 for (ValueSetExpansionParameterComponent p : exp.getParameter()) { 1289 if ("example".equals(p.getName()) && p.hasValueUriType() && url.equals(p.getValue().primitiveValue())) { 1290 return; 1291 } 1292 } 1293 exp.addParameter().setName("example").setValue(new CanonicalType(url)); 1294 } 1295 1296 private List<ConceptDefinitionDesignationComponent> convertDesignations(List<ConceptReferenceDesignationComponent> list) { 1297 List<ConceptDefinitionDesignationComponent> res = new ArrayList<CodeSystem.ConceptDefinitionDesignationComponent>(); 1298 for (ConceptReferenceDesignationComponent t : list) { 1299 ConceptDefinitionDesignationComponent c = new ConceptDefinitionDesignationComponent(); 1300 c.setLanguage(t.getLanguage()); 1301 c.setUse(t.getUse()); 1302 c.setValue(t.getValue()); 1303 c.getExtension().addAll(t.getExtension()); 1304 res.add(c); 1305 } 1306 return res; 1307 } 1308 1309 private String key(String uri, String code) { 1310 return "{" + uri + "}" + code; 1311 } 1312 1313 private String key(ValueSetExpansionContainsComponent c) { 1314 return key(c.getSystem(), c.getCode()); 1315 } 1316 1317 private FHIRException fail(String msg) { 1318 allErrors.add(msg); 1319 return new FHIRException(msg); 1320 } 1321 1322 private ETooCostly failCostly(String msg) { 1323 allErrors.add(msg); 1324 return new ETooCostly(msg); 1325 } 1326 1327 private TerminologyServiceException failTSE(String msg) { 1328 allErrors.add(msg); 1329 return new TerminologyServiceException(msg); 1330 } 1331 1332 public Collection<? extends String> getAllErrors() { 1333 return allErrors; 1334 } 1335 1336 public boolean isCheckCodesWhenExpanding() { 1337 return checkCodesWhenExpanding; 1338 } 1339 1340 public void setCheckCodesWhenExpanding(boolean checkCodesWhenExpanding) { 1341 this.checkCodesWhenExpanding = checkCodesWhenExpanding; 1342 } 1343 1344 private boolean passesOtherFilters(List<WorkingContext> otherFilters, CodeSystem cs, String code) { 1345 if (otherFilters == null) { 1346 return true; 1347 } 1348 String key = key(cs.getUrl(), code); 1349 for (WorkingContext wc : otherFilters) { 1350 if (!wc.getMap().containsKey(key)) { 1351 return false; 1352 } 1353 } 1354 return true; 1355 } 1356 1357 public boolean isDebug() { 1358 return debug; 1359 } 1360 1361 public ValueSetExpander setDebug(boolean debug) { 1362 this.debug = debug; 1363 return this; 1364 } 1365 1366 1367}