001package org.hl7.fhir.r5.terminologies; 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.Collection; 070import java.util.HashMap; 071import java.util.HashSet; 072import java.util.List; 073import java.util.Map; 074import java.util.Set; 075 076import org.apache.commons.lang3.NotImplementedException; 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.model.BooleanType; 083import org.hl7.fhir.r5.model.CodeSystem; 084import org.hl7.fhir.r5.model.CodeSystem.CodeSystemContentMode; 085import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; 086import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent; 087import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent; 088import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent; 089import org.hl7.fhir.r5.model.CodeSystem.PropertyType; 090import org.hl7.fhir.r5.model.DataType; 091import org.hl7.fhir.r5.model.DateTimeType; 092import org.hl7.fhir.r5.model.Enumerations.FilterOperator; 093import org.hl7.fhir.r5.model.Extension; 094import org.hl7.fhir.r5.model.Factory; 095import org.hl7.fhir.r5.model.Parameters; 096import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent; 097import org.hl7.fhir.r5.model.PrimitiveType; 098import org.hl7.fhir.r5.model.UriType; 099import org.hl7.fhir.r5.model.ValueSet; 100import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent; 101import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceDesignationComponent; 102import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; 103import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent; 104import org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent; 105import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent; 106import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 107import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionParameterComponent; 108import org.hl7.fhir.r5.utils.ToolingExtensions; 109import org.hl7.fhir.utilities.Utilities; 110 111import com.google.errorprone.annotations.NoAllocation; 112 113public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetExpander { 114 115 public class PropertyFilter implements IConceptFilter { 116 117 private ConceptSetFilterComponent filter; 118 private PropertyComponent property; 119 120 public PropertyFilter(ConceptSetFilterComponent fc, PropertyComponent propertyDefinition) { 121 this.filter = fc; 122 this.property = propertyDefinition; 123 } 124 125 @Override 126 public boolean includeConcept(CodeSystem cs, ConceptDefinitionComponent def) { 127 ConceptPropertyComponent pc = getPropertyForConcept(def); 128 if (pc != null) { 129 String v = pc.getValue().isPrimitive() ? pc.getValue().primitiveValue() : null; 130 switch (filter.getOp()) { 131 case DESCENDENTOF: throw fail("not supported yet: "+filter.getOp().toCode()); 132 case EQUAL: return filter.getValue().equals(v); 133 case EXISTS: throw fail("not supported yet: "+filter.getOp().toCode()); 134 case GENERALIZES: throw fail("not supported yet: "+filter.getOp().toCode()); 135 case IN: throw fail("not supported yet: "+filter.getOp().toCode()); 136 case ISA: throw fail("not supported yet: "+filter.getOp().toCode()); 137 case ISNOTA: throw fail("not supported yet: "+filter.getOp().toCode()); 138 case NOTIN: throw fail("not supported yet: "+filter.getOp().toCode()); 139 case NULL: throw fail("not supported yet: "+filter.getOp().toCode()); 140 case REGEX: throw fail("not supported yet: "+filter.getOp().toCode()); 141 default: 142 throw fail("Shouldn't get here"); 143 } 144 } else if (property.getType() == PropertyType.BOOLEAN && filter.getOp() == FilterOperator.EQUAL) { 145 return "false".equals(filter.getValue()); 146 } else { 147 return false; 148 } 149 } 150 151 private ConceptPropertyComponent getPropertyForConcept(ConceptDefinitionComponent def) { 152 for (ConceptPropertyComponent pc : def.getProperty()) { 153 if (pc.getCode().equals(property.getCode())) { 154 return pc; 155 } 156 } 157 return null; 158 } 159 160 } 161 162 public class AllConceptsFilter implements IConceptFilter { 163 164 @Override 165 public boolean includeConcept(CodeSystem cs, ConceptDefinitionComponent def) { 166 return true; 167 } 168 } 169 170 public interface IConceptFilter { 171 172 boolean includeConcept(CodeSystem cs, ConceptDefinitionComponent def); 173 174 } 175 176 private List<ValueSetExpansionContainsComponent> codes = new ArrayList<ValueSet.ValueSetExpansionContainsComponent>(); 177 private List<ValueSetExpansionContainsComponent> roots = new ArrayList<ValueSet.ValueSetExpansionContainsComponent>(); 178 private Map<String, ValueSetExpansionContainsComponent> map = new HashMap<String, ValueSet.ValueSetExpansionContainsComponent>(); 179 private IWorkerContext context; 180 private boolean canBeHeirarchy = true; 181 private boolean includeAbstract = true; 182 private Set<String> excludeKeys = new HashSet<String>(); 183 private Set<String> excludeSystems = new HashSet<String>(); 184 private ValueSet focus; 185 private int maxExpansionSize = 500; 186 private List<String> allErrors = new ArrayList<>(); 187 188 private int total; 189 private boolean checkCodesWhenExpanding; 190 191 public ValueSetExpanderSimple(IWorkerContext context) { 192 super(); 193 this.context = context; 194 } 195 196 public ValueSetExpanderSimple(IWorkerContext context, List<String> allErrors) { 197 super(); 198 this.context = context; 199 this.allErrors = allErrors; 200 } 201 202 public void setMaxExpansionSize(int theMaxExpansionSize) { 203 maxExpansionSize = theMaxExpansionSize; 204 } 205 206 private ValueSetExpansionContainsComponent addCode(String system, String code, String display, ValueSetExpansionContainsComponent parent, List<ConceptDefinitionDesignationComponent> designations, Parameters expParams, 207 boolean isAbstract, boolean inactive, List<ValueSet> filters, boolean noInactive) { 208 209 if (filters != null && !filters.isEmpty() && !filterContainsCode(filters, system, code)) 210 return null; 211 if (noInactive && inactive) { 212 return null; 213 } 214 215 ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent(); 216 n.setSystem(system); 217 n.setCode(code); 218 if (isAbstract) 219 n.setAbstract(true); 220 if (inactive) 221 n.setInactive(true); 222 223 if (expParams.getParameterBool("includeDesignations") && designations != null) { 224 for (ConceptDefinitionDesignationComponent t : designations) { 225 ToolingExtensions.addLanguageTranslation(n, t.getLanguage(), t.getValue()); 226 } 227 } 228 ConceptDefinitionDesignationComponent t = expParams.hasLanguage() ? getMatchingLang(designations, expParams.getLanguage()) : null; 229 if (t == null) 230 n.setDisplay(display); 231 else 232 n.setDisplay(t.getValue()); 233 234 String s = key(n); 235 if (map.containsKey(s) || excludeKeys.contains(s)) { 236 canBeHeirarchy = false; 237 } else { 238 codes.add(n); 239 map.put(s, n); 240 total++; 241 } 242 if (canBeHeirarchy && parent != null) { 243 parent.getContains().add(n); 244 } else { 245 roots.add(n); 246 } 247 return n; 248 } 249 250 private boolean filterContainsCode(List<ValueSet> filters, String system, String code) { 251 for (ValueSet vse : filters) 252 if (expansionContainsCode(vse.getExpansion().getContains(), system, code)) 253 return true; 254 return false; 255 } 256 257 private boolean expansionContainsCode(List<ValueSetExpansionContainsComponent> contains, String system, String code) { 258 for (ValueSetExpansionContainsComponent cc : contains) { 259 if (system.equals(cc.getSystem()) && code.equals(cc.getCode())) 260 return true; 261 if (expansionContainsCode(cc.getContains(), system, code)) 262 return true; 263 } 264 return false; 265 } 266 267 private ConceptDefinitionDesignationComponent getMatchingLang(List<ConceptDefinitionDesignationComponent> list, String lang) { 268 for (ConceptDefinitionDesignationComponent t : list) 269 if (t.getLanguage().equals(lang)) 270 return t; 271 for (ConceptDefinitionDesignationComponent t : list) 272 if (t.getLanguage().startsWith(lang)) 273 return t; 274 return null; 275 } 276 277 private void addCodeAndDescendents(ValueSetExpansionContainsComponent focus, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters, boolean noInactive) throws FHIRException { 278 focus.checkNoModifiers("Expansion.contains", "expanding"); 279 ValueSetExpansionContainsComponent np = addCode(focus.getSystem(), focus.getCode(), focus.getDisplay(), parent, 280 convert(focus.getDesignation()), expParams, focus.getAbstract(), focus.getInactive(), filters, noInactive); 281 for (ValueSetExpansionContainsComponent c : focus.getContains()) 282 addCodeAndDescendents(focus, np, expParams, filters, noInactive); 283 } 284 285 private List<ConceptDefinitionDesignationComponent> convert(List<ConceptReferenceDesignationComponent> designations) { 286 List<ConceptDefinitionDesignationComponent> list = new ArrayList<ConceptDefinitionDesignationComponent>(); 287 for (ConceptReferenceDesignationComponent d : designations) { 288 ConceptDefinitionDesignationComponent n = new ConceptDefinitionDesignationComponent(); 289 n.setLanguage(d.getLanguage()); 290 n.setUse(d.getUse()); 291 n.setValue(d.getValue()); 292 list.add(n); 293 } 294 return list; 295 } 296 297 private void addCodeAndDescendents(CodeSystem cs, String system, ConceptDefinitionComponent def, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters, ConceptDefinitionComponent exclusion, IConceptFilter filterFunc, boolean noInactive) throws FHIRException { 298 def.checkNoModifiers("Code in Code System", "expanding"); 299 if (exclusion != null) { 300 if (exclusion.getCode().equals(def.getCode())) 301 return; // excluded. 302 } 303 if (!CodeSystemUtilities.isDeprecated(cs, def, false)) { 304 ValueSetExpansionContainsComponent np = null; 305 boolean abs = CodeSystemUtilities.isNotSelectable(cs, def); 306 boolean inc = CodeSystemUtilities.isInactive(cs, def); 307 if ((includeAbstract || !abs) && filterFunc.includeConcept(cs, def)) { 308 np = addCode(system, def.getCode(), def.getDisplay(), parent, def.getDesignation(), expParams, abs, inc, filters, noInactive); 309 } 310 for (ConceptDefinitionComponent c : def.getConcept()) { 311 addCodeAndDescendents(cs, system, c, np, expParams, filters, exclusion, filterFunc, noInactive); 312 } 313 if (def.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) { 314 List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) def.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK); 315 for (ConceptDefinitionComponent c : children) 316 addCodeAndDescendents(cs, system, c, np, expParams, filters, exclusion, filterFunc, noInactive); 317 } 318 } else { 319 for (ConceptDefinitionComponent c : def.getConcept()) { 320 addCodeAndDescendents(cs, system, c, null, expParams, filters, exclusion, filterFunc, noInactive); 321 } 322 if (def.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) { 323 List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) def.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK); 324 for (ConceptDefinitionComponent c : children) 325 addCodeAndDescendents(cs, system, c, null, expParams, filters, exclusion, filterFunc, noInactive); 326 } 327 } 328 329 } 330 331 private void addCodes(ValueSetExpansionComponent expand, List<ValueSetExpansionParameterComponent> params, Parameters expParams, List<ValueSet> filters, boolean noInactive) throws ETooCostly, FHIRException { 332 if (expand != null) { 333 if (expand.getContains().size() > maxExpansionSize) 334 throw failCostly("Too many codes to display (>" + Integer.toString(expand.getContains().size()) + ")"); 335 for (ValueSetExpansionParameterComponent p : expand.getParameter()) { 336 if (!existsInParams(params, p.getName(), p.getValue())) 337 params.add(p); 338 } 339 340 copyImportContains(expand.getContains(), null, expParams, filters, noInactive); 341 } 342 } 343 344 private void excludeCode(String theSystem, String theCode) { 345 ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent(); 346 n.setSystem(theSystem); 347 n.setCode(theCode); 348 String s = key(n); 349 excludeKeys.add(s); 350 } 351 352 private void excludeCodes(ConceptSetComponent exc, List<ValueSetExpansionParameterComponent> params, String ctxt) throws FHIRException { 353 exc.checkNoModifiers("Compose.exclude", "expanding"); 354 if (exc.hasSystem() && exc.getConcept().size() == 0 && exc.getFilter().size() == 0) { 355 excludeSystems.add(exc.getSystem()); 356 } 357 358 if (exc.hasValueSet()) 359 throw fail("Processing Value set references in exclude is not yet done in "+ctxt); 360 // importValueSet(imp.getValue(), params, expParams); 361 362 CodeSystem cs = context.fetchCodeSystem(exc.getSystem()); 363 if ((cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE) && context.supportsSystem(exc.getSystem())) { 364 ValueSetExpansionOutcome vse = context.expandVS(exc, false, false); 365 ValueSet valueset = vse.getValueset(); 366 if (valueset == null) 367 throw failTSE("Error Expanding ValueSet: "+vse.getError()); 368 excludeCodes(valueset.getExpansion(), params); 369 return; 370 } 371 372 for (ConceptReferenceComponent c : exc.getConcept()) { 373 excludeCode(exc.getSystem(), c.getCode()); 374 } 375 376 if (exc.getFilter().size() > 0) 377 throw fail("not done yet - multiple filters"); 378 } 379 380 381 382 private void excludeCodes(ValueSetExpansionComponent expand, List<ValueSetExpansionParameterComponent> params) { 383 for (ValueSetExpansionContainsComponent c : expand.getContains()) { 384 excludeCode(c.getSystem(), c.getCode()); 385 } 386 } 387 388 private boolean existsInParams(List<ValueSetExpansionParameterComponent> params, String name, DataType value) { 389 for (ValueSetExpansionParameterComponent p : params) { 390 if (p.getName().equals(name) && PrimitiveType.compareDeep(p.getValue(), value, false)) 391 return true; 392 } 393 return false; 394 } 395 396 @Override 397 public ValueSetExpansionOutcome expand(ValueSet source, Parameters expParams) { 398 allErrors.clear(); 399 try { 400 return expandInternal(source, expParams); 401 } catch (NoTerminologyServiceException e) { 402 // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set 403 // that might fail too, but it might not, later. 404 return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.NOSERVICE, allErrors); 405 } catch (Exception e) { 406 // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set 407 // that might fail too, but it might not, later. 408 return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.UNKNOWN, allErrors); 409 } 410 } 411 412 public ValueSetExpansionOutcome expandInternal(ValueSet source, Parameters expParams) throws FHIRException, FileNotFoundException, ETooCostly, IOException { 413 return doExpand(source, expParams); 414 } 415 416 public ValueSetExpansionOutcome doExpand(ValueSet source, Parameters expParams) throws FHIRException, ETooCostly, FileNotFoundException, IOException { 417 if (expParams == null) 418 expParams = makeDefaultExpansion(); 419 source.checkNoModifiers("ValueSet", "expanding"); 420 focus = source.copy(); 421 focus.setExpansion(new ValueSet.ValueSetExpansionComponent()); 422 focus.getExpansion().setTimestampElement(DateTimeType.now()); 423 focus.getExpansion().setIdentifier(Factory.createUUID()); 424 for (ParametersParameterComponent p : expParams.getParameter()) { 425 if (Utilities.existsInList(p.getName(), "includeDesignations", "excludeNested")) 426 focus.getExpansion().addParameter().setName(p.getName()).setValue(p.getValue()); 427 } 428 429 if (source.hasCompose()) 430 handleCompose(source.getCompose(), focus.getExpansion(), expParams, source.getUrl(), focus.getExpansion().getExtension()); 431 432 if (canBeHeirarchy) { 433 for (ValueSetExpansionContainsComponent c : roots) { 434 focus.getExpansion().getContains().add(c); 435 } 436 } else { 437 for (ValueSetExpansionContainsComponent c : codes) { 438 if (map.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 439 focus.getExpansion().getContains().add(c); 440 c.getContains().clear(); // make sure any heirarchy is wiped 441 } 442 } 443 } 444 445 if (total > 0) { 446 focus.getExpansion().setTotal(total); 447 } 448 449 return new ValueSetExpansionOutcome(focus); 450 } 451 452 private Parameters makeDefaultExpansion() { 453 Parameters res = new Parameters(); 454 res.addParameter("excludeNested", true); 455 res.addParameter("includeDesignations", false); 456 return res; 457 } 458 459 private ConceptDefinitionComponent getConceptForCode(List<ConceptDefinitionComponent> clist, String code) { 460 for (ConceptDefinitionComponent c : clist) { 461 if (code.equals(c.getCode())) 462 return c; 463 ConceptDefinitionComponent v = getConceptForCode(c.getConcept(), code); 464 if (v != null) 465 return v; 466 } 467 return null; 468 } 469 470 private void handleCompose(ValueSetComposeComponent compose, ValueSetExpansionComponent exp, Parameters expParams, String ctxt, List<Extension> extensions) 471 throws ETooCostly, FileNotFoundException, IOException, FHIRException { 472 compose.checkNoModifiers("ValueSet.compose", "expanding"); 473 // Exclude comes first because we build up a map of things to exclude 474 for (ConceptSetComponent inc : compose.getExclude()) 475 excludeCodes(inc, exp.getParameter(), ctxt); 476 canBeHeirarchy = !expParams.getParameterBool("excludeNested") && excludeKeys.isEmpty() && excludeSystems.isEmpty(); 477 includeAbstract = !expParams.getParameterBool("excludeNotForUI"); 478 boolean first = true; 479 for (ConceptSetComponent inc : compose.getInclude()) { 480 if (first == true) 481 first = false; 482 else 483 canBeHeirarchy = false; 484 includeCodes(inc, exp, expParams, canBeHeirarchy, compose.hasInactive() && !compose.getInactive(), extensions); 485 } 486 } 487 488 private ValueSet importValueSet(String value, ValueSetExpansionComponent exp, Parameters expParams, boolean noInactive) throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException, FHIRFormatError { 489 if (value == null) 490 throw fail("unable to find value set with no identity"); 491 ValueSet vs = context.fetchResource(ValueSet.class, value); 492 if (vs == null) { 493 if (context.fetchResource(CodeSystem.class, value) != null) { 494 throw fail("Cannot include value set "+value+" because it's actually a code system"); 495 } else { 496 throw fail("Unable to find imported value set " + value); 497 } 498 } 499 if (noInactive) { 500 expParams = expParams.copy(); 501 expParams.addParameter("activeOnly", true); 502 } 503 ValueSetExpansionOutcome vso = new ValueSetExpanderSimple(context, allErrors).expand(vs, expParams); 504 if (vso.getError() != null) { 505 addErrors(vso.getAllErrors()); 506 throw fail("Unable to expand imported value set "+vs.getUrl()+": " + vso.getError()); 507 } 508 if (vs.hasVersion()) 509 if (!existsInParams(exp.getParameter(), "version", new UriType(vs.getUrl() + "|" + vs.getVersion()))) 510 exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(vs.getUrl() + "|" + vs.getVersion()))); 511 for (Extension ex : vso.getValueset().getExpansion().getExtension()) { 512 if (ex.getUrl().equals(ToolingExtensions.EXT_EXP_TOOCOSTLY)) { 513 if (ex.getValue() instanceof BooleanType) { 514 exp.getExtension().add(new Extension(ToolingExtensions.EXT_EXP_TOOCOSTLY).setValue(new UriType(value))); 515 } else { 516 exp.getExtension().add(ex); 517 } 518 } 519 } 520 for (ValueSetExpansionParameterComponent p : vso.getValueset().getExpansion().getParameter()) { 521 if (!existsInParams(exp.getParameter(), p.getName(), p.getValue())) 522 exp.getParameter().add(p); 523 } 524 copyExpansion(vso.getValueset().getExpansion().getContains()); 525 canBeHeirarchy = false; // if we're importing a value set, we have to be combining, so we won't try for a heirarchy 526 return vso.getValueset(); 527 } 528 529 public void copyExpansion(List<ValueSetExpansionContainsComponent> list) { 530 for (ValueSetExpansionContainsComponent cc : list) { 531 ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent(); 532 n.setSystem(cc.getSystem()); 533 n.setCode(cc.getCode()); 534 n.setAbstract(cc.getAbstract()); 535 n.setInactive(cc.getInactive()); 536 n.setDisplay(cc.getDisplay()); 537 n.getDesignation().addAll(cc.getDesignation()); 538 539 String s = key(n); 540 if (!map.containsKey(s) && !excludeKeys.contains(s)) { 541 codes.add(n); 542 map.put(s, n); 543 total++; 544 } 545 copyExpansion(cc.getContains()); 546 } 547 } 548 549 private void addErrors(List<String> errs) { 550 for (String s : errs) { 551 if (!allErrors.contains(s)) { 552 allErrors.add(s); 553 } 554 } 555 } 556 557 private void copyImportContains(List<ValueSetExpansionContainsComponent> list, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filter, boolean noInactive) throws FHIRException { 558 for (ValueSetExpansionContainsComponent c : list) { 559 c.checkNoModifiers("Imported Expansion in Code System", "expanding"); 560 ValueSetExpansionContainsComponent np = addCode(c.getSystem(), c.getCode(), c.getDisplay(), parent, null, expParams, c.getAbstract(), c.getInactive(), filter, noInactive); 561 copyImportContains(c.getContains(), np, expParams, filter, noInactive); 562 } 563 } 564 565 private void includeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, boolean heirarchical, boolean noInactive, List<Extension> extensions) throws ETooCostly, FileNotFoundException, IOException, FHIRException { 566 inc.checkNoModifiers("Compose.include", "expanding"); 567 List<ValueSet> imports = new ArrayList<ValueSet>(); 568 for (UriType imp : inc.getValueSet()) { 569 imports.add(importValueSet(imp.getValue(), exp, expParams, noInactive)); 570 } 571 572 if (!inc.hasSystem()) { 573 if (imports.isEmpty()) // though this is not supposed to be the case 574 return; 575 ValueSet base = imports.get(0); 576 imports.remove(0); 577 base.checkNoModifiers("Imported ValueSet", "expanding"); 578 copyImportContains(base.getExpansion().getContains(), null, expParams, imports, noInactive); 579 } else { 580 CodeSystem cs = context.fetchCodeSystem(inc.getSystem()); 581 if (isServerSide(inc.getSystem()) || (cs == null || (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT))) { 582 doServerIncludeCodes(inc, heirarchical, exp, imports, expParams, extensions, noInactive); 583 } else { 584 doInternalIncludeCodes(inc, exp, expParams, imports, cs, noInactive); 585 } 586 } 587 } 588 589 private void doServerIncludeCodes(ConceptSetComponent inc, boolean heirarchical, ValueSetExpansionComponent exp, List<ValueSet> imports, Parameters expParams, List<Extension> extensions, boolean noInactive) throws FHIRException { 590 ValueSetExpansionOutcome vso = context.expandVS(inc, heirarchical, noInactive); 591 if (vso.getError() != null) { 592 throw failTSE("Unable to expand imported value set: " + vso.getError()); 593 } 594 ValueSet vs = vso.getValueset(); 595 if (vs.hasVersion()) { 596 if (!existsInParams(exp.getParameter(), "version", new UriType(vs.getUrl() + "|" + vs.getVersion()))) { 597 exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(vs.getUrl() + "|" + vs.getVersion()))); 598 } 599 } 600 for (ValueSetExpansionParameterComponent p : vso.getValueset().getExpansion().getParameter()) { 601 if (!existsInParams(exp.getParameter(), p.getName(), p.getValue())) { 602 exp.getParameter().add(p); 603 } 604 } 605 for (Extension ex : vs.getExpansion().getExtension()) { 606 if (Utilities.existsInList(ex.getUrl(), ToolingExtensions.EXT_EXP_TOOCOSTLY, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed")) { 607 if (!hasExtension(extensions, ex.getUrl())) { 608 extensions.add(ex); 609 } 610 } 611 } 612 for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) { 613 addCodeAndDescendents(cc, null, expParams, imports, noInactive); 614 } 615 } 616 617 private boolean hasExtension(List<Extension> extensions, String url) { 618 for (Extension ex : extensions) { 619 if (ex.getUrl().equals(url)) { 620 return true; 621 } 622 } 623 return false; 624 } 625 626 public void doInternalIncludeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, List<ValueSet> imports, CodeSystem cs, boolean noInactive) throws NoTerminologyServiceException, TerminologyServiceException, FHIRException { 627 if (cs == null) { 628 if (context.isNoTerminologyServer()) 629 throw failTSE("Unable to find code system " + inc.getSystem().toString()); 630 else 631 throw failTSE("Unable to find code system " + inc.getSystem().toString()); 632 } 633 cs.checkNoModifiers("Code System", "expanding"); 634 if (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT) 635 throw failTSE("Code system " + inc.getSystem().toString() + " is incomplete"); 636 if (cs.hasVersion()) 637 if (!existsInParams(exp.getParameter(), "version", new UriType(cs.getUrl() + "|" + cs.getVersion()))) 638 exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(cs.getUrl() + "|" + cs.getVersion()))); 639 640 if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) { 641 // special case - add all the code system 642 for (ConceptDefinitionComponent def : cs.getConcept()) { 643 addCodeAndDescendents(cs, inc.getSystem(), def, null, expParams, imports, null, new AllConceptsFilter(), noInactive); 644 } 645 if (cs.getContent() == CodeSystemContentMode.FRAGMENT) { 646 addFragmentWarning(exp, cs); 647 } 648 if (cs.getContent() == CodeSystemContentMode.EXAMPLE) { 649 addExampleWarning(exp, cs); 650 } 651 } 652 653 if (!inc.getConcept().isEmpty()) { 654 canBeHeirarchy = false; 655 for (ConceptReferenceComponent c : inc.getConcept()) { 656 c.checkNoModifiers("Code in Code System", "expanding"); 657 ConceptDefinitionComponent def = CodeSystemUtilities.findCode(cs.getConcept(), c.getCode()); 658 Boolean inactive = false; // default is true if we're a fragment and 659 if (def == null) { 660 if (cs.getContent() == CodeSystemContentMode.FRAGMENT) { 661 addFragmentWarning(exp, cs); 662 } else if (cs.getContent() == CodeSystemContentMode.EXAMPLE) { 663 addExampleWarning(exp, cs); 664 } else { 665 if (checkCodesWhenExpanding) { 666 throw failTSE("Unable to find code '" + c.getCode() + "' in code system " + cs.getUrl()); 667 } 668 } 669 } else { 670 inactive = CodeSystemUtilities.isInactive(cs, def); 671 } 672 addCode(inc.getSystem(), c.getCode(), !Utilities.noString(c.getDisplay()) ? c.getDisplay() : def == null ? null : def.getDisplay(), null, convertDesignations(c.getDesignation()), expParams, false, inactive, imports, noInactive); 673 } 674 } 675 if (inc.getFilter().size() > 1) { 676 canBeHeirarchy = false; // which will bt the case if we get around to supporting this 677 throw failTSE("Multiple filters not handled yet"); // need to and them, and this isn't done yet. But this shouldn't arise in non loinc and snomed value sets 678 } 679 if (inc.getFilter().size() == 1) { 680 if (cs.getContent() == CodeSystemContentMode.FRAGMENT) { 681 addFragmentWarning(exp, cs); 682 } 683 ConceptSetFilterComponent fc = inc.getFilter().get(0); 684 if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISA) { 685 // special: all codes in the target code system under the value 686 ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue()); 687 if (def == null) 688 throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'"); 689 addCodeAndDescendents(cs, inc.getSystem(), def, null, expParams, imports, null, new AllConceptsFilter(), noInactive); 690 } else if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISNOTA) { 691 // special: all codes in the target code system that are not under the value 692 ConceptDefinitionComponent defEx = getConceptForCode(cs.getConcept(), fc.getValue()); 693 if (defEx == null) 694 throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'"); 695 for (ConceptDefinitionComponent def : cs.getConcept()) { 696 addCodeAndDescendents(cs, inc.getSystem(), def, null, expParams, imports, defEx, new AllConceptsFilter(), noInactive); 697 } 698 } else if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.DESCENDENTOF) { 699 // special: all codes in the target code system under the value 700 ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue()); 701 if (def == null) 702 throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'"); 703 for (ConceptDefinitionComponent c : def.getConcept()) 704 addCodeAndDescendents(cs, inc.getSystem(), c, null, expParams, imports, null, new AllConceptsFilter(), noInactive); 705 if (def.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) { 706 List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) def.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK); 707 for (ConceptDefinitionComponent c : children) 708 addCodeAndDescendents(cs, inc.getSystem(), c, null, expParams, imports, null, new AllConceptsFilter(), noInactive); 709 } 710 711 } else if ("display".equals(fc.getProperty()) && fc.getOp() == FilterOperator.EQUAL) { 712 // gg; note: wtf is this: if the filter is display=v, look up the code 'v', and see if it's diplsay is 'v'? 713 canBeHeirarchy = false; 714 ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue()); 715 if (def != null) { 716 if (isNotBlank(def.getDisplay()) && isNotBlank(fc.getValue())) { 717 if (def.getDisplay().contains(fc.getValue())) { 718 addCode(inc.getSystem(), def.getCode(), def.getDisplay(), null, def.getDesignation(), expParams, CodeSystemUtilities.isNotSelectable(cs, def), CodeSystemUtilities.isInactive(cs, def), 719 imports, noInactive); 720 } 721 } 722 } 723 } else if (isDefinedProperty(cs, fc.getProperty())) { 724 for (ConceptDefinitionComponent def : cs.getConcept()) { 725 addCodeAndDescendents(cs, inc.getSystem(), def, null, expParams, imports, null, new PropertyFilter(fc, getPropertyDefinition(cs, fc.getProperty())), noInactive); 726 } 727 } else { 728 throw fail("Search by property[" + fc.getProperty() + "] and op[" + fc.getOp() + "] is not supported yet"); 729 } 730 } 731 } 732 733 private PropertyComponent getPropertyDefinition(CodeSystem cs, String property) { 734 for (PropertyComponent cp : cs.getProperty()) { 735 if (cp.getCode().equals(property)) { 736 return cp; 737 } 738 } 739 return null; 740 } 741 742 private boolean isDefinedProperty(CodeSystem cs, String property) { 743 for (PropertyComponent cp : cs.getProperty()) { 744 if (cp.getCode().equals(property)) { 745 return true; 746 } 747 } 748 return false; 749 } 750 751 private void addFragmentWarning(ValueSetExpansionComponent exp, CodeSystem cs) { 752 String url = cs.getVersionedUrl(); 753 for (ValueSetExpansionParameterComponent p : exp.getParameter()) { 754 if ("fragment".equals(p.getName()) && p.hasValueUriType() && url.equals(p.getValue().primitiveValue())) { 755 return; 756 } 757 } 758 exp.addParameter().setName("fragment").setValue(new UriType(url)); 759 } 760 761 private void addExampleWarning(ValueSetExpansionComponent exp, CodeSystem cs) { 762 String url = cs.getVersionedUrl(); 763 for (ValueSetExpansionParameterComponent p : exp.getParameter()) { 764 if ("example".equals(p.getName()) && p.hasValueUriType() && url.equals(p.getValue().primitiveValue())) { 765 return; 766 } 767 } 768 exp.addParameter().setName("example").setValue(new UriType(url)); 769 } 770 771 private List<ConceptDefinitionDesignationComponent> convertDesignations(List<ConceptReferenceDesignationComponent> list) { 772 List<ConceptDefinitionDesignationComponent> res = new ArrayList<CodeSystem.ConceptDefinitionDesignationComponent>(); 773 for (ConceptReferenceDesignationComponent t : list) { 774 ConceptDefinitionDesignationComponent c = new ConceptDefinitionDesignationComponent(); 775 c.setLanguage(t.getLanguage()); 776 c.setUse(t.getUse()); 777 c.setValue(t.getValue()); 778 } 779 return res; 780 } 781 782 private String key(String uri, String code) { 783 return "{" + uri + "}" + code; 784 } 785 786 private String key(ValueSetExpansionContainsComponent c) { 787 return key(c.getSystem(), c.getCode()); 788 } 789 790 private FHIRException fail(String msg) { 791 allErrors.add(msg); 792 return new FHIRException(msg); 793 } 794 795 private ETooCostly failCostly(String msg) { 796 allErrors.add(msg); 797 return new ETooCostly(msg); 798 } 799 800 private TerminologyServiceException failTSE(String msg) { 801 allErrors.add(msg); 802 return new TerminologyServiceException(msg); 803 } 804 805 public Collection<? extends String> getAllErrors() { 806 return allErrors; 807 } 808 809 public boolean isCheckCodesWhenExpanding() { 810 return checkCodesWhenExpanding; 811 } 812 813 public void setCheckCodesWhenExpanding(boolean checkCodesWhenExpanding) { 814 this.checkCodesWhenExpanding = checkCodesWhenExpanding; 815 } 816 817}