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