![](/hapi-fhir/images/logos/raccoon-forwards.png)
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) 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.getExcludeKeys().contains(s)) { 323 return null; 324 } else if (wc.getMap().containsKey(s)) { 325 wc.setCanBeHierarchy(false); 326 } else { 327 wc.getCodes().add(n); 328 wc.getMap().put(s, n); 329// if (wc == dwc && wc.getTotal() > maxExpansionSize) { 330// if (wc.getOffset()+wc.getCount() > 0 && wc.getTotal() > wc.getOffset()+wc.getCount()) { 331// wc.setTotal(-1); 332// throw new EFinished(); 333// } 334// throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY, focus.getUrl(), ">" + Integer.toString(maxExpansionSize))); 335// } 336 } 337 if (wc.isCanBeHierarchy() && parent != null) { 338 parent.getContains().add(n); 339 } else if (!wc.getRootMap().containsKey(s)) { 340 wc.getRootMap().put(s, n); 341 wc.getRoots().add(n); 342 } 343 return n; 344 } 345 346 private DataType convertToDecimal(DataType v) { 347 if (v == null) { 348 return null; 349 } 350 if (v instanceof DecimalType) { 351 return v; 352 } 353 if (v instanceof IntegerType) { 354 return new DecimalType(((IntegerType) v).asStringValue()); 355 } 356 return null; 357 } 358 359 private boolean passesDesignationFilter(ConceptDefinitionDesignationComponent d) { 360 if (designations.isEmpty()) { 361 return true; 362 } 363 for (Token t : designations) { 364 if (t.matches(d.getUse()) || t.matchesLang(d.getLanguage())) { 365 return true; 366 } 367 for (Coding c : d.getAdditionalUse()) { 368 if (t.matches(c)) { 369 return true; 370 } 371 } 372 } 373 return false; 374 } 375 376 private ConceptDefinitionDesignationComponent findMatchingDesignation(List<ConceptDefinitionDesignationComponent> designations) { 377 if (langs == null) { 378 return null; 379 } 380 // we have a list of languages in priority order 381 // we have a list of designations in no order 382 // language exact match is preferred 383 // display is always preferred 384 385 for (LanguagePreference lang : langs.getLangs()) { 386 if (lang.getValue() > 0) { 387 for (ConceptDefinitionDesignationComponent cd : designations) { 388 if (isDisplay(cd) && LanguageUtils.langsMatchExact(cd.getLanguage(), lang.getLang())) { 389 return cd; 390 } 391 } 392 for (ConceptDefinitionDesignationComponent cd : designations) { 393 if (isDisplay(cd) && LanguageUtils.langsMatch(cd.getLanguage(), lang.getLang())) { 394 return cd; 395 } 396 } 397 for (ConceptDefinitionDesignationComponent cd : designations) { 398 if (LanguageUtils.langsMatchExact(cd.getLanguage(), lang.getLang())) { 399 return cd; 400 } 401 } 402 for (ConceptDefinitionDesignationComponent cd : designations) { 403 if (LanguageUtils.langsMatch(cd.getLanguage(), lang.getLang())) { 404 return cd; 405 } 406 } 407 } 408 } 409 return null; 410 } 411 412 private boolean isDisplay(ConceptDefinitionDesignationComponent cd) { 413 return cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display"); 414 } 415 416 private boolean filterContainsCode(List<ValueSet> filters, String system, String code, ValueSetExpansionComponent exp) { 417 for (ValueSet vse : filters) { 418 checkCanonical(exp, vse, focus); 419 if (expansionContainsCode(vse.getExpansion().getContains(), system, code)) 420 return true; 421 } 422 return false; 423 } 424 425 private boolean expansionContainsCode(List<ValueSetExpansionContainsComponent> contains, String system, String code) { 426 for (ValueSetExpansionContainsComponent cc : contains) { 427 if (system.equals(cc.getSystem()) && code.equals(cc.getCode())) 428 return true; 429 if (expansionContainsCode(cc.getContains(), system, code)) 430 return true; 431 } 432 return false; 433 } 434 435 private ConceptDefinitionDesignationComponent getMatchingLang(List<ConceptDefinitionDesignationComponent> list, AcceptLanguageHeader langs) { 436 for (ConceptDefinitionDesignationComponent t : list) { 437 if (LanguageUtils.langsMatchExact(langs, t.getLanguage())) { 438 return t; 439 } 440 } 441 for (ConceptDefinitionDesignationComponent t : list) { 442 if (LanguageUtils.langsMatch(langs, t.getLanguage())) { 443 return t; 444 } 445 } 446 return null; 447 } 448 449 private void addCodeAndDescendents(WorkingContext wc, ValueSetExpansionContainsComponent focus, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, ValueSet vsSrc, ValueSetExpansionComponent exp) throws FHIRException, ETooCostly { 450 opContext.deadCheck(); 451 focus.checkNoModifiers("Expansion.contains", "expanding"); 452 ValueSetExpansionContainsComponent np = null; 453 for (String code : getCodesForConcept(focus, expParams)) { 454 ValueSetExpansionContainsComponent t = addCode(wc, focus.getSystem(), code, focus.getDisplay(), vsSrc.getLanguage(), parent, 455 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); 456 if (np == null) { 457 np = t; 458 } 459 } 460 for (ValueSetExpansionContainsComponent c : focus.getContains()) 461 addCodeAndDescendents(wc, c, np, expParams, filters, noInactive, vsProps, vsSrc, exp); 462 } 463 464 private List<ConceptPropertyComponent> makeCSProps(String definition, List<ConceptPropertyComponent> list) { 465 List<ConceptPropertyComponent> res = new ArrayList<>(); 466 if (!Utilities.noString(definition)) { 467 res.add(new ConceptPropertyComponent("definition", new StringType(definition))); 468 } 469 if (list != null) { 470 res.addAll(list); 471 } 472 return res; 473 } 474 475 private List<String> getCodesForConcept(ValueSetExpansionContainsComponent focus, Parameters expParams) { 476 List<String> codes = new ArrayList<>(); 477 codes.add(focus.getCode()); 478 for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent p : focus.getProperty()) { 479 if ("alternateCode".equals(p.getCode()) && (altCodeParams.passes(p.getExtension())) && p.getValue().isPrimitive()) { 480 codes.add(p.getValue().primitiveValue()); 481 } 482 } 483 return codes; 484 } 485 486 private List<ConceptDefinitionDesignationComponent> convert(List<ConceptReferenceDesignationComponent> designations) { 487 List<ConceptDefinitionDesignationComponent> list = new ArrayList<ConceptDefinitionDesignationComponent>(); 488 for (ConceptReferenceDesignationComponent d : designations) { 489 ConceptDefinitionDesignationComponent n = new ConceptDefinitionDesignationComponent(); 490 n.setLanguage(d.getLanguage()); 491 n.setUse(d.getUse()); 492 n.setValue(d.getValue()); 493 list.add(n); 494 } 495 return list; 496 } 497 498 private void addCodeAndDescendents(WorkingContext wc, CodeSystem cs, String system, ConceptDefinitionComponent def, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters, 499 ConceptDefinitionComponent exclusion, ConceptFilter filterFunc, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, List<WorkingContext> otherFilters, ValueSetExpansionComponent exp) throws FHIRException, ETooCostly { 500 opContext.deadCheck(); 501 def.checkNoModifiers("Code in Code System", "expanding"); 502 if (exclusion != null) { 503 if (exclusion.getCode().equals(def.getCode())) 504 return; // excluded. 505 } 506 ValueSetExpansionContainsComponent np = null; 507 boolean abs = CodeSystemUtilities.isNotSelectable(cs, def); 508 boolean inc = CodeSystemUtilities.isInactive(cs, def); 509 boolean dep = CodeSystemUtilities.isDeprecated(cs, def, false); 510 if ((includeAbstract || !abs) && filterFunc.includeConcept(cs, def) && passesOtherFilters(otherFilters, cs, def.getCode())) { 511 for (String code : getCodesForConcept(def, expParams)) { 512 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); 513 if (np == null) { 514 np = t; 515 } 516 } 517 } 518 for (ConceptDefinitionComponent c : def.getConcept()) { 519 addCodeAndDescendents(wc, cs, system, c, np, expParams, filters, exclusion, filterFunc, noInactive, vsProps, otherFilters, exp); 520 } 521 if (def.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) { 522 List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) def.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK); 523 for (ConceptDefinitionComponent c : children) 524 addCodeAndDescendents(wc, cs, system, c, np, expParams, filters, exclusion, filterFunc, noInactive, vsProps, otherFilters, exp); 525 } 526 } 527 528 529 private void excludeCodeAndDescendents(WorkingContext wc, CodeSystem cs, String system, ConceptDefinitionComponent def, Parameters expParams, List<ValueSet> filters, 530 ConceptDefinitionComponent exclusion, ConceptFilter filterFunc, List<WorkingContext> otherFilters, ValueSetExpansionComponent exp) throws FHIRException, ETooCostly { 531 opContext.deadCheck(); 532 def.checkNoModifiers("Code in Code System", "expanding"); 533 if (exclusion != null) { 534 if (exclusion.getCode().equals(def.getCode())) 535 return; // excluded. 536 } 537 boolean abs = CodeSystemUtilities.isNotSelectable(cs, def); 538 if ((includeAbstract || !abs) && filterFunc.includeConcept(cs, def) && passesOtherFilters(otherFilters, cs, def.getCode())) { 539 for (String code : getCodesForConcept(def, expParams)) { 540 if (!(filters != null && !filters.isEmpty() && !filterContainsCode(filters, system, code, exp))) 541 excludeCode(wc, system, code); 542 } 543 } 544 for (ConceptDefinitionComponent c : def.getConcept()) { 545 excludeCodeAndDescendents(wc, cs, system, c, expParams, filters, exclusion, filterFunc, otherFilters, exp); 546 } 547 if (def.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) { 548 List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) def.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK); 549 for (ConceptDefinitionComponent c : children) 550 excludeCodeAndDescendents(wc, cs, system, c, expParams, filters, exclusion, filterFunc, otherFilters, exp); 551 } 552 } 553 554 555 private List<String> getCodesForConcept(ConceptDefinitionComponent focus, Parameters expParams) { 556 List<String> codes = new ArrayList<>(); 557 codes.add(focus.getCode()); 558 for (ConceptPropertyComponent p : focus.getProperty()) { 559 if ("alternateCode".equals(p.getCode()) && (altCodeParams.passes(p.getExtension())) && p.getValue().isPrimitive()) { 560 codes.add(p.getValue().primitiveValue()); 561 } 562 } 563 return codes; 564 } 565 566 private static boolean hasUse(ConceptPropertyComponent p, List<String> uses) { 567 for (Extension ext : p.getExtensionsByUrl(ToolingExtensions.EXT_CS_ALTERNATE_USE)) { 568 if (ext.hasValueCoding() && Utilities.existsInList(ext.getValueCoding().getCode(), uses)) { 569 return true; 570 } 571 } 572 return false; 573 } 574 575 576 577 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 { 578 if (expand != null) { 579 if (expand.getContains().size() > maxExpansionSize) 580 throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY, vsSrc.getUrl(), ">" + Integer.toString(expand.getContains().size()))); 581 for (ValueSetExpansionParameterComponent p : expand.getParameter()) { 582 if (!existsInParams(params, p.getName(), p.getValue())) 583 params.add(p); 584 } 585 586 copyImportContains(expand.getContains(), null, expParams, filters, noInactive, vsProps, vsSrc, exp); 587 } 588 } 589 590 private void excludeCode(WorkingContext wc, String theSystem, String theCode) { 591 ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent(); 592 n.setSystem(theSystem); 593 n.setCode(theCode); 594 String s = key(n); 595 wc.getExcludeKeys().add(s); 596 } 597 598 private void excludeCodes(WorkingContext wc, ConceptSetComponent exc, Parameters expParams, ValueSetExpansionComponent exp, ValueSet vs) throws FHIRException, FileNotFoundException, ETooCostly, IOException { 599 opContext.deadCheck(); 600 exc.checkNoModifiers("Compose.exclude", "expanding"); 601 if (exc.hasSystem() && exc.getConcept().size() == 0 && exc.getFilter().size() == 0) { 602 wc.getExcludeSystems().add(exc.getSystem()); 603 } 604 605 for (UriType imp : exc.getValueSet()) { 606 excludeCodes(wc, importValueSetForExclude(wc, imp.getValue(), exp, expParams, false, vs).getExpansion()); 607 } 608 609 CodeSystem cs = context.fetchSupplementedCodeSystem(exc.getSystem()); 610 if ((cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE) && context.supportsSystem(exc.getSystem(), opContext.getOptions().getFhirVersion())) { 611 ValueSetExpansionOutcome vse = context.expandVS(exc, false, false); 612 ValueSet valueset = vse.getValueset(); 613 if (valueset == null) 614 throw failTSE("Error Expanding ValueSet: "+vse.getError()); 615 excludeCodes(wc, valueset.getExpansion()); 616 return; 617 } 618 619 for (ConceptReferenceComponent c : exc.getConcept()) { 620 excludeCode(wc, exc.getSystem(), c.getCode()); 621 } 622 623 if (exc.getFilter().size() > 0) { 624 if (cs.getContent() == CodeSystemContentMode.FRAGMENT) { 625 addFragmentWarning(exp, cs); 626 } 627 List<WorkingContext> filters = new ArrayList<>(); 628 for (int i = 1; i < exc.getFilter().size(); i++) { 629 WorkingContext wc1 = new WorkingContext(); 630 filters.add(wc1); 631 processFilter(exc, exp, expParams, null, cs, false, exc.getFilter().get(i), wc1, null, true); 632 } 633 ConceptSetFilterComponent fc = exc.getFilter().get(0); 634 WorkingContext wc1 = dwc; 635 processFilter(exc, exp, expParams, null, cs, false, fc, wc1, filters, true); 636 } 637 } 638 639 private void excludeCodes(WorkingContext wc, ValueSetExpansionComponent expand) { 640 opContext.deadCheck(); 641 for (ValueSetExpansionContainsComponent c : expand.getContains()) { 642 excludeCode(wc, c.getSystem(), c.getCode()); 643 } 644 } 645 646 private boolean existsInParams(List<ValueSetExpansionParameterComponent> params, String name, DataType value) { 647 for (ValueSetExpansionParameterComponent p : params) { 648 if (p.getName().equals(name) && PrimitiveType.compareDeep(p.getValue(), value, false)) { 649 return true; 650 } 651 } 652 return false; 653 } 654 655 public ValueSetExpansionOutcome expand(ValueSet source, Parameters expParams) { 656 657 allErrors.clear(); 658 try { 659 opContext.seeContext(source.getVersionedUrl()); 660 661 return expandInternal(source, expParams); 662 } catch (NoTerminologyServiceException e) { 663 // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set 664 // that might fail too, but it might not, later. 665 return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.NOSERVICE, allErrors, false); 666 } catch (CodeSystemProviderExtension e) { 667 // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set 668 // that might fail too, but it might not, later. 669 return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.INTERNAL_ERROR, allErrors, false); 670 } catch (TerminologyServiceProtectionException e) { 671 if (opContext.isOriginal()) { 672 return new ValueSetExpansionOutcome(e.getMessage(), e.getError(), allErrors, false); 673 } else { 674 throw e; 675 } 676 } catch (ETooCostly e) { 677 return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.TOO_COSTLY, allErrors, false); 678 } catch (Exception e) { 679 if (debug) { 680 e.printStackTrace(); 681 } 682 return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.UNKNOWN, allErrors, e instanceof EFhirClientException || e instanceof TerminologyServiceException); 683 } 684 } 685 686 public ValueSetExpansionOutcome expandInternal(ValueSet source, Parameters expParams) throws FHIRException, FileNotFoundException, ETooCostly, IOException, CodeSystemProviderExtension { 687 return doExpand(source, expParams); 688 } 689 690 private void processParameter(String name, DataType value) { 691 if (Utilities.existsInList(name, "includeDesignations", "excludeNested", "activeOnly", "offset", "count")) { 692 focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name)); 693 focus.getExpansion().addParameter().setName(name).setValue(value); 694 } 695 if ("displayLanguage".equals(name)) { 696 this.langs = new AcceptLanguageHeader(value.primitiveValue(), true); 697 focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name)); 698 focus.getExpansion().addParameter().setName(name).setValue(new CodeType(value.primitiveValue())); 699 } 700 if ("designation".equals(name)) { 701 String[] v = value.primitiveValue().split("\\|"); 702 if (v.length != 2 || !Utilities.isAbsoluteUrl(v[0]) || Utilities.noString(v[1])) { 703 throw new NoTerminologyServiceException("Unable to understand designation parameter "+value.primitiveValue()); 704 } 705 this.designations.add(new Token(v[0], v[1])); 706 focus.getExpansion().addParameter().setName(name).setValue(new StringType(value.primitiveValue())); 707 } 708 if ("offset".equals(name) && value instanceof IntegerType) { 709 focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name)); 710 focus.getExpansion().addParameter().setName(name).setValue(value); 711 dwc.setOffsetParam(((IntegerType) value).getValue()); 712 if (dwc.getOffsetParam() < 0) { 713 dwc.setOffsetParam(0); 714 } 715 } 716 if ("count".equals(name)) { 717 focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name)); 718 focus.getExpansion().addParameter().setName(name).setValue(value); 719 dwc.setCountParam(((IntegerType) value).getValue()); 720 if (dwc.getCountParam() < 0) { 721 dwc.setCountParam(0); 722 } 723 } 724 } 725 726 public ValueSetExpansionOutcome doExpand(ValueSet source, Parameters expParams) throws FHIRException, ETooCostly, FileNotFoundException, IOException, CodeSystemProviderExtension { 727 if (expParams == null) 728 expParams = makeDefaultExpansion(); 729 altCodeParams.seeParameters(expParams); 730 altCodeParams.seeValueSet(source); 731 732 source.checkNoModifiers("ValueSet", "expanding"); 733 focus = source.copy(); 734 focus.setIdBase(null); 735 focus.setExpansion(new ValueSet.ValueSetExpansionComponent()); 736 focus.getExpansion().setTimestampElement(DateTimeType.now()); 737 focus.getExpansion().setIdentifier(Factory.createUUID()); 738 checkCanonical(focus.getExpansion(), focus, focus); 739 for (Extension ext : focus.getCompose().getExtensionsByUrl("http://hl7.org/fhir/tools/StructureDefinion/valueset-expansion-param")) { 740 processParameter(ext.getExtensionString("name"), ext.getExtensionByUrl("value").getValue()); 741 } 742 for (ParametersParameterComponent p : expParams.getParameter()) { 743 processParameter(p.getName(), p.getValue()); 744 } 745 for (Extension s : focus.getExtensionsByUrl(ExtensionConstants.EXT_VSSUPPLEMENT)) { 746 requiredSupplements.add(s.getValue().primitiveValue()); 747 } 748 if (langs == null && focus.hasLanguage()) { 749 langs = new AcceptLanguageHeader(focus.getLanguage(), true); 750 } 751 752 try { 753 if (source.hasCompose()) { 754// ExtensionsUtils.stripExtensions(focus.getCompose()); - disabled 23/05/2023 GDG - why was this ever thought to be a good idea? 755 handleCompose(source.getCompose(), focus.getExpansion(), expParams, source.getUrl(), focus.getExpansion().getExtension(), source); 756 } 757 } catch (EFinished e) { 758 // nothing - we intended to trap this here 759 } 760 761 if (dwc.getCount() > maxExpansionSize && dwc.getOffsetParam() + dwc.getCountParam() == 0) { 762 if (dwc.isNoTotal()) { 763 throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY, focus.getVersionedUrl(), ">" + MessageFormat.format("{0,number,#}", maxExpansionSize))); 764 } else { 765 throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY_COUNT, focus.getVersionedUrl(), ">" + MessageFormat.format("{0,number,#}", maxExpansionSize), MessageFormat.format("{0,number,#}", dwc.getCount()))); 766 } 767 } else if (dwc.isCanBeHierarchy() && ((dwc.getCountParam() == 0) || dwc.getCountParam() > dwc.getCodes().size())) { 768 for (ValueSetExpansionContainsComponent c : dwc.getRoots()) { 769 focus.getExpansion().getContains().add(c); 770 } 771 } else { 772 int i = 0; 773 int cc = 0; 774 for (ValueSetExpansionContainsComponent c : dwc.getCodes()) { 775 c.getContains().clear(); // make sure any hierarchy is wiped 776 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 777 if (dwc.getOffsetParam() == 0 || i >= dwc.getOffsetParam()) { 778 focus.getExpansion().getContains().add(c); 779 cc++; 780 if (cc == dwc.getCountParam()) { 781 break; 782 } 783 } 784 i++; 785 } 786 } 787 } 788 789 if (dwc.hasOffsetParam()) { 790 focus.getExpansion().setOffset(dwc.getOffsetParam()); 791 } 792 if (!dwc.isNoTotal()) { 793 focus.getExpansion().setTotal(dwc.getStatedTotal()); 794 } 795 if (!requiredSupplements.isEmpty()) { 796 return new ValueSetExpansionOutcome(context.formatMessagePlural(requiredSupplements.size(), I18nConstants.VALUESET_SUPPLEMENT_MISSING, CommaSeparatedStringBuilder.build(requiredSupplements)), TerminologyServiceErrorClass.BUSINESS_RULE, allErrors, false); 797 } 798 if (!expParams.hasParameter("includeDefinition") || !expParams.getParameterBool("includeDefinition")) { 799 focus.setCompose(null); 800 focus.getExtension().clear(); 801 focus.setPublisher(null); 802 focus.setDescription(null); 803 focus.setPurpose(null); 804 focus.getContact().clear(); 805 focus.setCopyright(null); 806 focus.setText(null); 807 } 808 return new ValueSetExpansionOutcome(focus); 809 } 810 811 812 private Parameters makeDefaultExpansion() { 813 Parameters res = new Parameters(); 814 res.addParameter("excludeNested", true); 815 res.addParameter("includeDesignations", false); 816 return res; 817 } 818 819 private ConceptDefinitionComponent getConceptForCode(List<ConceptDefinitionComponent> clist, String code) { 820 for (ConceptDefinitionComponent c : clist) { 821 if (code.equals(c.getCode())) 822 return c; 823 ConceptDefinitionComponent v = getConceptForCode(c.getConcept(), code); 824 if (v != null) 825 return v; 826 } 827 return null; 828 } 829 830 private void handleCompose(ValueSetComposeComponent compose, ValueSetExpansionComponent exp, Parameters expParams, String ctxt, List<Extension> extensions, ValueSet valueSet) 831 throws ETooCostly, FileNotFoundException, IOException, FHIRException, CodeSystemProviderExtension { 832 compose.checkNoModifiers("ValueSet.compose", "expanding"); 833 // Exclude comes first because we build up a map of things to exclude 834 for (ConceptSetComponent inc : compose.getExclude()) { 835 excludeCodes(dwc, inc, expParams, exp, valueSet); 836 } 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 // because if there's no total, we can't know if we got everything 903 dwc.setNoTotal(true); 904 } 905 for (ValueSetExpansionParameterComponent p : evs.getParameter()) { 906 if (!existsInParams(exp.getParameter(), p.getName(), p.getValue())) 907 exp.getParameter().add(p); 908 } 909 if (isValueSetUnionImports(valueSet)) { 910 copyExpansion(wc, evs.getContains()); 911 } 912 wc.setCanBeHierarchy(false); // if we're importing a value set, we have to be combining, so we won't try for a hierarchy 913 return vso.getValueset(); 914 } 915 916 917 private ValueSet importValueSetForExclude(WorkingContext wc, String value, ValueSetExpansionComponent exp, Parameters expParams, boolean noInactive, ValueSet valueSet) throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException, FHIRFormatError { 918 if (value == null) 919 throw fail("unable to find value set with no identity"); 920 ValueSet vs = context.findTxResource(ValueSet.class, value, valueSet); 921 if (vs == null) { 922 if (context.fetchResource(CodeSystem.class, value, valueSet) != null) { 923 throw fail("Cannot include value set "+value+" because it's actually a code system"); 924 } else { 925 throw fail("Unable to find imported value set " + value); 926 } 927 } 928 checkCanonical(exp, vs, focus); 929 if (noInactive) { 930 expParams = expParams.copy(); 931 expParams.addParameter("activeOnly", true); 932 } 933 ValueSetExpansionOutcome vso = new ValueSetExpander(context, opContext.copy(), allErrors).expand(vs, expParams); 934 if (vso.getError() != null) { 935 addErrors(vso.getAllErrors()); 936 throw fail("Unable to expand imported value set "+vs.getUrl()+": " + vso.getError()); 937 } 938 if (vs.hasVersion() || REPORT_VERSION_ANYWAY) { 939 UriType u = new UriType(vs.getUrl() + (vs.hasVersion() ? "|"+vs.getVersion() : "")); 940 if (!existsInParams(exp.getParameter(), "used-valueset", u)) 941 exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-valueset").setValue(u)); 942 } 943 for (Extension ex : vso.getValueset().getExpansion().getExtension()) { 944 if (ex.getUrl().equals(ToolingExtensions.EXT_EXP_TOOCOSTLY)) { 945 throw fail("Unable to expand imported value set "+vs.getUrl()+" for exclude: too costly"); 946 } 947 } 948 return vso.getValueset(); 949 } 950 951 protected boolean isValueSetUnionImports(ValueSet valueSet) { 952 PackageInformation p = valueSet.getSourcePackage(); 953 if (p != null) { 954 return p.getDate().before(new GregorianCalendar(2022, Calendar.MARCH, 31).getTime()); 955 } else { 956 return false; 957 } 958 } 959 960 public void copyExpansion(WorkingContext wc,List<ValueSetExpansionContainsComponent> list) { 961 opContext.deadCheck(); 962 for (ValueSetExpansionContainsComponent cc : list) { 963 ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent(); 964 n.setSystem(cc.getSystem()); 965 n.setCode(cc.getCode()); 966 n.setAbstract(cc.getAbstract()); 967 n.setInactive(cc.getInactive()); 968 n.setDisplay(cc.getDisplay()); 969 n.getDesignation().addAll(cc.getDesignation()); 970 971 String s = key(n); 972 if (!wc.getMap().containsKey(s) && !wc.getExcludeKeys().contains(s)) { 973 wc.getCodes().add(n); 974 wc.getMap().put(s, n); 975 } 976 copyExpansion(wc, cc.getContains()); 977 } 978 } 979 980 private void addErrors(List<String> errs) { 981 for (String s : errs) { 982 if (!allErrors.contains(s)) { 983 allErrors.add(s); 984 } 985 } 986 } 987 988 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 { 989 int count = 0; 990 opContext.deadCheck(); 991 for (ValueSetExpansionContainsComponent c : list) { 992 c.checkNoModifiers("Imported Expansion in Code System", "expanding"); 993 ValueSetExpansionContainsComponent np = addCode(dwc, c.getSystem(), c.getCode(), c.getDisplay(), vsSrc.getLanguage(), parent, null, expParams, c.getAbstract(), c.getInactive(), 994 filter, noInactive, false, vsProps, makeCSProps(c.getExtensionString(ToolingExtensions.EXT_DEFINITION), null), null, c.getProperty(), null, c.getExtension(), exp); 995 if (np != null) { 996 count++; 997 } 998 count = count + copyImportContains(c.getContains(), np, expParams, filter, noInactive, vsProps, vsSrc, exp); 999 } 1000 return count; 1001 } 1002 1003 private void includeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, boolean heirarchical, boolean noInactive, List<Extension> extensions, ValueSet valueSet) throws ETooCostly, FileNotFoundException, IOException, FHIRException, CodeSystemProviderExtension { 1004 opContext.deadCheck(); 1005 inc.checkNoModifiers("Compose.include", "expanding"); 1006 List<ValueSet> imports = new ArrayList<ValueSet>(); 1007 for (CanonicalType imp : inc.getValueSet()) { 1008 imports.add(importValueSet(dwc, imp.getValue(), exp, expParams, noInactive, valueSet)); 1009 } 1010 1011 if (!inc.hasSystem()) { 1012 if (imports.isEmpty()) // though this is not supposed to be the case 1013 return; 1014 ValueSet base = imports.get(0); 1015 checkCanonical(exp, base, focus); 1016 imports.remove(0); 1017 base.checkNoModifiers("Imported ValueSet", "expanding"); 1018 copyImportContains(base.getExpansion().getContains(), null, expParams, imports, noInactive, base.getExpansion().getProperty(), base, exp); 1019 } else { 1020 CodeSystem cs = context.fetchSupplementedCodeSystem(inc.getSystem()); 1021 if (ValueSetUtilities.isServerSide(inc.getSystem()) || (cs == null || (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT))) { 1022 doServerIncludeCodes(inc, heirarchical, exp, imports, expParams, extensions, noInactive, valueSet.getExpansion().getProperty()); 1023 } else { 1024 if (cs.hasUserData("supplements.installed")) { 1025 for (String s : cs.getUserString("supplements.installed").split("\\,")) { 1026 requiredSupplements.remove(s); 1027 } 1028 } 1029 doInternalIncludeCodes(inc, exp, expParams, imports, cs, noInactive, valueSet); 1030 } 1031 } 1032 } 1033 1034 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 { 1035 opContext.deadCheck(); 1036 CodeSystemProvider csp = CodeSystemProvider.factory(inc.getSystem()); 1037 if (csp != null) { 1038 csp.includeCodes(inc, heirarchical, exp, imports, expParams, extensions, noInactive, vsProps); 1039 return; 1040 } 1041 1042 ValueSetExpansionOutcome vso = context.expandVS(inc, heirarchical, noInactive); 1043 if (vso.getError() != null) { 1044 throw failTSE("Unable to expand imported value set: " + vso.getError()); 1045 } 1046 ValueSet vs = vso.getValueset(); 1047 if (vs.hasVersion() || REPORT_VERSION_ANYWAY) { 1048 UriType u = new UriType(vs.getUrl() + (vs.hasVersion() ? "|"+vs.getVersion() : "")); 1049 if (!existsInParams(exp.getParameter(), "used-valueset", u)) { 1050 exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-valueset").setValue(u)); 1051 } 1052 } 1053 if (vs.getExpansion().hasTotal()) { 1054 // 0 for now... dwc.incExtraCount(!vs.getExpansion().getTotal()); 1055 } else { 1056 dwc.setNoTotal(true); 1057 } 1058 for (ValueSetExpansionParameterComponent p : vso.getValueset().getExpansion().getParameter()) { 1059 if (!existsInParams(exp.getParameter(), p.getName(), p.getValue())) { 1060 exp.getParameter().add(p); 1061 } 1062 } 1063 for (Extension ex : vs.getExpansion().getExtension()) { 1064 if (Utilities.existsInList(ex.getUrl(), ToolingExtensions.EXT_EXP_TOOCOSTLY, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed")) { 1065 if (!ExtensionsUtils.hasExtension(extensions, ex.getUrl())) { 1066 extensions.add(ex); 1067 } 1068 } 1069 } 1070 for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) { 1071 addCodeAndDescendents(dwc, cc, null, expParams, imports, noInactive, vsProps, vs, exp); 1072 } 1073 } 1074 1075 1076 public void doInternalIncludeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, List<ValueSet> imports, CodeSystem cs, boolean noInactive, Resource vsSrc) throws NoTerminologyServiceException, TerminologyServiceException, FHIRException, ETooCostly { 1077 opContext.deadCheck(); 1078 if (cs == null) { 1079 if (context.isNoTerminologyServer()) 1080 throw failTSE("Unable to find code system " + inc.getSystem().toString()); 1081 else 1082 throw failTSE("Unable to find code system " + inc.getSystem().toString()); 1083 } 1084 checkCanonical(exp, cs, focus); 1085 cs.checkNoModifiers("Code System", "expanding"); 1086 if (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT) 1087 throw failTSE("Code system " + inc.getSystem().toString() + " is incomplete"); 1088 if (cs.hasVersion() || REPORT_VERSION_ANYWAY) { 1089 UriType u = new UriType(cs.getUrl() + (cs.hasVersion() ? "|"+cs.getVersion() : "")); 1090 if (!existsInParams(exp.getParameter(), "used-codesystem", u)) 1091 exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-codesystem").setValue(u)); 1092 if (cs.hasUserData("supplements.installed")) { 1093 for (String s : cs.getUserString("supplements.installed").split("\\,")) { 1094 u = new UriType(s); 1095 if (!existsInParams(exp.getParameter(), "used-supplement", u)) { 1096 exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("used-supplement").setValue(u)); 1097 } 1098 } 1099 } 1100 } 1101 if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) { 1102 // special case - add all the code system 1103 for (ConceptDefinitionComponent def : cs.getConcept()) { 1104 addCodeAndDescendents(dwc, cs, inc.getSystem(), def, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), null, exp); 1105 } 1106 if (cs.getContent() == CodeSystemContentMode.FRAGMENT) { 1107 addFragmentWarning(exp, cs); 1108 } 1109 if (cs.getContent() == CodeSystemContentMode.EXAMPLE) { 1110 addExampleWarning(exp, cs); 1111 } 1112 } 1113 1114 if (!inc.getConcept().isEmpty()) { 1115 dwc.setCanBeHierarchy(false); 1116 for (ConceptReferenceComponent c : inc.getConcept()) { 1117 c.checkNoModifiers("Code in Value Set", "expanding"); 1118 ConceptDefinitionComponent def = CodeSystemUtilities.findCodeOrAltCode(cs.getConcept(), c.getCode(), null); 1119 boolean inactive = false; // default is true if we're a fragment and 1120 boolean isAbstract = false; 1121 if (def == null) { 1122 if (cs.getContent() == CodeSystemContentMode.FRAGMENT) { 1123 addFragmentWarning(exp, cs); 1124 } else if (cs.getContent() == CodeSystemContentMode.EXAMPLE) { 1125 addExampleWarning(exp, cs); 1126 } else { 1127 if (checkCodesWhenExpanding) { 1128 throw failTSE("Unable to find code '" + c.getCode() + "' in code system " + cs.getUrl()); 1129 } 1130 } 1131 } else { 1132 def.checkNoModifiers("Code in Code System", "expanding"); 1133 inactive = CodeSystemUtilities.isInactive(cs, def); 1134 isAbstract = CodeSystemUtilities.isNotSelectable(cs, def); 1135 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())), 1136 expParams, isAbstract, inactive, imports, noInactive, false, exp.getProperty(), makeCSProps(def.getDefinition(), def.getProperty()), cs, null, def.getExtension(), c.getExtension(), exp); 1137 } 1138 } 1139 } 1140 if (inc.getFilter().size() > 0) { 1141 if (inc.getFilter().size() > 1) { 1142 dwc.setCanBeHierarchy(false); // which will be the case if we get around to supporting this 1143 } 1144 if (cs.getContent() == CodeSystemContentMode.FRAGMENT) { 1145 addFragmentWarning(exp, cs); 1146 } 1147 List<WorkingContext> filters = new ArrayList<>(); 1148 for (int i = 1; i < inc.getFilter().size(); i++) { 1149 WorkingContext wc = new WorkingContext(); 1150 filters.add(wc); 1151 processFilter(inc, exp, expParams, imports, cs, noInactive, inc.getFilter().get(i), wc, null, false); 1152 } 1153 ConceptSetFilterComponent fc = inc.getFilter().get(0); 1154 WorkingContext wc = dwc; 1155 processFilter(inc, exp, expParams, imports, cs, noInactive, fc, wc, filters, false); 1156 } 1157 } 1158 1159 private void processFilter(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, List<ValueSet> imports, CodeSystem cs, boolean noInactive, 1160 ConceptSetFilterComponent fc, WorkingContext wc, List<WorkingContext> filters, boolean exclude) 1161 throws ETooCostly { 1162 opContext.deadCheck(); 1163 if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISA) { 1164 // special: all codes in the target code system under the value 1165 ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue()); 1166 if (def == null) 1167 throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'"); 1168 if (exclude) { 1169 excludeCodeAndDescendents(wc, cs, inc.getSystem(), def, null, imports, null, new AllConceptsFilter(allErrors), filters, exp); 1170 } else { 1171 addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp); 1172 } 1173 } else if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISNOTA) { 1174 // special: all codes in the target code system that are not under the value 1175 ConceptDefinitionComponent defEx = getConceptForCode(cs.getConcept(), fc.getValue()); 1176 if (defEx == null) 1177 throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'"); 1178 for (ConceptDefinitionComponent def : cs.getConcept()) { 1179 if (exclude) { 1180 excludeCodeAndDescendents(wc, cs, inc.getSystem(), def, null, imports, defEx, new AllConceptsFilter(allErrors), filters, exp); 1181 } else { 1182 addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, defEx, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp); 1183 } 1184 } 1185 } else if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.DESCENDENTOF) { 1186 // special: all codes in the target code system under the value 1187 ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue()); 1188 if (def == null) 1189 throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'"); 1190 for (ConceptDefinitionComponent c : def.getConcept()) 1191 if (exclude) { 1192 excludeCodeAndDescendents(wc, cs, inc.getSystem(), c, null, imports, null, new AllConceptsFilter(allErrors), filters, exp); 1193 } else { 1194 addCodeAndDescendents(wc, cs, inc.getSystem(), c, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp); 1195 } 1196 if (def.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) { 1197 List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) def.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK); 1198 for (ConceptDefinitionComponent c : children) { 1199 if (exclude) { 1200 excludeCodeAndDescendents(wc, cs, inc.getSystem(), c, null, imports, null, new AllConceptsFilter(allErrors), filters, exp); 1201 } else { 1202 addCodeAndDescendents(wc, cs, inc.getSystem(), c, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp); 1203 } 1204 } 1205 } 1206 1207 } else if ("display".equals(fc.getProperty()) && fc.getOp() == FilterOperator.EQUAL) { 1208 // gg; note: wtf is this: if the filter is display=v, look up the code 'v', and see if it's display is 'v'? 1209 dwc.setCanBeHierarchy(false); 1210 ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue()); 1211 if (def != null) { 1212 if (isNotBlank(def.getDisplay()) && isNotBlank(fc.getValue())) { 1213 if (def.getDisplay().contains(fc.getValue()) && passesOtherFilters(filters, cs, def.getCode())) { 1214 for (String code : getCodesForConcept(def, expParams)) { 1215 opContext.deadCheck(); 1216 if (exclude) { 1217 excludeCode(wc, inc.getSystem(), code); 1218 } else { 1219 addCode(wc, inc.getSystem(), code, def.getDisplay(), cs.getLanguage(), null, def.getDesignation(), expParams, CodeSystemUtilities.isNotSelectable(cs, def), CodeSystemUtilities.isInactive(cs, def), 1220 imports, noInactive, false, exp.getProperty(), makeCSProps(def.getDefinition(), def.getProperty()), cs, null, def.getExtension(), null, exp); 1221 } 1222 } 1223 } 1224 } 1225 } 1226 } else if (CodeSystemUtilities.isDefinedProperty(cs, fc.getProperty())) { 1227 for (ConceptDefinitionComponent def : cs.getConcept()) { 1228 PropertyFilter pf = new PropertyFilter(allErrors, fc, CodeSystemUtilities.getPropertyDefinition(cs, fc.getProperty())); 1229 if (exclude) { 1230 excludeCodeAndDescendents(wc, cs, inc.getSystem(), def, null, imports, null, pf, filters, exp); 1231 } else { 1232 addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, pf, noInactive, exp.getProperty(), filters, exp); 1233 } 1234 } 1235 } else if (isKnownProperty(fc.getProperty(), cs)) { 1236 for (ConceptDefinitionComponent def : cs.getConcept()) { 1237 KnownPropertyFilter pf = new KnownPropertyFilter(allErrors, fc, fc.getProperty()); 1238 if (exclude) { 1239 excludeCodeAndDescendents(wc, cs, inc.getSystem(), def, null, imports, null, pf, filters, exp); 1240 } else { 1241 addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, pf, noInactive, exp.getProperty(), filters, exp); 1242 } 1243 } 1244 } else if ("code".equals(fc.getProperty()) && fc.getOp() == FilterOperator.REGEX) { 1245 for (ConceptDefinitionComponent def : cs.getConcept()) { 1246 if (exclude) { 1247 excludeCodeAndDescendents(wc, cs, inc.getSystem(), def, null, imports, null, new RegexFilter(allErrors, fc.getValue()), filters, exp); 1248 } else { 1249 addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, new RegexFilter(allErrors, fc.getValue()), noInactive, exp.getProperty(), filters, exp); 1250 } 1251 } 1252 } else { 1253 throw fail("Filter by property[" + fc.getProperty() + "] and op[" + fc.getOp() + "] is not supported yet"); 1254 } 1255 } 1256 1257 private boolean isKnownProperty(String property, CodeSystem cs) { 1258 return Utilities.existsInList(property, "notSelectable"); 1259 } 1260 1261 private List<ConceptDefinitionDesignationComponent> mergeDesignations(ConceptDefinitionComponent def, 1262 List<ConceptDefinitionDesignationComponent> list) { 1263 List<ConceptDefinitionDesignationComponent> res = new ArrayList<>(); 1264 if (def != null) { 1265 res.addAll(def.getDesignation()); 1266 } 1267 res.addAll(list); 1268 return res; 1269 } 1270 1271 1272 1273 private void addFragmentWarning(ValueSetExpansionComponent exp, CodeSystem cs) { 1274 String url = cs.getVersionedUrl(); 1275 for (ValueSetExpansionParameterComponent p : exp.getParameter()) { 1276 if ("fragment".equals(p.getName()) && p.hasValueUriType() && url.equals(p.getValue().primitiveValue())) { 1277 return; 1278 } 1279 } 1280 exp.addParameter().setName("fragment").setValue(new CanonicalType(url)); 1281 } 1282 1283 private void addExampleWarning(ValueSetExpansionComponent exp, CodeSystem cs) { 1284 String url = cs.getVersionedUrl(); 1285 for (ValueSetExpansionParameterComponent p : exp.getParameter()) { 1286 if ("example".equals(p.getName()) && p.hasValueUriType() && url.equals(p.getValue().primitiveValue())) { 1287 return; 1288 } 1289 } 1290 exp.addParameter().setName("example").setValue(new CanonicalType(url)); 1291 } 1292 1293 private List<ConceptDefinitionDesignationComponent> convertDesignations(List<ConceptReferenceDesignationComponent> list) { 1294 List<ConceptDefinitionDesignationComponent> res = new ArrayList<CodeSystem.ConceptDefinitionDesignationComponent>(); 1295 for (ConceptReferenceDesignationComponent t : list) { 1296 ConceptDefinitionDesignationComponent c = new ConceptDefinitionDesignationComponent(); 1297 c.setLanguage(t.getLanguage()); 1298 c.setUse(t.getUse()); 1299 c.setValue(t.getValue()); 1300 c.getExtension().addAll(t.getExtension()); 1301 res.add(c); 1302 } 1303 return res; 1304 } 1305 1306 private String key(String uri, String code) { 1307 return "{" + uri + "}" + code; 1308 } 1309 1310 private String key(ValueSetExpansionContainsComponent c) { 1311 return key(c.getSystem(), c.getCode()); 1312 } 1313 1314 private FHIRException fail(String msg) { 1315 allErrors.add(msg); 1316 return new FHIRException(msg); 1317 } 1318 1319 private ETooCostly failCostly(String msg) { 1320 allErrors.add(msg); 1321 return new ETooCostly(msg); 1322 } 1323 1324 private TerminologyServiceException failTSE(String msg) { 1325 allErrors.add(msg); 1326 return new TerminologyServiceException(msg); 1327 } 1328 1329 public Collection<? extends String> getAllErrors() { 1330 return allErrors; 1331 } 1332 1333 public boolean isCheckCodesWhenExpanding() { 1334 return checkCodesWhenExpanding; 1335 } 1336 1337 public void setCheckCodesWhenExpanding(boolean checkCodesWhenExpanding) { 1338 this.checkCodesWhenExpanding = checkCodesWhenExpanding; 1339 } 1340 1341 private boolean passesOtherFilters(List<WorkingContext> otherFilters, CodeSystem cs, String code) { 1342 if (otherFilters == null) { 1343 return true; 1344 } 1345 String key = key(cs.getUrl(), code); 1346 for (WorkingContext wc : otherFilters) { 1347 if (!wc.getMap().containsKey(key)) { 1348 return false; 1349 } 1350 } 1351 return true; 1352 } 1353 1354 public boolean isDebug() { 1355 return debug; 1356 } 1357 1358 public ValueSetExpander setDebug(boolean debug) { 1359 this.debug = debug; 1360 return this; 1361 } 1362 1363 1364}