
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.BooleanType; 047import org.hl7.fhir.r5.model.CanonicalType; 048import org.hl7.fhir.r5.model.CodeSystem; 049import org.hl7.fhir.r5.model.DateTimeType; 050import org.hl7.fhir.r5.model.StringType; 051import org.hl7.fhir.r5.model.IntegerType; 052import org.hl7.fhir.r5.model.Enumerations.FilterOperator; 053import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; 054import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent; 055import org.hl7.fhir.r5.model.Identifier; 056import org.hl7.fhir.r5.model.Meta; 057import org.hl7.fhir.r5.model.Parameters; 058import org.hl7.fhir.r5.model.UriType; 059import org.hl7.fhir.r5.model.ValueSet; 060import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; 061import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent; 062import org.hl7.fhir.r5.model.CodeType; 063import org.hl7.fhir.r5.model.Coding; 064import org.hl7.fhir.r5.model.DataType; 065import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent; 066import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; 067import org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent; 068import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent; 069import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 070import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent; 071import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.ConceptDefinitionComponentSorter; 072import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.ConceptStatus; 073import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.SourcedValueSet; 074import org.hl7.fhir.r5.utils.CanonicalResourceUtilities; 075 076import org.hl7.fhir.r5.utils.UserDataNames; 077import org.hl7.fhir.utilities.StandardsStatus; 078import org.hl7.fhir.utilities.Utilities; 079import org.hl7.fhir.utilities.VersionUtilities; 080 081@Slf4j 082public class ValueSetUtilities extends TerminologyUtilities { 083 084 public static class ValueSetSorter implements Comparator<ValueSet> { 085 086 @Override 087 public int compare(ValueSet o1, ValueSet o2) { 088 String url1 = o1.getUrl(); 089 String url2 = o2.getUrl(); 090 int c = compareString(url1, url2); 091 if (c == 0) { 092 String ver1 = o1.getVersion(); 093 String ver2 = o2.getVersion(); 094 c = VersionUtilities.compareVersions(ver1, ver2); 095 if (c == 0) { 096 String d1 = o1.getDateElement().asStringValue(); 097 String d2 = o2.getDateElement().asStringValue(); 098 c = compareString(url1, url2); 099 } 100 } 101 return c; 102 } 103 104 private int compareString(String s1, String s2) { 105 if (s1 == null) { 106 return s2 == null ? 0 : 1; 107 } else { 108 return s1.compareTo(s2); 109 } 110 } 111 112 } 113 114 115 public static boolean isServerSide(String url) { 116 return Utilities.existsInList(url, "http://hl7.org/fhir/sid/cvx"); 117 } 118 119 public static ValueSet makeShareable(ValueSet vs) { 120 if (!vs.hasExperimental()) { 121 vs.setExperimental(false); 122 } 123 if (!vs.hasMeta()) 124 vs.setMeta(new Meta()); 125 for (UriType t : vs.getMeta().getProfile()) 126 if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablevalueset")) 127 return vs; 128 vs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablevalueset")); 129 return vs; 130 } 131 132 public static boolean makeVSShareable(ValueSet vs) { 133 if (!vs.hasMeta()) 134 vs.setMeta(new Meta()); 135 for (UriType t : vs.getMeta().getProfile()) 136 if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablevalueset")) 137 return false; 138 vs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablevalueset")); 139 return true; 140 } 141 142 public static void checkShareable(ValueSet vs) { 143 if (!vs.hasMeta()) 144 throw new Error("ValueSet "+vs.getUrl()+" is not shareable"); 145 for (UriType t : vs.getMeta().getProfile()) { 146 if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablevalueset")) 147 return; 148 } 149 throw new Error("ValueSet "+vs.getUrl()+" is not shareable"); 150 } 151 152 public static boolean hasOID(ValueSet vs) { 153 return getOID(vs) != null; 154 } 155 156 public static String getOID(ValueSet vs) { 157 for (Identifier id : vs.getIdentifier()) { 158 if ("urn:ietf:rfc:3986".equals(id.getSystem()) && id.hasValue() && id.getValue().startsWith("urn:oid:")) 159 return id.getValue().substring(8); 160 } 161 return null; 162 } 163 164 public static void setOID(ValueSet vs, String oid) { 165 if (!oid.startsWith("urn:oid:")) 166 oid = "urn:oid:" + oid; 167 for (Identifier id : vs.getIdentifier()) { 168 if ("urn:ietf:rfc:3986".equals(id.getSystem()) && id.hasValue() && id.getValue().startsWith("urn:oid:")) { 169 id.setValue(oid); 170 return; 171 } 172 } 173 vs.addIdentifier().setSystem("urn:ietf:rfc:3986").setValue(oid); 174 } 175 176 public static void markStatus(ValueSet vs, String wg, StandardsStatus status, String pckage, String fmm, IWorkerContext context, String normativeVersion) throws FHIRException { 177 if (vs.hasUserData(UserDataNames.render_external_link)) 178 return; 179 180 if (wg != null) { 181 if (!ExtensionUtilities.hasExtension(vs, ExtensionDefinitions.EXT_WORKGROUP) || 182 (!Utilities.existsInList(ExtensionUtilities.readStringExtension(vs, ExtensionDefinitions.EXT_WORKGROUP), "fhir", "vocab") && Utilities.existsInList(wg, "fhir", "vocab"))) { 183 CanonicalResourceUtilities.setHl7WG(vs, wg); 184 } 185 } 186 if (status != null) { 187 StandardsStatus ss = ExtensionUtilities.getStandardsStatus(vs); 188 if (ss == null || ss.isLowerThan(status)) 189 ExtensionUtilities.setStandardsStatus(vs, status, normativeVersion); 190 if (pckage != null) { 191 if (!vs.hasUserData(UserDataNames.kindling_ballot_package)) 192 vs.setUserData(UserDataNames.kindling_ballot_package, pckage); 193 else if (!pckage.equals(vs.getUserString(UserDataNames.kindling_ballot_package))) 194 if (!"infrastructure".equals(vs.getUserString(UserDataNames.kindling_ballot_package))) 195 log.warn("Value Set "+vs.getUrl()+": ownership clash "+pckage+" vs "+vs.getUserString(UserDataNames.kindling_ballot_package)); 196 } 197 if (status == StandardsStatus.NORMATIVE) { 198 vs.setStatus(PublicationStatus.ACTIVE); 199 } 200 } 201 if (fmm != null) { 202 String sfmm = ExtensionUtilities.readStringExtension(vs, ExtensionDefinitions.EXT_FMM_LEVEL); 203 if (Utilities.noString(sfmm) || Integer.parseInt(sfmm) < Integer.parseInt(fmm)) { 204 ExtensionUtilities.setIntegerExtension(vs, ExtensionDefinitions.EXT_FMM_LEVEL, Integer.parseInt(fmm)); 205 } 206 } 207 if (vs.hasUserData(UserDataNames.TX_ASSOCIATED_CODESYSTEM)) 208 CodeSystemUtilities.markStatus((CodeSystem) vs.getUserData(UserDataNames.TX_ASSOCIATED_CODESYSTEM), wg, status, pckage, fmm, normativeVersion); 209 else if (status == StandardsStatus.NORMATIVE && context != null) { 210 for (ConceptSetComponent csc : vs.getCompose().getInclude()) { 211 if (csc.hasSystem()) { 212 CodeSystem cs = context.fetchCodeSystem(csc.getSystem()); 213 if (cs != null) { 214 CodeSystemUtilities.markStatus(cs, wg, status, pckage, fmm, normativeVersion); 215 } 216 } 217 } 218 } 219 } 220 221 private static int ssval(String status) { 222 if ("Draft".equals("status")) 223 return 1; 224 if ("Informative".equals("status")) 225 return 2; 226 if ("External".equals("status")) 227 return 3; 228 if ("Trial Use".equals("status")) 229 return 3; 230 if ("Normative".equals("status")) 231 return 4; 232 return -1; 233 } 234 235 236 public static class ConceptReferenceComponentSorter implements Comparator<ConceptReferenceComponent> { 237 238 @Override 239 public int compare(ConceptReferenceComponent o1, ConceptReferenceComponent o2) { 240 return o1.getCode().compareToIgnoreCase(o2.getCode()); 241 } 242 } 243 244 245 public static void sortInclude(ConceptSetComponent inc) { 246 Collections.sort(inc.getConcept(), new ConceptReferenceComponentSorter()); 247 } 248 249 public static String getAllCodesSystem(ValueSet vs) { 250 if (vs.hasCompose()) { 251 ValueSetComposeComponent c = vs.getCompose(); 252 if (c.getExclude().isEmpty() && c.getInclude().size() == 1) { 253 ConceptSetComponent i = c.getIncludeFirstRep(); 254 if (i.hasSystem() && !i.hasValueSet() && !i.hasConcept() && !i.hasFilter()) { 255 return i.getSystem(); 256 } 257 } 258 } 259 return null; 260 } 261 262 public static boolean isDeprecated(ValueSet vs, ValueSetExpansionContainsComponent c) { 263 try { 264 for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent p : c.getProperty()) { 265 if ("status".equals(p.getCode()) && p.hasValue() && p.hasValueCodeType() && "deprecated".equals(p.getValueCodeType().getCode())) { 266 return true; 267 } 268 // this, though status should also be set 269 if ("deprecationDate".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof DateTimeType) 270 return ((DateTimeType) p.getValue()).before(new DateTimeType(Calendar.getInstance())); 271 // legacy 272 if ("deprecated".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof BooleanType) 273 return ((BooleanType) p.getValue()).getValue(); 274 } 275 StandardsStatus ss = ExtensionUtilities.getStandardsStatus(c); 276 if (ss == StandardsStatus.DEPRECATED) { 277 return true; 278 } 279 return false; 280 } catch (FHIRException e) { 281 return false; 282 } 283 } 284 285 public static boolean hasCodeInExpansion(ValueSet vs, Coding code) { 286 return hasCodeInExpansion(vs.getExpansion().getContains(), code); 287 } 288 289 private static boolean hasCodeInExpansion(List<ValueSetExpansionContainsComponent> list, Coding code) { 290 for (ValueSetExpansionContainsComponent c : list) { 291 if (c.getSystem().equals(code.getSystem()) && c.getCode().equals(code.getCode())) { 292 return true; 293 } 294 if (hasCodeInExpansion(c.getContains(), code)) { 295 return true; 296 } 297 } 298 return false; 299 } 300 301 public static org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent addCodeProperty(ValueSet vs, ValueSetExpansionContainsComponent ctxt, String url, String code, String value) { 302 if (value != null) { 303 return addProperty(vs, ctxt, url, code, new CodeType(value)); 304 } else { 305 return null; 306 } 307 } 308 public static org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent addProperty(ValueSet vs, ValueSetExpansionContainsComponent ctxt, String url, String code, String value) { 309 if (value != null) { 310 return addProperty(vs, ctxt, url, code, new StringType(value)); 311 } else { 312 return null; 313 } 314 } 315 316 public static org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent addProperty(ValueSet vs, ValueSetExpansionContainsComponent ctxt, String url, String code, Integer value) { 317 if (value != null) { 318 return addProperty(vs, ctxt, url, code, new IntegerType(value)); 319 } else { 320 return null; 321 } 322 } 323 324 public static org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent addProperty(ValueSet vs, ValueSetExpansionContainsComponent ctxt, String url, String code, DataType value) { 325 code = defineProperty(vs, url, code); 326 org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent p = getProperty(ctxt.getProperty(), code); 327 if (p != null) { 328 p.setValue(value); 329 } else { 330 p = ctxt.addProperty().setCode(code).setValue(value); 331 } 332 return p; 333 } 334 335 private static org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent getProperty(List<org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent> list, String code) { 336 for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent t : list) { 337 if (code.equals(t.getCode())) { 338 return t; 339 } 340 } 341 return null; 342 } 343 344 private static String defineProperty(ValueSet vs, String url, String code) { 345 for (ValueSetExpansionPropertyComponent p : vs.getExpansion().getProperty()) { 346 if (p.hasUri() && p.getUri().equals(url)) { 347 return p.getCode(); 348 } 349 } 350 for (ValueSetExpansionPropertyComponent p : vs.getExpansion().getProperty()) { 351 if (p.hasCode() && p.getCode().equals(code)) { 352 p.setUri(url); 353 return code; 354 } 355 } 356 ValueSetExpansionPropertyComponent p = vs.getExpansion().addProperty(); 357 p.setUri(url); 358 p.setCode(code); 359 return code; 360 } 361 362 public static int countExpansion(ValueSet valueset) { 363 int i = valueset.getExpansion().getContains().size(); 364 for (ValueSetExpansionContainsComponent t : valueset.getExpansion().getContains()) { 365 i = i + countExpansion(t); 366 } 367 return i; 368 } 369 370 public static int countExpansion(List<ValueSetExpansionContainsComponent> list) { 371 int i = list.size(); 372 for (ValueSetExpansionContainsComponent t : list) { 373 i = i + countExpansion(t); 374 } 375 return i; 376 } 377 378 private static int countExpansion(ValueSetExpansionContainsComponent c) { 379 int i = c.getContains().size(); 380 for (ValueSetExpansionContainsComponent t : c.getContains()) { 381 i = i + countExpansion(t); 382 } 383 return i; 384 } 385 386 public static Set<String> listSystems(IWorkerContext ctxt, ValueSet vs) { 387 Set<String> systems = new HashSet<>(); 388 for (ConceptSetComponent inc : vs.getCompose().getInclude()) { 389 for (CanonicalType ct : inc.getValueSet()) { 390 ValueSet vsr = ctxt.findTxResource(ValueSet.class, ct.asStringValue(), vs); 391 if (vsr != null) { 392 systems.addAll(listSystems(ctxt, vsr)); 393 } 394 } 395 if (inc.hasSystem()) { 396 systems.add(inc.getSystem()); 397 } 398 } 399 return systems; 400 } 401 402 403 public static boolean isIncompleteExpansion(ValueSet valueSet) { 404 if (valueSet.hasExpansion()) { 405 ValueSetExpansionComponent exp = valueSet.getExpansion(); 406 if (exp.hasTotal()) { 407 if (exp.getTotal() != countExpansion(exp.getContains())) { 408 return true; 409 } 410 } 411 } 412 return false; 413 } 414 415 416 public static Set<String> codes(ValueSet vs, CodeSystem cs) { 417 Set<String> res = new HashSet<>(); 418 for (ConceptSetComponent inc : vs.getCompose().getInclude()) { 419 if (inc.getSystem().equals(cs.getUrl())) { 420 addCodes(res, inc, cs.getConcept()); 421 } 422 } 423 return res; 424 } 425 426 private static void addCodes(Set<String> res, ConceptSetComponent inc, List<ConceptDefinitionComponent> list) { 427 for (ConceptDefinitionComponent cd : list) { 428 if (cd.hasCode() && (!inc.hasConcept() || inc.hasConcept(cd.getCode()))) { 429 res.add(cd.getCode()); 430 } 431 if (cd.hasConcept()) { 432 addCodes(res, inc, cd.getConcept()); 433 } 434 } 435 } 436 437 public static String versionFromExpansionParams(Parameters expParameters, String system, String defaultVersion) { 438 if (expParameters != null) { 439 for (ParametersParameterComponent p : expParameters.getParameter()) { 440 if ("system-version".equals(p.getName()) || "force-system-version".equals(p.getName())) { 441 String v = p.getValue().primitiveValue(); 442 if (v.startsWith(system+"|")) { 443 String ver = v.substring(v.indexOf("|")+1); 444 if (defaultVersion == null || ver.startsWith(defaultVersion) || "force-system-version".equals(p.getName())) { 445 return ver; 446 } 447 } 448 } 449 } 450 } 451 return defaultVersion; 452 } 453 454 public static Set<String> checkExpansionSubset(ValueSet vs1, ValueSet vs2) { 455 Set<String> codes = new HashSet<>(); 456 checkCodes(codes, vs2.getExpansion().getContains(), vs1.getExpansion().getContains()); 457 return codes; 458 } 459 460 private static void checkCodes(Set<String> codes, List<ValueSetExpansionContainsComponent> listS, List<ValueSetExpansionContainsComponent> listT) { 461 for (ValueSetExpansionContainsComponent c : listS) { 462 ValueSetExpansionContainsComponent t = findContained(c, listT); 463 if (t == null) { 464 codes.add(c.getCode()); 465 } 466 if (c.hasContains()) { 467 checkCodes(codes, c.getContains(), listT); 468 } 469 } 470 } 471 472 private static ValueSetExpansionContainsComponent findContained(ValueSetExpansionContainsComponent c, List<ValueSetExpansionContainsComponent> listT) { 473 for (ValueSetExpansionContainsComponent t : listT) { 474 if (t.getSystem().equals(c.getSystem()) && t.getCode().equals(c.getCode())) { 475 return t; 476 } 477 if (t.hasContains()) { 478 ValueSetExpansionContainsComponent tt = findContained(c, t.getContains()); 479 if (tt != null) { 480 return tt; 481 } 482 } 483 } 484 return null; 485 } 486 487 public static void setDeprecated(List<ValueSet.ValueSetExpansionPropertyComponent> vsProp, ValueSet.ValueSetExpansionContainsComponent n) { 488 if (!"deprecated".equals(ValueSetUtilities.getStatus(vsProp, n))) { 489 n.addProperty().setCode("status").setValue(new CodeType("deprecated")); 490 for (ValueSet.ValueSetExpansionPropertyComponent o : vsProp) { 491 if ("status".equals(o.getCode())) { 492 return; 493 } 494 } 495 vsProp.add(new ValueSet.ValueSetExpansionPropertyComponent().setCode("status").setUri("http://hl7.org/fhir/concept-properties#status")); 496 } 497 } 498 499 private static String getStatus(List<ValueSetExpansionPropertyComponent> vsProp, ValueSetExpansionContainsComponent n) { 500 return ValueSetUtilities.getProperty(vsProp, n, "status", "http://hl7.org/fhir/concept-properties#status"); 501 } 502 503 504 public static String getProperty(List<ValueSetExpansionPropertyComponent> vsProp, ValueSetExpansionContainsComponent focus, String code, String url) { 505 ValueSet.ValueSetExpansionPropertyComponent pc = null; 506 for (ValueSet.ValueSetExpansionPropertyComponent t : vsProp) { 507 if (t.hasUri() && t.getUri().equals(url)) { 508 pc = t; 509 } 510 } 511 if (pc == null) { 512 for (ValueSet.ValueSetExpansionPropertyComponent t : vsProp) { 513 if (t.hasCode() && t.getCode().equals(code)) { 514 pc = t; 515 } 516 } 517 } 518 if (pc != null) { 519 for (ValueSet.ConceptPropertyComponent t : focus.getProperty()) { 520 if (t.hasCode() && t.getCode().equals(pc.getCode())) { 521 return t.getValue().primitiveValue(); 522 } 523 } 524 } 525 return null; 526 } 527 528 529}