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