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