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