
001package org.hl7.fhir.r5.terminologies; 002 003import java.util.Calendar; 004import java.util.Collections; 005import java.util.Comparator; 006import java.util.HashSet; 007import java.util.List; 008import java.util.Set; 009 010/* 011 Copyright (c) 2011+, HL7, Inc. 012 All rights reserved. 013 014 Redistribution and use in source and binary forms, with or without modification, 015 are permitted provided that the following conditions are met: 016 017 * Redistributions of source code must retain the above copyright notice, this 018 list of conditions and the following disclaimer. 019 * Redistributions in binary form must reproduce the above copyright notice, 020 this list of conditions and the following disclaimer in the documentation 021 and/or other materials provided with the distribution. 022 * Neither the name of HL7 nor the names of its contributors may be used to 023 endorse or promote products derived from this software without specific 024 prior written permission. 025 026 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 027 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 028 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 029 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 030 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 031 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 032 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 033 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 034 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 035 POSSIBILITY OF SUCH DAMAGE. 036 037 */ 038 039 040import lombok.extern.slf4j.Slf4j; 041import org.hl7.fhir.exceptions.FHIRException; 042import org.hl7.fhir.exceptions.FHIRFormatError; 043import org.hl7.fhir.r5.context.IWorkerContext; 044import org.hl7.fhir.r5.extensions.ExtensionDefinitions; 045import org.hl7.fhir.r5.extensions.ExtensionUtilities; 046import org.hl7.fhir.r5.model.*; 047import org.hl7.fhir.r5.model.Enumerations.FilterOperator; 048import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; 049import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent; 050import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; 051import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent; 052import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent; 053import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; 054import org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent; 055import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent; 056import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 057import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent; 058import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.ConceptDefinitionComponentSorter; 059import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.ConceptStatus; 060import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.SourcedValueSet; 061import org.hl7.fhir.r5.utils.CanonicalResourceUtilities; 062 063import org.hl7.fhir.r5.utils.UserDataNames; 064import org.hl7.fhir.utilities.NaturalOrderComparator; 065import org.hl7.fhir.utilities.StandardsStatus; 066import org.hl7.fhir.utilities.Utilities; 067import org.hl7.fhir.utilities.VersionUtilities; 068 069@Slf4j 070public class ValueSetUtilities extends TerminologyUtilities { 071 072 public static class ValueSetSorter implements Comparator<ValueSet> { 073 074 @Override 075 public int compare(ValueSet o1, ValueSet o2) { 076 String url1 = o1.getUrl(); 077 String url2 = o2.getUrl(); 078 int c = compareString(url1, url2); 079 if (c == 0) { 080 String ver1 = o1.getVersion(); 081 String ver2 = o2.getVersion(); 082 c = compareVersions(ver1, ver2); 083 if (c == 0) { 084 String d1 = o1.getDateElement().asStringValue(); 085 String d2 = o2.getDateElement().asStringValue(); 086 c = compareString(url1, url2); 087 } 088 } 089 return c; 090 } 091 092 private int compareVersions(String v1, String v2) { 093 if (v1 == null && v2 == null) { 094 return 0; 095 } else if (v1 == null) { 096 return -1; // this order is deliberate 097 } else if (v2 == null) { 098 return 1; 099 } else if (VersionUtilities.isSemVer(v1) && VersionUtilities.isSemVer(v2)) { 100 return VersionUtilities.compareVersions(v1, v2); 101 } else if (Utilities.isInteger(v1) && Utilities.isInteger(v2)) { 102 return Integer.compare(Integer.parseInt(v1), Integer.parseInt(v2)); 103 } else { 104 return new NaturalOrderComparator<String>().compare(v1, v2); 105 } 106 } 107 108 private int compareString(String s1, String s2) { 109 if (s1 == null) { 110 return s2 == null ? 0 : 1; 111 } else { 112 return s1.compareTo(s2); 113 } 114 } 115 116 } 117 118 public static boolean isServerSide(String url) { 119 // this is required to work around placeholders or bad definitions in THO (cvx) 120 return Utilities.existsInList(url, 121 "http://hl7.org/fhir/sid/cvx", 122 "http://loinc.org", 123 "http://snomed.info/sct", 124 "http://www.nlm.nih.gov/research/umls/rxnorm", 125 "http://www.ama-assn.org/go/cpt", 126 "http://hl7.org/fhir/ndfrt", 127 "http://hl7.org/fhir/sid/ndc", 128 "http://hl7.org/fhir/sid/icd-10", 129 "http://hl7.org/fhir/sid/icd-9-cm", 130 "http://hl7.org/fhir/sid/icd-10-cm"); 131 } 132 133 public static boolean isServerSide(Coding c) { 134 return isServerSide(c.getSystem()); 135 } 136 137 public static boolean hasServerSide(CodeableConcept cc) { 138 for (Coding c : cc.getCoding()) { 139 if (isServerSide(c)) { 140 return true; 141 } 142 } 143 return false; 144 } 145 146 public static ValueSet makeShareable(ValueSet vs, boolean extension) { 147 if (!vs.hasExperimental()) { 148 vs.setExperimental(false); 149 } 150 if (extension) { 151 if (!vs.hasMeta()) 152 vs.setMeta(new Meta()); 153 for (UriType t : vs.getMeta().getProfile()) 154 if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablevalueset")) 155 return vs; 156 vs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablevalueset")); 157 } 158 return vs; 159 } 160 161 162 public static void checkShareable(ValueSet vs) { 163 if (!vs.hasMeta()) 164 throw new Error("ValueSet "+vs.getUrl()+" is not shareable"); 165 for (UriType t : vs.getMeta().getProfile()) { 166 if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablevalueset")) 167 return; 168 } 169 throw new Error("ValueSet "+vs.getUrl()+" is not shareable"); 170 } 171 172 public static boolean hasOID(ValueSet vs) { 173 return getOID(vs) != null; 174 } 175 176 public static String getOID(ValueSet vs) { 177 for (Identifier id : vs.getIdentifier()) { 178 if ("urn:ietf:rfc:3986".equals(id.getSystem()) && id.hasValue() && id.getValue().startsWith("urn:oid:")) 179 return id.getValue().substring(8); 180 } 181 return null; 182 } 183 184 public static void setOID(ValueSet vs, String oid) { 185 if (!oid.startsWith("urn:oid:")) 186 oid = "urn:oid:" + oid; 187 for (Identifier id : vs.getIdentifier()) { 188 if ("urn:ietf:rfc:3986".equals(id.getSystem()) && id.hasValue() && id.getValue().startsWith("urn:oid:")) { 189 id.setValue(oid); 190 return; 191 } 192 } 193 vs.addIdentifier().setSystem("urn:ietf:rfc:3986").setValue(oid); 194 } 195 196 public static void markStatus(ValueSet vs, String wg, StandardsStatus status, String fmm, IWorkerContext context, String normativeVersion) throws FHIRException { 197 if (vs.hasUserData(UserDataNames.render_external_link)) 198 return; 199 200 if (wg != null) { 201 if (!ExtensionUtilities.hasExtension(vs, ExtensionDefinitions.EXT_WORKGROUP) || 202 (!Utilities.existsInList(ExtensionUtilities.readStringExtension(vs, ExtensionDefinitions.EXT_WORKGROUP), "fhir", "vocab") && Utilities.existsInList(wg, "fhir", "vocab"))) { 203 CanonicalResourceUtilities.setHl7WG(vs, wg); 204 } 205 } 206 if (status != null) { 207 StandardsStatus ss = ExtensionUtilities.getStandardsStatus(vs); 208 if (ss == null || ss.isLowerThan(status)) 209 ExtensionUtilities.setStandardsStatus(vs, status, normativeVersion); 210 if (status == StandardsStatus.NORMATIVE) { 211 vs.setStatus(PublicationStatus.ACTIVE); 212 } 213 } 214 if (fmm != null) { 215 String sfmm = ExtensionUtilities.readStringExtension(vs, ExtensionDefinitions.EXT_FMM_LEVEL); 216 if (Utilities.noString(sfmm) || Integer.parseInt(sfmm) < Integer.parseInt(fmm)) { 217 ExtensionUtilities.setIntegerExtension(vs, ExtensionDefinitions.EXT_FMM_LEVEL, Integer.parseInt(fmm)); 218 } 219 } 220 if (vs.hasUserData(UserDataNames.TX_ASSOCIATED_CODESYSTEM)) 221 CodeSystemUtilities.markStatus((CodeSystem) vs.getUserData(UserDataNames.TX_ASSOCIATED_CODESYSTEM), wg, status, fmm, normativeVersion); 222 else if (status == StandardsStatus.NORMATIVE && context != null) { 223 for (ConceptSetComponent csc : vs.getCompose().getInclude()) { 224 if (csc.hasSystem()) { 225 CodeSystem cs = context.fetchCodeSystem(csc.getSystem()); 226 if (cs != null) { 227 CodeSystemUtilities.markStatus(cs, wg, status, fmm, normativeVersion); 228 } 229 } 230 } 231 } 232 } 233 234 private static int ssval(String status) { 235 if ("Draft".equals("status")) 236 return 1; 237 if ("Informative".equals("status")) 238 return 2; 239 if ("External".equals("status")) 240 return 3; 241 if ("Trial Use".equals("status")) 242 return 3; 243 if ("Normative".equals("status")) 244 return 4; 245 return -1; 246 } 247 248 249 public static class ConceptReferenceComponentSorter implements Comparator<ConceptReferenceComponent> { 250 251 @Override 252 public int compare(ConceptReferenceComponent o1, ConceptReferenceComponent o2) { 253 return o1.getCode().compareToIgnoreCase(o2.getCode()); 254 } 255 } 256 257 258 public static void sortInclude(ConceptSetComponent inc) { 259 Collections.sort(inc.getConcept(), new ConceptReferenceComponentSorter()); 260 } 261 262 public static String getAllCodesSystem(ValueSet vs) { 263 if (vs.hasCompose()) { 264 ValueSetComposeComponent c = vs.getCompose(); 265 if (c.getExclude().isEmpty() && c.getInclude().size() == 1) { 266 ConceptSetComponent i = c.getIncludeFirstRep(); 267 if (i.hasSystem() && !i.hasValueSet() && !i.hasConcept() && !i.hasFilter()) { 268 return i.getSystem(); 269 } 270 } 271 } 272 return null; 273 } 274 275 public static boolean isDeprecated(ValueSet vs, ValueSetExpansionContainsComponent c) { 276 try { 277 for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent p : c.getProperty()) { 278 if ("status".equals(p.getCode()) && p.hasValue() && p.hasValueCodeType() && "deprecated".equals(p.getValueCodeType().getCode())) { 279 return true; 280 } 281 // this, though status should also be set 282 if ("deprecationDate".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof DateTimeType) 283 return ((DateTimeType) p.getValue()).before(new DateTimeType(Calendar.getInstance())); 284 // legacy 285 if ("deprecated".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof BooleanType) 286 return ((BooleanType) p.getValue()).getValue(); 287 } 288 StandardsStatus ss = ExtensionUtilities.getStandardsStatus(c); 289 if (ss == StandardsStatus.DEPRECATED) { 290 return true; 291 } 292 return false; 293 } catch (FHIRException e) { 294 return false; 295 } 296 } 297 298 public static boolean hasCodeInExpansion(ValueSet vs, Coding code) { 299 return hasCodeInExpansion(vs.getExpansion().getContains(), code); 300 } 301 302 private static boolean hasCodeInExpansion(List<ValueSetExpansionContainsComponent> list, Coding code) { 303 for (ValueSetExpansionContainsComponent c : list) { 304 if (c.getSystem().equals(code.getSystem()) && c.getCode().equals(code.getCode())) { 305 return true; 306 } 307 if (hasCodeInExpansion(c.getContains(), code)) { 308 return true; 309 } 310 } 311 return false; 312 } 313 314 public static org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent addCodeProperty(ValueSet vs, ValueSetExpansionContainsComponent ctxt, String url, String code, String value) { 315 if (value != null) { 316 return addProperty(vs, ctxt, url, code, new CodeType(value)); 317 } else { 318 return null; 319 } 320 } 321 public static org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent addProperty(ValueSet vs, ValueSetExpansionContainsComponent ctxt, String url, String code, String value) { 322 if (value != null) { 323 return addProperty(vs, ctxt, url, code, new StringType(value)); 324 } else { 325 return null; 326 } 327 } 328 329 public static org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent addProperty(ValueSet vs, ValueSetExpansionContainsComponent ctxt, String url, String code, Integer value) { 330 if (value != null) { 331 return addProperty(vs, ctxt, url, code, new IntegerType(value)); 332 } else { 333 return null; 334 } 335 } 336 337 public static org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent addProperty(ValueSet vs, ValueSetExpansionContainsComponent ctxt, String url, String code, DataType value) { 338 code = defineProperty(vs, url, code); 339 org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent p = getProperty(ctxt.getProperty(), code); 340 if (p != null) { 341 p.setValue(value); 342 } else { 343 p = ctxt.addProperty().setCode(code).setValue(value); 344 } 345 return p; 346 } 347 348 private static org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent getProperty(List<org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent> list, String code) { 349 for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent t : list) { 350 if (code.equals(t.getCode())) { 351 return t; 352 } 353 } 354 return null; 355 } 356 357 private static String defineProperty(ValueSet vs, String url, String code) { 358 for (ValueSetExpansionPropertyComponent p : vs.getExpansion().getProperty()) { 359 if (p.hasUri() && p.getUri().equals(url)) { 360 return p.getCode(); 361 } 362 } 363 for (ValueSetExpansionPropertyComponent p : vs.getExpansion().getProperty()) { 364 if (p.hasCode() && p.getCode().equals(code)) { 365 p.setUri(url); 366 return code; 367 } 368 } 369 ValueSetExpansionPropertyComponent p = vs.getExpansion().addProperty(); 370 p.setUri(url); 371 p.setCode(code); 372 return code; 373 } 374 375 public static int countExpansion(ValueSet valueset) { 376 int i = valueset.getExpansion().getContains().size(); 377 for (ValueSetExpansionContainsComponent t : valueset.getExpansion().getContains()) { 378 i = i + countExpansion(t); 379 } 380 return i; 381 } 382 383 public static int countExpansion(List<ValueSetExpansionContainsComponent> list) { 384 int i = list.size(); 385 for (ValueSetExpansionContainsComponent t : list) { 386 i = i + countExpansion(t); 387 } 388 return i; 389 } 390 391 private static int countExpansion(ValueSetExpansionContainsComponent c) { 392 int i = c.getContains().size(); 393 for (ValueSetExpansionContainsComponent t : c.getContains()) { 394 i = i + countExpansion(t); 395 } 396 return i; 397 } 398 399 public static Set<String> listSystems(IWorkerContext ctxt, ValueSet vs) { 400 Set<String> systems = new HashSet<>(); 401 for (ConceptSetComponent inc : vs.getCompose().getInclude()) { 402 for (CanonicalType ct : inc.getValueSet()) { 403 ValueSet vsr = ctxt.findTxResource(ValueSet.class, ct.asStringValue(), null, vs); 404 if (vsr != null) { 405 systems.addAll(listSystems(ctxt, vsr)); 406 } 407 } 408 if (inc.hasSystem()) { 409 systems.add(inc.getSystem()); 410 } 411 } 412 return systems; 413 } 414 415 416 public static boolean isIncompleteExpansion(ValueSet valueSet) { 417 if (valueSet.hasExpansion()) { 418 ValueSetExpansionComponent exp = valueSet.getExpansion(); 419 if (exp.hasTotal()) { 420 if (exp.getTotal() != countExpansion(exp.getContains())) { 421 return true; 422 } 423 } 424 } 425 return false; 426 } 427 428 429 public static Set<String> codes(ValueSet vs, CodeSystem cs) { 430 Set<String> res = new HashSet<>(); 431 for (ConceptSetComponent inc : vs.getCompose().getInclude()) { 432 if (inc.getSystem().equals(cs.getUrl())) { 433 addCodes(res, inc, cs.getConcept()); 434 } 435 } 436 return res; 437 } 438 439 private static void addCodes(Set<String> res, ConceptSetComponent inc, List<ConceptDefinitionComponent> list) { 440 for (ConceptDefinitionComponent cd : list) { 441 if (cd.hasCode() && (!inc.hasConcept() || inc.hasConcept(cd.getCode()))) { 442 res.add(cd.getCode()); 443 } 444 if (cd.hasConcept()) { 445 addCodes(res, inc, cd.getConcept()); 446 } 447 } 448 } 449 450 public static String versionFromExpansionParams(Parameters expParameters, String system, String defaultVersion) { 451 if (expParameters != null) { 452 for (ParametersParameterComponent p : expParameters.getParameter()) { 453 if ("system-version".equals(p.getName()) || "force-system-version".equals(p.getName())) { 454 String v = p.getValue().primitiveValue(); 455 if (v.startsWith(system+"|")) { 456 String ver = v.substring(v.indexOf("|")+1); 457 if (defaultVersion == null || ver.startsWith(defaultVersion) || "force-system-version".equals(p.getName())) { 458 return ver; 459 } 460 } 461 } 462 } 463 } 464 return defaultVersion; 465 } 466 467 public static Set<String> checkExpansionSubset(ValueSet vs1, ValueSet vs2) { 468 Set<String> codes = new HashSet<>(); 469 checkCodes(codes, vs2.getExpansion().getContains(), vs1.getExpansion().getContains()); 470 return codes; 471 } 472 473 private static void checkCodes(Set<String> codes, List<ValueSetExpansionContainsComponent> listS, List<ValueSetExpansionContainsComponent> listT) { 474 for (ValueSetExpansionContainsComponent c : listS) { 475 ValueSetExpansionContainsComponent t = findContained(c, listT); 476 if (t == null) { 477 codes.add(c.getCode()); 478 } 479 if (c.hasContains()) { 480 checkCodes(codes, c.getContains(), listT); 481 } 482 } 483 } 484 485 private static ValueSetExpansionContainsComponent findContained(ValueSetExpansionContainsComponent c, List<ValueSetExpansionContainsComponent> listT) { 486 for (ValueSetExpansionContainsComponent t : listT) { 487 if (t.getSystem().equals(c.getSystem()) && t.getCode().equals(c.getCode())) { 488 return t; 489 } 490 if (t.hasContains()) { 491 ValueSetExpansionContainsComponent tt = findContained(c, t.getContains()); 492 if (tt != null) { 493 return tt; 494 } 495 } 496 } 497 return null; 498 } 499 500 public static void setDeprecated(List<ValueSet.ValueSetExpansionPropertyComponent> vsProp, ValueSet.ValueSetExpansionContainsComponent n) { 501 if (!"deprecated".equals(ValueSetUtilities.getStatus(vsProp, n))) { 502 n.addProperty().setCode("status").setValue(new CodeType("deprecated")); 503 for (ValueSet.ValueSetExpansionPropertyComponent o : vsProp) { 504 if ("status".equals(o.getCode())) { 505 return; 506 } 507 } 508 vsProp.add(new ValueSet.ValueSetExpansionPropertyComponent().setCode("status").setUri("http://hl7.org/fhir/concept-properties#status")); 509 } 510 } 511 512 private static String getStatus(List<ValueSetExpansionPropertyComponent> vsProp, ValueSetExpansionContainsComponent n) { 513 return ValueSetUtilities.getProperty(vsProp, n, "status", "http://hl7.org/fhir/concept-properties#status"); 514 } 515 516 517 public static String getProperty(List<ValueSetExpansionPropertyComponent> vsProp, ValueSetExpansionContainsComponent focus, String code, String url) { 518 ValueSet.ValueSetExpansionPropertyComponent pc = null; 519 for (ValueSet.ValueSetExpansionPropertyComponent t : vsProp) { 520 if (t.hasUri() && t.getUri().equals(url)) { 521 pc = t; 522 } 523 } 524 if (pc == null) { 525 for (ValueSet.ValueSetExpansionPropertyComponent t : vsProp) { 526 if (t.hasCode() && t.getCode().equals(code)) { 527 pc = t; 528 } 529 } 530 } 531 if (pc != null) { 532 for (ValueSet.ConceptPropertyComponent t : focus.getProperty()) { 533 if (t.hasCode() && t.getCode().equals(pc.getCode())) { 534 return t.getValue().primitiveValue(); 535 } 536 } 537 } 538 return null; 539 } 540 541 542}