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