001package org.hl7.fhir.r5.terminologies; 002 003import java.util.Calendar; 004import java.util.Collections; 005import java.util.Comparator; 006import java.util.List; 007 008/* 009 Copyright (c) 2011+, HL7, Inc. 010 All rights reserved. 011 012 Redistribution and use in source and binary forms, with or without modification, 013 are permitted provided that the following conditions are met: 014 015 * Redistributions of source code must retain the above copyright notice, this 016 list of conditions and the following disclaimer. 017 * Redistributions in binary form must reproduce the above copyright notice, 018 this list of conditions and the following disclaimer in the documentation 019 and/or other materials provided with the distribution. 020 * Neither the name of HL7 nor the names of its contributors may be used to 021 endorse or promote products derived from this software without specific 022 prior written permission. 023 024 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 025 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 026 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 027 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 028 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 029 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 030 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 031 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 032 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 033 POSSIBILITY OF SUCH DAMAGE. 034 035 */ 036 037 038import org.hl7.fhir.exceptions.FHIRException; 039import org.hl7.fhir.exceptions.FHIRFormatError; 040import org.hl7.fhir.r5.context.IWorkerContext; 041import org.hl7.fhir.r5.model.BooleanType; 042import org.hl7.fhir.r5.model.CanonicalType; 043import org.hl7.fhir.r5.model.CodeSystem; 044import org.hl7.fhir.r5.model.DateTimeType; 045import org.hl7.fhir.r5.model.StringType; 046import org.hl7.fhir.r5.model.IntegerType; 047import org.hl7.fhir.r5.model.Enumerations.FilterOperator; 048import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; 049import org.hl7.fhir.r5.model.Identifier; 050import org.hl7.fhir.r5.model.Meta; 051import org.hl7.fhir.r5.model.UriType; 052import org.hl7.fhir.r5.model.ValueSet; 053import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; 054import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent; 055import org.hl7.fhir.r5.model.CodeType; 056import org.hl7.fhir.r5.model.Coding; 057import org.hl7.fhir.r5.model.DataType; 058import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent; 059import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; 060import org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent; 061import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 062import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent; 063import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.ConceptDefinitionComponentSorter; 064import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.ConceptStatus; 065import org.hl7.fhir.r5.utils.ToolingExtensions; 066import org.hl7.fhir.utilities.StandardsStatus; 067import org.hl7.fhir.utilities.Utilities; 068 069public class ValueSetUtilities { 070 071 072 public static boolean isServerSide(String url) { 073 return Utilities.existsInList(url, "http://hl7.org/fhir/sid/cvx"); 074 } 075 076 public static ValueSet makeShareable(ValueSet vs) { 077 if (!vs.hasExperimental()) { 078 vs.setExperimental(false); 079 } 080 if (!vs.hasMeta()) 081 vs.setMeta(new Meta()); 082 for (UriType t : vs.getMeta().getProfile()) 083 if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablevalueset")) 084 return vs; 085 vs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablevalueset")); 086 return vs; 087 } 088 089 public static boolean makeVSShareable(ValueSet vs) { 090 if (!vs.hasMeta()) 091 vs.setMeta(new Meta()); 092 for (UriType t : vs.getMeta().getProfile()) 093 if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablevalueset")) 094 return false; 095 vs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablevalueset")); 096 return true; 097 } 098 099 public static void checkShareable(ValueSet vs) { 100 if (!vs.hasMeta()) 101 throw new Error("ValueSet "+vs.getUrl()+" is not shareable"); 102 for (UriType t : vs.getMeta().getProfile()) { 103 if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablevalueset")) 104 return; 105 } 106 throw new Error("ValueSet "+vs.getUrl()+" is not shareable"); 107 } 108 109 public static boolean hasOID(ValueSet vs) { 110 return getOID(vs) != null; 111 } 112 113 public static String getOID(ValueSet vs) { 114 for (Identifier id : vs.getIdentifier()) { 115 if ("urn:ietf:rfc:3986".equals(id.getSystem()) && id.hasValue() && id.getValue().startsWith("urn:oid:")) 116 return id.getValue().substring(8); 117 } 118 return null; 119 } 120 121 public static void setOID(ValueSet vs, String oid) { 122 if (!oid.startsWith("urn:oid:")) 123 oid = "urn:oid:" + oid; 124 for (Identifier id : vs.getIdentifier()) { 125 if ("urn:ietf:rfc:3986".equals(id.getSystem()) && id.hasValue() && id.getValue().startsWith("urn:oid:")) { 126 id.setValue(oid); 127 return; 128 } 129 } 130 vs.addIdentifier().setSystem("urn:ietf:rfc:3986").setValue(oid); 131 } 132 133 public static void markStatus(ValueSet vs, String wg, StandardsStatus status, String pckage, String fmm, IWorkerContext context, String normativeVersion) throws FHIRException { 134 if (vs.hasUserData("external.url")) 135 return; 136 137 if (wg != null) { 138 if (!ToolingExtensions.hasExtension(vs, ToolingExtensions.EXT_WORKGROUP) || 139 (!Utilities.existsInList(ToolingExtensions.readStringExtension(vs, ToolingExtensions.EXT_WORKGROUP), "fhir", "vocab") && Utilities.existsInList(wg, "fhir", "vocab"))) { 140 ToolingExtensions.setCodeExtension(vs, ToolingExtensions.EXT_WORKGROUP, wg); 141 } 142 } 143 if (status != null) { 144 StandardsStatus ss = ToolingExtensions.getStandardsStatus(vs); 145 if (ss == null || ss.isLowerThan(status)) 146 ToolingExtensions.setStandardsStatus(vs, status, normativeVersion); 147 if (pckage != null) { 148 if (!vs.hasUserData("ballot.package")) 149 vs.setUserData("ballot.package", pckage); 150 else if (!pckage.equals(vs.getUserString("ballot.package"))) 151 if (!"infrastructure".equals(vs.getUserString("ballot.package"))) 152 System.out.println("Value Set "+vs.getUrl()+": ownership clash "+pckage+" vs "+vs.getUserString("ballot.package")); 153 } 154 if (status == StandardsStatus.NORMATIVE) { 155 vs.setExperimental(false); 156 vs.setStatus(PublicationStatus.ACTIVE); 157 } 158 } 159 if (fmm != null) { 160 String sfmm = ToolingExtensions.readStringExtension(vs, ToolingExtensions.EXT_FMM_LEVEL); 161 if (Utilities.noString(sfmm) || Integer.parseInt(sfmm) < Integer.parseInt(fmm)) { 162 ToolingExtensions.setIntegerExtension(vs, ToolingExtensions.EXT_FMM_LEVEL, Integer.parseInt(fmm)); 163 } 164 if (Integer.parseInt(fmm) <= 1) { 165 vs.setExperimental(true); 166 } 167 } 168 if (vs.hasUserData("cs")) 169 CodeSystemUtilities.markStatus((CodeSystem) vs.getUserData("cs"), wg, status, pckage, fmm, normativeVersion); 170 else if (status == StandardsStatus.NORMATIVE && context != null) { 171 for (ConceptSetComponent csc : vs.getCompose().getInclude()) { 172 if (csc.hasSystem()) { 173 CodeSystem cs = context.fetchCodeSystem(csc.getSystem()); 174 if (cs != null) { 175 CodeSystemUtilities.markStatus(cs, wg, status, pckage, fmm, normativeVersion); 176 } 177 } 178 } 179 } 180 } 181 182 private static int ssval(String status) { 183 if ("Draft".equals("status")) 184 return 1; 185 if ("Informative".equals("status")) 186 return 2; 187 if ("External".equals("status")) 188 return 3; 189 if ("Trial Use".equals("status")) 190 return 3; 191 if ("Normative".equals("status")) 192 return 4; 193 return -1; 194 } 195 196 public static ValueSet generateImplicitValueSet(String uri) { 197 if (uri.startsWith("http://snomed.info/sct")) 198 return generateImplicitSnomedValueSet(uri); 199 if (uri.startsWith("http://loinc.org/vs")) 200 return generateImplicitLoincValueSet(uri); 201 if (uri.equals("http://hl7.org/fhir/ValueSet/mimetypes")) { 202 return generateImplicitMimetypesValueSet(uri); 203 } 204 return null; 205 } 206 207 private static ValueSet generateImplicitMimetypesValueSet(String theUri) { 208 ValueSet valueSet = new ValueSet(); 209 valueSet.setStatus(PublicationStatus.ACTIVE); 210 valueSet.setUrl(theUri); 211 valueSet.setDescription("This value set includes all possible codes from BCP-13 (http://tools.ietf.org/html/bcp13)"); 212 valueSet.getCompose() 213 .addInclude().setSystem("urn:ietf:bcp:13"); 214 return valueSet; 215 } 216 217 private static ValueSet generateImplicitLoincValueSet(String uri) { 218 if ("http://loinc.org/vs".equals(uri)) 219 return makeLoincValueSet(); 220 if (uri.startsWith("http://loinc.org/vs/LL")) 221 return makeAnswerList(makeLoincValueSet(), uri); 222 return null; 223 } 224 225 private static ValueSet makeAnswerList(ValueSet vs, String uri) { 226 vs.setUrl(uri); 227 String c = uri.substring(20); 228 vs.setName("LOINCAnswers"+c); 229 vs.setTitle("LOINC Answer Codes for "+c); 230 vs.getCompose().getIncludeFirstRep().addFilter().setProperty("LIST").setOp(FilterOperator.EQUAL).setValue(c); 231 return vs; 232 } 233 234 private static ValueSet makeLoincValueSet() { 235 ValueSet vs = new ValueSet(); 236 vs.setUrl("http://loinc.org/vs"); 237 vs.setName("LOINCCodes"); 238 vs.setTitle("All LOINC codes"); 239 vs.setCopyright("This content LOINC® is copyright © 1995 Regenstrief Institute, Inc. and the LOINC Committee, and available at no cost under the license at http://loinc.org/terms-of-use"); 240 vs.setStatus(PublicationStatus.ACTIVE); 241 vs.getCompose().addInclude().setSystem("http://loinc.org"); 242 return vs; 243 } 244 245 private static ValueSet generateImplicitSnomedValueSet(String uri) { 246 if ("http://snomed.info/sct?fhir_vs".equals(uri)) 247 return makeImplicitSnomedValueSet(uri); 248 return null; 249 } 250 251 private static ValueSet makeImplicitSnomedValueSet(String uri) { 252 ValueSet vs = new ValueSet(); 253 vs.setUrl(uri); 254 vs.setName("SCTValueSet"); 255 vs.setTitle("SCT ValueSet"); 256 vs.setDescription("All SNOMED CT Concepts"); 257 vs.setCopyright("This value set includes content from SNOMED CT, which is copyright © 2002+ International Health Terminology Standards Development Organisation (SNOMED International), and distributed by agreement between SNOMED International and HL7. Implementer use of SNOMED CT is not covered by this agreement"); 258 vs.setStatus(PublicationStatus.ACTIVE); 259 vs.getCompose().addInclude().setSystem("http://snomed.info/sct"); 260 return vs; 261 } 262 263 public static void setDeprecated(List<ValueSetExpansionPropertyComponent> vsProp, ValueSetExpansionContainsComponent n) { 264 n.addProperty().setCode("status").setValue(new CodeType("deprecated")); 265 for (ValueSetExpansionPropertyComponent o : vsProp) { 266 if ("status".equals(o.getCode())) { 267 return; 268 } 269 } 270 vsProp.add(new ValueSetExpansionPropertyComponent().setCode("status").setUri("http://hl7.org/fhir/concept-properties#status")); 271 } 272 273 274 public static class ConceptReferenceComponentSorter implements Comparator<ConceptReferenceComponent> { 275 276 @Override 277 public int compare(ConceptReferenceComponent o1, ConceptReferenceComponent o2) { 278 return o1.getCode().compareToIgnoreCase(o2.getCode()); 279 } 280 } 281 282 283 public static void sortInclude(ConceptSetComponent inc) { 284 Collections.sort(inc.getConcept(), new ConceptReferenceComponentSorter()); 285 } 286 287 public static String getAllCodesSystem(ValueSet vs) { 288 if (vs.hasCompose()) { 289 ValueSetComposeComponent c = vs.getCompose(); 290 if (c.getExclude().isEmpty() && c.getInclude().size() == 1) { 291 ConceptSetComponent i = c.getIncludeFirstRep(); 292 if (i.hasSystem() && !i.hasValueSet() && !i.hasConcept() && !i.hasFilter()) { 293 return i.getSystem(); 294 } 295 } 296 } 297 return null; 298 } 299 300 public static boolean isDeprecated(ValueSet vs, ValueSetExpansionContainsComponent c) { 301 try { 302 for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent p : c.getProperty()) { 303 if ("status".equals(p.getCode()) && p.hasValue() && p.hasValueCodeType() && "deprecated".equals(p.getValueCodeType().getCode())) { 304 return true; 305 } 306 // this, though status should also be set 307 if ("deprecationDate".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof DateTimeType) 308 return ((DateTimeType) p.getValue()).before(new DateTimeType(Calendar.getInstance())); 309 // legacy 310 if ("deprecated".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof BooleanType) 311 return ((BooleanType) p.getValue()).getValue(); 312 } 313 StandardsStatus ss = ToolingExtensions.getStandardsStatus(c); 314 if (ss == StandardsStatus.DEPRECATED) { 315 return true; 316 } 317 return false; 318 } catch (FHIRException e) { 319 return false; 320 } 321 } 322 323 public static boolean hasCodeInExpansion(ValueSet vs, Coding code) { 324 return hasCodeInExpansion(vs.getExpansion().getContains(), code); 325 } 326 327 private static boolean hasCodeInExpansion(List<ValueSetExpansionContainsComponent> list, Coding code) { 328 for (ValueSetExpansionContainsComponent c : list) { 329 if (c.getSystem().equals(code.getSystem()) && c.getCode().equals(code.getCode())) { 330 return true; 331 } 332 if (hasCodeInExpansion(c.getContains(), code)) { 333 return true; 334 } 335 } 336 return false; 337 } 338 339 public static void addProperty(ValueSet vs, ValueSetExpansionContainsComponent ctxt, String url, String code, String value) { 340 if (value != null) { 341 addProperty(vs, ctxt, url, code, new StringType(value)); 342 } 343 } 344 345 public static void addProperty(ValueSet vs, ValueSetExpansionContainsComponent ctxt, String url, String code, Integer value) { 346 if (value != null) { 347 addProperty(vs, ctxt, url, code, new IntegerType(value)); 348 } 349 } 350 351 public static void addProperty(ValueSet vs, ValueSetExpansionContainsComponent ctxt, String url, String code, DataType value) { 352 code = defineProperty(vs, url, code); 353 org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent p = getProperty(ctxt.getProperty(), code); 354 if (p != null) 355 p.setValue(value); 356 else 357 ctxt.addProperty().setCode(code).setValue(value); 358 359 } 360 361 private static org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent getProperty(List<org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent> list, String code) { 362 for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent t : list) { 363 if (code.equals(t.getCode())) { 364 return t; 365 } 366 } 367 return null; 368 } 369 370 private static String defineProperty(ValueSet vs, String url, String code) { 371 for (ValueSetExpansionPropertyComponent p : vs.getExpansion().getProperty()) { 372 if (p.hasUri() && p.getUri().equals(url)) { 373 return p.getCode(); 374 } 375 } 376 for (ValueSetExpansionPropertyComponent p : vs.getExpansion().getProperty()) { 377 if (p.hasCode() && p.getCode().equals(code)) { 378 p.setUri(url); 379 return code; 380 } 381 } 382 ValueSetExpansionPropertyComponent p = vs.getExpansion().addProperty(); 383 p.setUri(url); 384 p.setCode(code); 385 return code; 386 } 387 388 public static int countExpansion(ValueSet valueset) { 389 int i = valueset.getExpansion().getContains().size(); 390 for (ValueSetExpansionContainsComponent t : valueset.getExpansion().getContains()) { 391 i = i + countExpansion(t); 392 } 393 return i; 394 } 395 396 private static int countExpansion(ValueSetExpansionContainsComponent c) { 397 int i = c.getContains().size(); 398 for (ValueSetExpansionContainsComponent t : c.getContains()) { 399 i = i + countExpansion(t); 400 } 401 return i; 402 } 403 404 405}