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.Enumerations.FilterOperator; 046import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; 047import org.hl7.fhir.r5.model.Identifier; 048import org.hl7.fhir.r5.model.Meta; 049import org.hl7.fhir.r5.model.UriType; 050import org.hl7.fhir.r5.model.ValueSet; 051import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; 052import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent; 053import org.hl7.fhir.r5.model.CodeType; 054import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent; 055import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; 056import org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent; 057import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 058import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent; 059import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.ConceptDefinitionComponentSorter; 060import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.ConceptStatus; 061import org.hl7.fhir.r5.utils.ToolingExtensions; 062import org.hl7.fhir.utilities.StandardsStatus; 063import org.hl7.fhir.utilities.Utilities; 064 065public class ValueSetUtilities { 066 067 public static ValueSet makeShareable(ValueSet vs) { 068 if (!vs.hasExperimental()) { 069 vs.setExperimental(false); 070 } 071 if (!vs.hasMeta()) 072 vs.setMeta(new Meta()); 073 for (UriType t : vs.getMeta().getProfile()) 074 if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablevalueset")) 075 return vs; 076 vs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablevalueset")); 077 return vs; 078 } 079 080 public static boolean makeVSShareable(ValueSet vs) { 081 if (!vs.hasMeta()) 082 vs.setMeta(new Meta()); 083 for (UriType t : vs.getMeta().getProfile()) 084 if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablevalueset")) 085 return false; 086 vs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablevalueset")); 087 return true; 088 } 089 090 public static void checkShareable(ValueSet vs) { 091 if (!vs.hasMeta()) 092 throw new Error("ValueSet "+vs.getUrl()+" is not shareable"); 093 for (UriType t : vs.getMeta().getProfile()) { 094 if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablevalueset")) 095 return; 096 } 097 throw new Error("ValueSet "+vs.getUrl()+" is not shareable"); 098 } 099 100 public static boolean hasOID(ValueSet vs) { 101 return getOID(vs) != null; 102 } 103 104 public static String getOID(ValueSet vs) { 105 for (Identifier id : vs.getIdentifier()) { 106 if ("urn:ietf:rfc:3986".equals(id.getSystem()) && id.hasValue() && id.getValue().startsWith("urn:oid:")) 107 return id.getValue().substring(8); 108 } 109 return null; 110 } 111 112 public static void setOID(ValueSet vs, String oid) { 113 if (!oid.startsWith("urn:oid:")) 114 oid = "urn:oid:" + oid; 115 for (Identifier id : vs.getIdentifier()) { 116 if ("urn:ietf:rfc:3986".equals(id.getSystem()) && id.hasValue() && id.getValue().startsWith("urn:oid:")) { 117 id.setValue(oid); 118 return; 119 } 120 } 121 vs.addIdentifier().setSystem("urn:ietf:rfc:3986").setValue(oid); 122 } 123 124 public static void markStatus(ValueSet vs, String wg, StandardsStatus status, String pckage, String fmm, IWorkerContext context, String normativeVersion) throws FHIRException { 125 if (vs.hasUserData("external.url")) 126 return; 127 128 if (wg != null) { 129 if (!ToolingExtensions.hasExtension(vs, ToolingExtensions.EXT_WORKGROUP) || 130 (!Utilities.existsInList(ToolingExtensions.readStringExtension(vs, ToolingExtensions.EXT_WORKGROUP), "fhir", "vocab") && Utilities.existsInList(wg, "fhir", "vocab"))) { 131 ToolingExtensions.setCodeExtension(vs, ToolingExtensions.EXT_WORKGROUP, wg); 132 } 133 } 134 if (status != null) { 135 StandardsStatus ss = ToolingExtensions.getStandardsStatus(vs); 136 if (ss == null || ss.isLowerThan(status)) 137 ToolingExtensions.setStandardsStatus(vs, status, normativeVersion); 138 if (pckage != null) { 139 if (!vs.hasUserData("ballot.package")) 140 vs.setUserData("ballot.package", pckage); 141 else if (!pckage.equals(vs.getUserString("ballot.package"))) 142 if (!"infrastructure".equals(vs.getUserString("ballot.package"))) 143 System.out.println("Value Set "+vs.getUrl()+": ownership clash "+pckage+" vs "+vs.getUserString("ballot.package")); 144 } 145 if (status == StandardsStatus.NORMATIVE) { 146 vs.setExperimental(false); 147 vs.setStatus(PublicationStatus.ACTIVE); 148 } 149 } 150 if (fmm != null) { 151 String sfmm = ToolingExtensions.readStringExtension(vs, ToolingExtensions.EXT_FMM_LEVEL); 152 if (Utilities.noString(sfmm) || Integer.parseInt(sfmm) < Integer.parseInt(fmm)) { 153 ToolingExtensions.setIntegerExtension(vs, ToolingExtensions.EXT_FMM_LEVEL, Integer.parseInt(fmm)); 154 } 155 if (Integer.parseInt(fmm) <= 1) { 156 vs.setExperimental(true); 157 } 158 } 159 if (vs.hasUserData("cs")) 160 CodeSystemUtilities.markStatus((CodeSystem) vs.getUserData("cs"), wg, status, pckage, fmm, normativeVersion); 161 else if (status == StandardsStatus.NORMATIVE && context != null) { 162 for (ConceptSetComponent csc : vs.getCompose().getInclude()) { 163 if (csc.hasSystem()) { 164 CodeSystem cs = context.fetchCodeSystem(csc.getSystem()); 165 if (cs != null) { 166 CodeSystemUtilities.markStatus(cs, wg, status, pckage, fmm, normativeVersion); 167 } 168 } 169 } 170 } 171 } 172 173 private static int ssval(String status) { 174 if ("Draft".equals("status")) 175 return 1; 176 if ("Informative".equals("status")) 177 return 2; 178 if ("External".equals("status")) 179 return 3; 180 if ("Trial Use".equals("status")) 181 return 3; 182 if ("Normative".equals("status")) 183 return 4; 184 return -1; 185 } 186 187 public static ValueSet generateImplicitValueSet(String uri) { 188 if (uri.startsWith("http://snomed.info/sct")) 189 return generateImplicitSnomedValueSet(uri); 190 if (uri.startsWith("http://loinc.org/vs")) 191 return generateImplicitLoincValueSet(uri); 192 if (uri.equals("http://hl7.org/fhir/ValueSet/mimetypes")) { 193 return generateImplicitMimetypesValueSet(uri); 194 } 195 return null; 196 } 197 198 private static ValueSet generateImplicitMimetypesValueSet(String theUri) { 199 ValueSet valueSet = new ValueSet(); 200 valueSet.setStatus(PublicationStatus.ACTIVE); 201 valueSet.setUrl(theUri); 202 valueSet.setDescription("This value set includes all possible codes from BCP-13 (http://tools.ietf.org/html/bcp13)"); 203 valueSet.getCompose() 204 .addInclude().setSystem("urn:ietf:bcp:13"); 205 return valueSet; 206 } 207 208 private static ValueSet generateImplicitLoincValueSet(String uri) { 209 if ("http://loinc.org/vs".equals(uri)) 210 return makeLoincValueSet(); 211 if (uri.startsWith("http://loinc.org/vs/LL")) 212 return makeAnswerList(makeLoincValueSet(), uri); 213 return null; 214 } 215 216 private static ValueSet makeAnswerList(ValueSet vs, String uri) { 217 vs.setUrl(uri); 218 String c = uri.substring(20); 219 vs.setName("LOINCAnswers"+c); 220 vs.setTitle("LOINC Answer Codes for "+c); 221 vs.getCompose().getIncludeFirstRep().addFilter().setProperty("LIST").setOp(FilterOperator.EQUAL).setValue(c); 222 return vs; 223 } 224 225 private static ValueSet makeLoincValueSet() { 226 ValueSet vs = new ValueSet(); 227 vs.setUrl("http://loinc.org/vs"); 228 vs.setName("LOINCCodes"); 229 vs.setTitle("All LOINC codes"); 230 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"); 231 vs.setStatus(PublicationStatus.ACTIVE); 232 vs.getCompose().addInclude().setSystem("http://loinc.org"); 233 return vs; 234 } 235 236 private static ValueSet generateImplicitSnomedValueSet(String uri) { 237 if ("http://snomed.info/sct?fhir_vs".equals(uri)) 238 return makeImplicitSnomedValueSet(uri); 239 return null; 240 } 241 242 private static ValueSet makeImplicitSnomedValueSet(String uri) { 243 ValueSet vs = new ValueSet(); 244 vs.setUrl(uri); 245 vs.setName("SCTValueSet"); 246 vs.setTitle("SCT ValueSet"); 247 vs.setDescription("All SNOMED CT Concepts"); 248 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"); 249 vs.setStatus(PublicationStatus.ACTIVE); 250 vs.getCompose().addInclude().setSystem("http://snomed.info/sct"); 251 return vs; 252 } 253 254 public static void setDeprecated(List<ValueSetExpansionPropertyComponent> vsProp, ValueSetExpansionContainsComponent n) { 255 n.addProperty().setCode("status").setValue(new CodeType("deprecated")); 256 for (ValueSetExpansionPropertyComponent o : vsProp) { 257 if ("status".equals(o.getCode())) { 258 return; 259 } 260 } 261 vsProp.add(new ValueSetExpansionPropertyComponent().setCode("status").setUri("http://hl7.org/fhir/concept-properties#status")); 262 } 263 264 265 public static class ConceptReferenceComponentSorter implements Comparator<ConceptReferenceComponent> { 266 267 @Override 268 public int compare(ConceptReferenceComponent o1, ConceptReferenceComponent o2) { 269 return o1.getCode().compareToIgnoreCase(o2.getCode()); 270 } 271 } 272 273 274 public static void sortInclude(ConceptSetComponent inc) { 275 Collections.sort(inc.getConcept(), new ConceptReferenceComponentSorter()); 276 } 277 278 public static String getAllCodesSystem(ValueSet vs) { 279 if (vs.hasCompose()) { 280 ValueSetComposeComponent c = vs.getCompose(); 281 if (c.getExclude().isEmpty() && c.getInclude().size() == 1) { 282 ConceptSetComponent i = c.getIncludeFirstRep(); 283 if (i.hasSystem() && !i.hasValueSet() && !i.hasConcept() && !i.hasFilter()) { 284 return i.getSystem(); 285 } 286 } 287 } 288 return null; 289 } 290 291 public static boolean isDeprecated(ValueSet vs, ValueSetExpansionContainsComponent c) { 292 try { 293 for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent p : c.getProperty()) { 294 if ("status".equals(p.getCode()) && p.hasValue() && p.hasValueCodeType() && "deprecated".equals(p.getValueCodeType().getCode())) { 295 return true; 296 } 297 // this, though status should also be set 298 if ("deprecationDate".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof DateTimeType) 299 return ((DateTimeType) p.getValue()).before(new DateTimeType(Calendar.getInstance())); 300 // legacy 301 if ("deprecated".equals(p.getCode()) && p.hasValue() && p.getValue() instanceof BooleanType) 302 return ((BooleanType) p.getValue()).getValue(); 303 } 304 StandardsStatus ss = ToolingExtensions.getStandardsStatus(c); 305 if (ss == StandardsStatus.DEPRECATED) { 306 return true; 307 } 308 return false; 309 } catch (FHIRException e) { 310 return false; 311 } 312 } 313 314 315}