001package org.hl7.fhir.r4.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 032import static org.apache.commons.lang3.StringUtils.isNotBlank; 033 034import java.io.FileNotFoundException; 035import java.io.IOException; 036 037/* 038 * Copyright (c) 2011+, HL7, Inc 039 * All rights reserved. 040 * 041 * Redistribution and use in source and binary forms, with or without modification, 042 * are permitted provided that the following conditions are met: 043 * 044 * Redistributions of source code must retain the above copyright notice, this 045 * list of conditions and the following disclaimer. 046 * Redistributions in binary form must reproduce the above copyright notice, 047 * this list of conditions and the following disclaimer in the documentation 048 * and/or other materials provided with the distribution. 049 * Neither the name of HL7 nor the names of its contributors may be used to 050 * endorse or promote products derived from this software without specific 051 * prior written permission. 052 * 053 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 054 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 055 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 056 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 057 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 058 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 059 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 060 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 061 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 062 * POSSIBILITY OF SUCH DAMAGE. 063 * 064 */ 065 066import java.util.ArrayList; 067import java.util.HashMap; 068import java.util.HashSet; 069import java.util.List; 070import java.util.Map; 071import java.util.Set; 072 073import org.apache.commons.lang3.NotImplementedException; 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.r4.context.IWorkerContext; 079import org.hl7.fhir.r4.model.CodeSystem; 080import org.hl7.fhir.r4.model.CodeSystem.CodeSystemContentMode; 081import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; 082import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionDesignationComponent; 083import org.hl7.fhir.r4.model.DateTimeType; 084import org.hl7.fhir.r4.model.Factory; 085import org.hl7.fhir.r4.model.Parameters; 086import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent; 087import org.hl7.fhir.r4.model.PrimitiveType; 088import org.hl7.fhir.r4.model.Type; 089import org.hl7.fhir.r4.model.UriType; 090import org.hl7.fhir.r4.model.ValueSet; 091import org.hl7.fhir.r4.model.ValueSet.ConceptReferenceComponent; 092import org.hl7.fhir.r4.model.ValueSet.ConceptReferenceDesignationComponent; 093import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; 094import org.hl7.fhir.r4.model.ValueSet.ConceptSetFilterComponent; 095import org.hl7.fhir.r4.model.ValueSet.FilterOperator; 096import org.hl7.fhir.r4.model.ValueSet.ValueSetComposeComponent; 097import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent; 098import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent; 099import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionParameterComponent; 100import org.hl7.fhir.r4.utils.ToolingExtensions; 101import org.hl7.fhir.utilities.Utilities; 102 103public class ValueSetExpanderSimple implements ValueSetExpander { 104 105 private List<ValueSetExpansionContainsComponent> codes = new ArrayList<ValueSet.ValueSetExpansionContainsComponent>(); 106 private List<ValueSetExpansionContainsComponent> roots = new ArrayList<ValueSet.ValueSetExpansionContainsComponent>(); 107 private Map<String, ValueSetExpansionContainsComponent> map = new HashMap<String, ValueSet.ValueSetExpansionContainsComponent>(); 108 private IWorkerContext context; 109 private boolean canBeHeirarchy = true; 110 private Set<String> excludeKeys = new HashSet<String>(); 111 private Set<String> excludeSystems = new HashSet<String>(); 112 private ValueSet focus; 113 private int maxExpansionSize = 500; 114 115 private int total; 116 117 public ValueSetExpanderSimple(IWorkerContext context) { 118 super(); 119 this.context = context; 120 } 121 122 public void setMaxExpansionSize(int theMaxExpansionSize) { 123 maxExpansionSize = theMaxExpansionSize; 124 } 125 126 private ValueSetExpansionContainsComponent addCode(String system, String code, String display, 127 ValueSetExpansionContainsComponent parent, List<ConceptDefinitionDesignationComponent> designations, 128 Parameters expParams, boolean isAbstract, boolean inactive, List<ValueSet> filters) { 129 130 if (filters != null && !filters.isEmpty() && !filterContainsCode(filters, system, code)) 131 return null; 132 ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent(); 133 n.setSystem(system); 134 n.setCode(code); 135 if (isAbstract) 136 n.setAbstract(true); 137 if (inactive) 138 n.setInactive(true); 139 140 if (expParams.getParameterBool("includeDesignations") && designations != null) { 141 for (ConceptDefinitionDesignationComponent t : designations) { 142 ToolingExtensions.addLanguageTranslation(n, t.getLanguage(), t.getValue()); 143 } 144 } 145 ConceptDefinitionDesignationComponent t = expParams.hasLanguage() 146 ? getMatchingLang(designations, expParams.getLanguage()) 147 : null; 148 if (t == null) 149 n.setDisplay(display); 150 else 151 n.setDisplay(t.getValue()); 152 153 String s = key(n); 154 if (map.containsKey(s) || excludeKeys.contains(s)) { 155 canBeHeirarchy = false; 156 } else { 157 codes.add(n); 158 map.put(s, n); 159 total++; 160 } 161 if (canBeHeirarchy && parent != null) { 162 parent.getContains().add(n); 163 } else { 164 roots.add(n); 165 } 166 return n; 167 } 168 169 private boolean filterContainsCode(List<ValueSet> filters, String system, String code) { 170 for (ValueSet vse : filters) 171 if (expansionContainsCode(vse.getExpansion().getContains(), system, code)) 172 return true; 173 return false; 174 } 175 176 private boolean expansionContainsCode(List<ValueSetExpansionContainsComponent> contains, String system, String code) { 177 for (ValueSetExpansionContainsComponent cc : contains) { 178 if (system.equals(cc.getSystem()) && code.equals(cc.getCode())) 179 return true; 180 if (expansionContainsCode(cc.getContains(), system, code)) 181 return true; 182 } 183 return false; 184 } 185 186 private ConceptDefinitionDesignationComponent getMatchingLang(List<ConceptDefinitionDesignationComponent> list, 187 String lang) { 188 for (ConceptDefinitionDesignationComponent t : list) 189 if (t.getLanguage().equals(lang)) 190 return t; 191 for (ConceptDefinitionDesignationComponent t : list) 192 if (t.getLanguage().startsWith(lang)) 193 return t; 194 return null; 195 } 196 197 private void addCodeAndDescendents(ValueSetExpansionContainsComponent focus, 198 ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters) throws FHIRException { 199 focus.checkNoModifiers("Expansion.contains", "expanding"); 200 ValueSetExpansionContainsComponent np = addCode(focus.getSystem(), focus.getCode(), focus.getDisplay(), parent, 201 convert(focus.getDesignation()), expParams, focus.getAbstract(), focus.getInactive(), filters); 202 for (ValueSetExpansionContainsComponent c : focus.getContains()) 203 addCodeAndDescendents(focus, np, expParams, filters); 204 } 205 206 private List<ConceptDefinitionDesignationComponent> convert(List<ConceptReferenceDesignationComponent> designations) { 207 List<ConceptDefinitionDesignationComponent> list = new ArrayList<ConceptDefinitionDesignationComponent>(); 208 for (ConceptReferenceDesignationComponent d : designations) { 209 ConceptDefinitionDesignationComponent n = new ConceptDefinitionDesignationComponent(); 210 n.setLanguage(d.getLanguage()); 211 n.setUse(d.getUse()); 212 n.setValue(d.getValue()); 213 list.add(n); 214 } 215 return list; 216 } 217 218 private void addCodeAndDescendents(CodeSystem cs, String system, ConceptDefinitionComponent def, 219 ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters, 220 ConceptDefinitionComponent exclusion) throws FHIRException { 221 def.checkNoModifiers("Code in Code System", "expanding"); 222 if (exclusion != null) { 223 if (exclusion.getCode().equals(def.getCode())) 224 return; // excluded. 225 } 226 if (!CodeSystemUtilities.isDeprecated(cs, def)) { 227 ValueSetExpansionContainsComponent np = null; 228 boolean abs = CodeSystemUtilities.isNotSelectable(cs, def); 229 boolean inc = CodeSystemUtilities.isInactive(cs, def); 230 if (canBeHeirarchy || !abs) 231 np = addCode(system, def.getCode(), def.getDisplay(), parent, def.getDesignation(), expParams, abs, inc, 232 filters); 233 for (ConceptDefinitionComponent c : def.getConcept()) 234 addCodeAndDescendents(cs, system, c, np, expParams, filters, exclusion); 235 } else { 236 for (ConceptDefinitionComponent c : def.getConcept()) 237 addCodeAndDescendents(cs, system, c, null, expParams, filters, exclusion); 238 } 239 240 } 241 242 private void addCodes(ValueSetExpansionComponent expand, List<ValueSetExpansionParameterComponent> params, 243 Parameters expParams, List<ValueSet> filters) throws ETooCostly, FHIRException { 244 if (expand != null) { 245 if (expand.getContains().size() > maxExpansionSize) 246 throw new ETooCostly("Too many codes to display (>" + Integer.toString(expand.getContains().size()) + ")"); 247 for (ValueSetExpansionParameterComponent p : expand.getParameter()) { 248 if (!existsInParams(params, p.getName(), p.getValue())) 249 params.add(p); 250 } 251 252 copyImportContains(expand.getContains(), null, expParams, filters); 253 } 254 } 255 256 private void excludeCode(String theSystem, String theCode) { 257 ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent(); 258 n.setSystem(theSystem); 259 n.setCode(theCode); 260 String s = key(n); 261 excludeKeys.add(s); 262 } 263 264 private void excludeCodes(ConceptSetComponent exc, List<ValueSetExpansionParameterComponent> params, String ctxt) 265 throws FHIRException { 266 exc.checkNoModifiers("Compose.exclude", "expanding"); 267 if (exc.hasSystem() && exc.getConcept().size() == 0 && exc.getFilter().size() == 0) { 268 excludeSystems.add(exc.getSystem()); 269 } 270 271 if (exc.hasValueSet()) 272 throw new Error("Processing Value set references in exclude is not yet done in " + ctxt); 273 // importValueSet(imp.getValue(), params, expParams); 274 275 CodeSystem cs = context.fetchCodeSystem(exc.getSystem()); 276 if ((cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE) && context.supportsSystem(exc.getSystem())) { 277 ValueSetExpansionOutcome vse = context.expandVS(exc, false); 278 ValueSet valueset = vse.getValueset(); 279 if (valueset == null) 280 throw new TerminologyServiceException("Error Expanding ValueSet: " + vse.getError()); 281 excludeCodes(valueset.getExpansion(), params); 282 return; 283 } 284 285 for (ConceptReferenceComponent c : exc.getConcept()) { 286 excludeCode(exc.getSystem(), c.getCode()); 287 } 288 289 if (exc.getFilter().size() > 0) 290 throw new NotImplementedException("not done yet"); 291 } 292 293 private void excludeCodes(ValueSetExpansionComponent expand, List<ValueSetExpansionParameterComponent> params) { 294 for (ValueSetExpansionContainsComponent c : expand.getContains()) { 295 excludeCode(c.getSystem(), c.getCode()); 296 } 297 } 298 299 private boolean existsInParams(List<ValueSetExpansionParameterComponent> params, String name, Type value) { 300 for (ValueSetExpansionParameterComponent p : params) { 301 if (p.getName().equals(name) && PrimitiveType.compareDeep(p.getValue(), value, false)) 302 return true; 303 } 304 return false; 305 } 306 307 @Override 308 public ValueSetExpansionOutcome expand(ValueSet source, Parameters expParams) { 309 try { 310 return doExpand(source, expParams); 311 } catch (NoTerminologyServiceException e) { 312 // well, we couldn't expand, so we'll return an interface to a checker that can 313 // check membership of the set 314 // that might fail too, but it might not, later. 315 return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.NOSERVICE); 316 } catch (RuntimeException e) { 317 // TODO: we should put something more specific instead of just Exception below, 318 // since 319 // it swallows bugs.. what would be expected to be caught there? 320 throw e; 321 } catch (Exception e) { 322 // well, we couldn't expand, so we'll return an interface to a checker that can 323 // check membership of the set 324 // that might fail too, but it might not, later. 325 return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.UNKNOWN); 326 } 327 } 328 329 public ValueSetExpansionOutcome doExpand(ValueSet source, Parameters expParams) 330 throws FHIRException, ETooCostly, FileNotFoundException, IOException { 331 if (expParams == null) 332 expParams = makeDefaultExpansion(); 333 source.checkNoModifiers("ValueSet", "expanding"); 334 focus = source.copy(); 335 focus.setExpansion(new ValueSet.ValueSetExpansionComponent()); 336 focus.getExpansion().setTimestampElement(DateTimeType.now()); 337 focus.getExpansion().setIdentifier(Factory.createUUID()); 338 for (ParametersParameterComponent p : expParams.getParameter()) { 339 if (Utilities.existsInList(p.getName(), "includeDesignations", "excludeNested")) 340 focus.getExpansion().addParameter().setName(p.getName()).setValue(p.getValue()); 341 } 342 343 if (source.hasCompose()) 344 handleCompose(source.getCompose(), focus.getExpansion().getParameter(), expParams, source.getUrl()); 345 346 if (canBeHeirarchy) { 347 for (ValueSetExpansionContainsComponent c : roots) { 348 focus.getExpansion().getContains().add(c); 349 } 350 } else { 351 for (ValueSetExpansionContainsComponent c : codes) { 352 if (map.containsKey(key(c)) && !c.getAbstract()) { // we may have added abstract codes earlier while we still 353 // thought it might be heirarchical, but later we gave up, so 354 // now ignore them 355 focus.getExpansion().getContains().add(c); 356 c.getContains().clear(); // make sure any heirarchy is wiped 357 } 358 } 359 } 360 361 if (total > 0) { 362 focus.getExpansion().setTotal(total); 363 } 364 365 return new ValueSetExpansionOutcome(focus); 366 } 367 368 private Parameters makeDefaultExpansion() { 369 Parameters res = new Parameters(); 370 res.addParameter("excludeNested", true); 371 res.addParameter("includeDesignations", false); 372 return res; 373 } 374 375 private void addToHeirarchy(List<ValueSetExpansionContainsComponent> target, 376 List<ValueSetExpansionContainsComponent> source) { 377 for (ValueSetExpansionContainsComponent s : source) { 378 target.add(s); 379 } 380 } 381 382 private String getCodeDisplay(CodeSystem cs, String code) throws TerminologyServiceException { 383 ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), code); 384 if (def == null) 385 throw new TerminologyServiceException("Unable to find code '" + code + "' in code system " + cs.getUrl()); 386 return def.getDisplay(); 387 } 388 389 private ConceptDefinitionComponent getConceptForCode(List<ConceptDefinitionComponent> clist, String code) { 390 for (ConceptDefinitionComponent c : clist) { 391 if (code.equals(c.getCode())) 392 return c; 393 ConceptDefinitionComponent v = getConceptForCode(c.getConcept(), code); 394 if (v != null) 395 return v; 396 } 397 return null; 398 } 399 400 private void handleCompose(ValueSetComposeComponent compose, List<ValueSetExpansionParameterComponent> params, 401 Parameters expParams, String ctxt) throws ETooCostly, FileNotFoundException, IOException, FHIRException { 402 compose.checkNoModifiers("ValueSet.compose", "expanding"); 403 // Exclude comes first because we build up a map of things to exclude 404 for (ConceptSetComponent inc : compose.getExclude()) 405 excludeCodes(inc, params, ctxt); 406 canBeHeirarchy = !expParams.getParameterBool("excludeNested") && excludeKeys.isEmpty() && excludeSystems.isEmpty(); 407 boolean first = true; 408 for (ConceptSetComponent inc : compose.getInclude()) { 409 if (first == true) 410 first = false; 411 else 412 canBeHeirarchy = false; 413 includeCodes(inc, params, expParams, canBeHeirarchy); 414 } 415 416 } 417 418 private ValueSet importValueSet(String value, List<ValueSetExpansionParameterComponent> params, Parameters expParams) 419 throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException, FHIRFormatError { 420 if (value == null) 421 throw new TerminologyServiceException("unable to find value set with no identity"); 422 ValueSet vs = context.fetchResource(ValueSet.class, value); 423 if (vs == null) 424 throw new TerminologyServiceException("Unable to find imported value set " + value); 425 ValueSetExpansionOutcome vso = new ValueSetExpanderSimple(context).expand(vs, expParams); 426 if (vso.getError() != null) 427 throw new TerminologyServiceException("Unable to expand imported value set: " + vso.getError()); 428 if (vs.hasVersion()) 429 if (!existsInParams(params, "version", new UriType(vs.getUrl() + "|" + vs.getVersion()))) 430 params.add(new ValueSetExpansionParameterComponent().setName("version") 431 .setValue(new UriType(vs.getUrl() + "|" + vs.getVersion()))); 432 for (ValueSetExpansionParameterComponent p : vso.getValueset().getExpansion().getParameter()) { 433 if (!existsInParams(params, p.getName(), p.getValue())) 434 params.add(p); 435 } 436 canBeHeirarchy = false; // if we're importing a value set, we have to be combining, so we won't try for 437 // a heirarchy 438 return vso.getValueset(); 439 } 440 441 private void copyImportContains(List<ValueSetExpansionContainsComponent> list, 442 ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filter) throws FHIRException { 443 for (ValueSetExpansionContainsComponent c : list) { 444 c.checkNoModifiers("Imported Expansion in Code System", "expanding"); 445 ValueSetExpansionContainsComponent np = addCode(c.getSystem(), c.getCode(), c.getDisplay(), parent, null, 446 expParams, c.getAbstract(), c.getInactive(), filter); 447 copyImportContains(c.getContains(), np, expParams, filter); 448 } 449 } 450 451 private void includeCodes(ConceptSetComponent inc, List<ValueSetExpansionParameterComponent> params, 452 Parameters expParams, boolean heirarchical) throws ETooCostly, FileNotFoundException, IOException, FHIRException { 453 inc.checkNoModifiers("Compose.include", "expanding"); 454 List<ValueSet> imports = new ArrayList<ValueSet>(); 455 for (UriType imp : inc.getValueSet()) { 456 imports.add(importValueSet(imp.getValue(), params, expParams)); 457 } 458 459 if (!inc.hasSystem()) { 460 if (imports.isEmpty()) // though this is not supposed to be the case 461 return; 462 ValueSet base = imports.get(0); 463 imports.remove(0); 464 base.checkNoModifiers("Imported ValueSet", "expanding"); 465 copyImportContains(base.getExpansion().getContains(), null, expParams, imports); 466 } else { 467 CodeSystem cs = context.fetchCodeSystem(inc.getSystem()); 468 if ((cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE)) { 469 doServerIncludeCodes(inc, heirarchical, params, imports, expParams); 470 } else { 471 doInternalIncludeCodes(inc, params, expParams, imports, cs); 472 } 473 } 474 } 475 476 private void doServerIncludeCodes(ConceptSetComponent inc, boolean heirarchical, 477 List<ValueSetExpansionParameterComponent> params, List<ValueSet> imports, Parameters expParams) 478 throws FHIRException { 479 ValueSetExpansionOutcome vso = context.expandVS(inc, heirarchical); 480 if (vso.getError() != null) 481 throw new TerminologyServiceException("Unable to expand imported value set: " + vso.getError()); 482 ValueSet vs = vso.getValueset(); 483 if (vs.hasVersion()) 484 if (!existsInParams(params, "version", new UriType(vs.getUrl() + "|" + vs.getVersion()))) 485 params.add(new ValueSetExpansionParameterComponent().setName("version") 486 .setValue(new UriType(vs.getUrl() + "|" + vs.getVersion()))); 487 for (ValueSetExpansionParameterComponent p : vso.getValueset().getExpansion().getParameter()) { 488 if (!existsInParams(params, p.getName(), p.getValue())) 489 params.add(p); 490 } 491 for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) { 492 addCodeAndDescendents(cc, null, expParams, imports); 493 } 494 } 495 496 public void doInternalIncludeCodes(ConceptSetComponent inc, List<ValueSetExpansionParameterComponent> params, 497 Parameters expParams, List<ValueSet> imports, CodeSystem cs) 498 throws NoTerminologyServiceException, TerminologyServiceException, FHIRException { 499 if (cs == null) { 500 if (context.isNoTerminologyServer()) 501 throw new NoTerminologyServiceException("unable to find code system " + inc.getSystem().toString()); 502 else 503 throw new TerminologyServiceException("unable to find code system " + inc.getSystem().toString()); 504 } 505 cs.checkNoModifiers("Code System", "expanding"); 506 if (cs.getContent() != CodeSystemContentMode.COMPLETE) 507 throw new TerminologyServiceException("Code system " + inc.getSystem().toString() + " is incomplete"); 508 if (cs.hasVersion()) 509 if (!existsInParams(params, "version", new UriType(cs.getUrl() + "|" + cs.getVersion()))) 510 params.add(new ValueSetExpansionParameterComponent().setName("version") 511 .setValue(new UriType(cs.getUrl() + "|" + cs.getVersion()))); 512 513 if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) { 514 // special case - add all the code system 515 for (ConceptDefinitionComponent def : cs.getConcept()) { 516 addCodeAndDescendents(cs, inc.getSystem(), def, null, expParams, imports, null); 517 } 518 } 519 520 if (!inc.getConcept().isEmpty()) { 521 canBeHeirarchy = false; 522 for (ConceptReferenceComponent c : inc.getConcept()) { 523 c.checkNoModifiers("Code in Code System", "expanding"); 524 addCode(inc.getSystem(), c.getCode(), 525 Utilities.noString(c.getDisplay()) ? getCodeDisplay(cs, c.getCode()) : c.getDisplay(), null, 526 convertDesignations(c.getDesignation()), expParams, false, CodeSystemUtilities.isInactive(cs, c.getCode()), 527 imports); 528 } 529 } 530 if (inc.getFilter().size() > 1) { 531 canBeHeirarchy = false; // which will bt the case if we get around to supporting this 532 throw new TerminologyServiceException("Multiple filters not handled yet"); // need to and them, and this isn't 533 // done yet. But this shouldn't arise 534 // in non loinc and snomed value sets 535 } 536 if (inc.getFilter().size() == 1) { 537 ConceptSetFilterComponent fc = inc.getFilter().get(0); 538 if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISA) { 539 // special: all codes in the target code system under the value 540 ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue()); 541 if (def == null) 542 throw new TerminologyServiceException( 543 "Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'"); 544 addCodeAndDescendents(cs, inc.getSystem(), def, null, expParams, imports, null); 545 } else if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISNOTA) { 546 // special: all codes in the target code system that are not under the value 547 ConceptDefinitionComponent defEx = getConceptForCode(cs.getConcept(), fc.getValue()); 548 if (defEx == null) 549 throw new TerminologyServiceException( 550 "Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'"); 551 for (ConceptDefinitionComponent def : cs.getConcept()) { 552 addCodeAndDescendents(cs, inc.getSystem(), def, null, expParams, imports, defEx); 553 } 554 } else if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.DESCENDENTOF) { 555 // special: all codes in the target code system under the value 556 ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue()); 557 if (def == null) 558 throw new TerminologyServiceException( 559 "Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'"); 560 for (ConceptDefinitionComponent c : def.getConcept()) 561 addCodeAndDescendents(cs, inc.getSystem(), c, null, expParams, imports, null); 562 } else if ("display".equals(fc.getProperty()) && fc.getOp() == FilterOperator.EQUAL) { 563 // gg; note: wtf is this: if the filter is display=v, look up the code 'v', and 564 // see if it's diplsay is 'v'? 565 canBeHeirarchy = false; 566 ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue()); 567 if (def != null) { 568 if (isNotBlank(def.getDisplay()) && isNotBlank(fc.getValue())) { 569 if (def.getDisplay().contains(fc.getValue())) { 570 addCode(inc.getSystem(), def.getCode(), def.getDisplay(), null, def.getDesignation(), expParams, 571 CodeSystemUtilities.isNotSelectable(cs, def), CodeSystemUtilities.isInactive(cs, def), imports); 572 } 573 } 574 } 575 } else 576 throw new NotImplementedException( 577 "Search by property[" + fc.getProperty() + "] and op[" + fc.getOp() + "] is not supported yet"); 578 } 579 } 580 581 private List<ConceptDefinitionDesignationComponent> convertDesignations( 582 List<ConceptReferenceDesignationComponent> list) { 583 List<ConceptDefinitionDesignationComponent> res = new ArrayList<CodeSystem.ConceptDefinitionDesignationComponent>(); 584 for (ConceptReferenceDesignationComponent t : list) { 585 ConceptDefinitionDesignationComponent c = new ConceptDefinitionDesignationComponent(); 586 c.setLanguage(t.getLanguage()); 587 c.setUse(t.getUse()); 588 c.setValue(t.getValue()); 589 } 590 return res; 591 } 592 593 private String key(String uri, String code) { 594 return "{" + uri + "}" + code; 595 } 596 597 private String key(ValueSetExpansionContainsComponent c) { 598 return key(c.getSystem(), c.getCode()); 599 } 600 601}