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