
001package org.hl7.fhir.r4.terminologies; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032import java.util.ArrayList; 033import java.util.Calendar; 034import java.util.Collections; 035import java.util.Comparator; 036import java.util.List; 037 038import lombok.extern.slf4j.Slf4j; 039import org.hl7.fhir.exceptions.FHIRException; 040import org.hl7.fhir.exceptions.FHIRFormatError; 041import org.hl7.fhir.r4.model.BooleanType; 042import org.hl7.fhir.r4.model.CanonicalType; 043import org.hl7.fhir.r4.model.CodeSystem; 044import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; 045import org.hl7.fhir.r4.model.CodeSystem.ConceptPropertyComponent; 046import org.hl7.fhir.r4.model.CodeSystem.PropertyComponent; 047import org.hl7.fhir.r4.model.CodeSystem.PropertyType; 048import org.hl7.fhir.r4.model.CodeType; 049import org.hl7.fhir.r4.model.DateTimeType; 050import org.hl7.fhir.r4.model.Enumerations.PublicationStatus; 051import org.hl7.fhir.r4.model.Identifier; 052import org.hl7.fhir.r4.model.Meta; 053import org.hl7.fhir.r4.model.Type; 054import org.hl7.fhir.r4.model.UriType; 055import org.hl7.fhir.r4.utils.ToolingExtensions; 056import org.hl7.fhir.r4.model.Coding; 057import org.hl7.fhir.utilities.StandardsStatus; 058import org.hl7.fhir.utilities.Utilities; 059 060@Slf4j 061public class CodeSystemUtilities { 062 063 public static boolean isNotSelectable(CodeSystem cs, ConceptDefinitionComponent def) { 064 for (ConceptPropertyComponent p : def.getProperty()) { 065 if (p.getCode().equals("notSelectable") && p.hasValue() && p.getValue() instanceof BooleanType) 066 return ((BooleanType) p.getValue()).getValue(); 067 } 068 return false; 069 } 070 071 public static void setNotSelectable(CodeSystem cs, ConceptDefinitionComponent concept) throws FHIRFormatError { 072 defineNotSelectableProperty(cs); 073 ConceptPropertyComponent p = getProperty(concept, "abstract"); 074 if (p != null) 075 p.setValue(new BooleanType(true)); 076 else 077 concept.addProperty().setCode("notSelectable").setValue(new BooleanType(true)); 078 } 079 080 public static void defineNotSelectableProperty(CodeSystem cs) { 081 defineCodeSystemProperty(cs, "notSelectable", 082 "Indicates that the code is abstract - only intended to be used as a selector for other concepts", 083 PropertyType.BOOLEAN); 084 } 085 086 087 public static Coding readCoding(String jurisdiction) { 088 return jurisdiction == null || !jurisdiction.contains("#") ? null : new Coding().setCode(jurisdiction.substring(jurisdiction.indexOf("#")+1)).setSystem(jurisdiction.substring(0, jurisdiction.indexOf("#"))); 089 } 090 091 public enum ConceptStatus { 092 Active, Experimental, Deprecated, Retired; 093 094 public String toCode() { 095 switch (this) { 096 case Active: 097 return "active"; 098 case Experimental: 099 return "experimental"; 100 case Deprecated: 101 return "deprecated"; 102 case Retired: 103 return "retired"; 104 default: 105 return null; 106 } 107 } 108 } 109 110 public static void setStatus(CodeSystem cs, ConceptDefinitionComponent concept, ConceptStatus status) 111 throws FHIRFormatError { 112 defineStatusProperty(cs); 113 ConceptPropertyComponent p = getProperty(concept, "status"); 114 if (p != null) 115 p.setValue(new CodeType(status.toCode())); 116 else 117 concept.addProperty().setCode("status").setValue(new CodeType(status.toCode())); 118 } 119 120 public static void defineStatusProperty(CodeSystem cs) { 121 defineCodeSystemProperty(cs, "status", 122 "A property that indicates the status of the concept. One of active, experimental, deprecated, retired", 123 PropertyType.CODE); 124 } 125 126 private static void defineDeprecatedProperty(CodeSystem cs) { 127 defineCodeSystemProperty(cs, "deprecationDate", 128 "The date at which a concept was deprecated. Concepts that are deprecated but not inactive can still be used, but their use is discouraged", 129 PropertyType.DATETIME); 130 } 131 132 public static void defineParentProperty(CodeSystem cs) { 133 defineCodeSystemProperty(cs, "parent", 134 "The concept identified in this property is a parent of the concept on which it is a property. The property type will be 'code'. The meaning of parent/child relationships is defined by the hierarchyMeaning attribute", 135 PropertyType.CODE); 136 } 137 138 public static void defineChildProperty(CodeSystem cs) { 139 defineCodeSystemProperty(cs, "child", 140 "The concept identified in this property is a child of the concept on which it is a property. The property type will be 'code'. The meaning of parent/child relationships is defined by the hierarchyMeaning attribute", 141 PropertyType.CODE); 142 } 143 144 public static boolean isDeprecated(CodeSystem cs, ConceptDefinitionComponent def) { 145 try { 146 for (ConceptPropertyComponent p : def.getProperty()) { 147 if (p.getCode().equals("status") && p.hasValue() && p.hasValueCodeType() 148 && p.getValueCodeType().getCode().equals("deprecated")) 149 return true; 150 // this, though status should also be set 151 if (p.getCode().equals("deprecationDate") && p.hasValue() && p.getValue() instanceof DateTimeType) 152 return ((DateTimeType) p.getValue()).before(new DateTimeType(Calendar.getInstance())); 153 // legacy 154 if (p.getCode().equals("deprecated") && p.hasValue() && p.getValue() instanceof BooleanType) 155 return ((BooleanType) p.getValue()).getValue(); 156 } 157 return false; 158 } catch (FHIRException e) { 159 return false; 160 } 161 } 162 163 public static void setDeprecated(CodeSystem cs, ConceptDefinitionComponent concept, DateTimeType date) 164 throws FHIRFormatError { 165 setStatus(cs, concept, ConceptStatus.Deprecated); 166 defineDeprecatedProperty(cs); 167 concept.addProperty().setCode("deprecationDate").setValue(date); 168 } 169 170 public static boolean isInactive(CodeSystem cs, ConceptDefinitionComponent def) throws FHIRException { 171 for (ConceptPropertyComponent p : def.getProperty()) { 172 if (p.getCode().equals("status") && p.hasValueStringType()) 173 return "inactive".equals(p.getValueStringType()); 174 } 175 return false; 176 } 177 178 public static boolean isInactive(CodeSystem cs, String code) throws FHIRException { 179 ConceptDefinitionComponent def = findCode(cs.getConcept(), code); 180 if (def == null) 181 return true; 182 return isInactive(cs, def); 183 } 184 185 public static PropertyComponent defineCodeSystemProperty(CodeSystem cs, String code, String description, 186 PropertyType type) { 187 for (PropertyComponent p : cs.getProperty()) { 188 if (p.getCode().equals(code)) 189 return p; 190 } 191 PropertyComponent p = cs.addProperty(); 192 p.setCode(code).setDescription(description).setType(type).setUri("http://hl7.org/fhir/concept-properties#" + code); 193 return p; 194 } 195 196 public static String getCodeDefinition(CodeSystem cs, String code) { 197 return getCodeDefinition(cs.getConcept(), code); 198 } 199 200 private static String getCodeDefinition(List<ConceptDefinitionComponent> list, String code) { 201 for (ConceptDefinitionComponent c : list) { 202 if (c.hasCode() && c.getCode().equals(code)) 203 return c.getDefinition(); 204 String s = getCodeDefinition(c.getConcept(), code); 205 if (s != null) 206 return s; 207 } 208 return null; 209 } 210 211 public static CodeSystem makeShareable(CodeSystem cs) { 212 if (!cs.hasMeta()) 213 cs.setMeta(new Meta()); 214 for (UriType t : cs.getMeta().getProfile()) 215 if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablecodesystem")) 216 return cs; 217 cs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablecodesystem")); 218 return cs; 219 } 220 221 public static void setOID(CodeSystem cs, String oid) { 222 if (!oid.startsWith("urn:oid:")) 223 oid = "urn:oid:" + oid; 224 if (!cs.hasIdentifier()) 225 cs.addIdentifier(new Identifier().setSystem("urn:ietf:rfc:3986").setValue(oid)); 226 else if ("urn:ietf:rfc:3986".equals(cs.getIdentifierFirstRep().getSystem()) && cs.getIdentifierFirstRep().hasValue() 227 && cs.getIdentifierFirstRep().getValue().startsWith("urn:oid:")) 228 cs.getIdentifierFirstRep().setValue(oid); 229 else 230 throw new Error("unable to set OID on code system"); 231 232 } 233 234 public static boolean hasOID(CodeSystem cs) { 235 return getOID(cs) != null; 236 } 237 238 public static String getOID(CodeSystem cs) { 239 if (cs.hasIdentifier() && "urn:ietf:rfc:3986".equals(cs.getIdentifierFirstRep().getSystem()) 240 && cs.getIdentifierFirstRep().hasValue() && cs.getIdentifierFirstRep().getValue().startsWith("urn:oid:")) 241 return cs.getIdentifierFirstRep().getValue().substring(8); 242 return null; 243 } 244 245 public static ConceptDefinitionComponent findCode(List<ConceptDefinitionComponent> list, String code) { 246 for (ConceptDefinitionComponent c : list) { 247 if (c.getCode().equals(code)) 248 return c; 249 ConceptDefinitionComponent s = findCode(c.getConcept(), code); 250 if (s != null) 251 return s; 252 } 253 return null; 254 } 255 256 public static void markStatus(CodeSystem cs, String wg, StandardsStatus status, String pckage, String fmm, 257 String normativeVersion) throws FHIRException { 258 if (wg != null) { 259 if (!ToolingExtensions.hasExtension(cs, ToolingExtensions.EXT_WORKGROUP) 260 || (Utilities.existsInList(ToolingExtensions.readStringExtension(cs, ToolingExtensions.EXT_WORKGROUP), "fhir", 261 "vocab") && !Utilities.existsInList(wg, "fhir", "vocab"))) { 262 ToolingExtensions.setCodeExtension(cs, ToolingExtensions.EXT_WORKGROUP, wg); 263 } 264 } 265 if (status != null) { 266 StandardsStatus ss = ToolingExtensions.getStandardsStatus(cs); 267 if (ss == null || ss.isLowerThan(status)) 268 ToolingExtensions.setStandardsStatus(cs, status, normativeVersion); 269 if (pckage != null) { 270 if (!cs.hasUserData("ballot.package")) 271 cs.setUserData("ballot.package", pckage); 272 else if (!pckage.equals(cs.getUserString("ballot.package"))) 273 if (!"infrastructure".equals(cs.getUserString("ballot.package"))) 274 log.warn("Code System " + cs.getUrl() + ": ownership clash " + pckage + " vs " 275 + cs.getUserString("ballot.package")); 276 } 277 if (status == StandardsStatus.NORMATIVE) { 278 cs.setExperimental(false); 279 cs.setStatus(PublicationStatus.ACTIVE); 280 } 281 } 282 if (fmm != null) { 283 String sfmm = ToolingExtensions.readStringExtension(cs, ToolingExtensions.EXT_FMM_LEVEL); 284 if (Utilities.noString(sfmm) || Integer.parseInt(sfmm) < Integer.parseInt(fmm)) 285 ToolingExtensions.setIntegerExtension(cs, ToolingExtensions.EXT_FMM_LEVEL, Integer.parseInt(fmm)); 286 } 287 } 288 289 public static Type readProperty(ConceptDefinitionComponent concept, String code) { 290 for (ConceptPropertyComponent p : concept.getProperty()) 291 if (p.getCode().equals(code)) 292 return p.getValue(); 293 return null; 294 } 295 296 public static ConceptPropertyComponent getProperty(ConceptDefinitionComponent concept, String code) { 297 for (ConceptPropertyComponent p : concept.getProperty()) 298 if (p.getCode().equals(code)) 299 return p; 300 return null; 301 } 302 303 // see http://hl7.org/fhir/R4/codesystem.html#hierachy 304 // returns additional parents not in the hierarchy 305 public static List<String> getOtherChildren(CodeSystem cs, ConceptDefinitionComponent c) { 306 List<String> res = new ArrayList<String>(); 307 for (ConceptPropertyComponent p : c.getProperty()) { 308 if ("parent".equals(p.getCode())) { 309 res.add(p.getValue().primitiveValue()); 310 } 311 } 312 return res; 313 } 314 315 // see http://hl7.org/fhir/R4/codesystem.html#hierachy 316 public static void addOtherChild(CodeSystem cs, ConceptDefinitionComponent owner, String code) { 317 defineChildProperty(cs); 318 owner.addProperty().setCode("child").setValue(new CodeType(code)); 319 } 320 321 public static class ConceptDefinitionComponentSorter implements Comparator<ConceptDefinitionComponent> { 322 323 @Override 324 public int compare(ConceptDefinitionComponent o1, ConceptDefinitionComponent o2) { 325 return o1.getCode().compareToIgnoreCase(o2.getCode()); 326 } 327 328 } 329 330 public static void sortAllCodes(CodeSystem cs) { 331 sortAllCodes(cs.getConcept()); 332 } 333 334 public static void sortAllCodes(List<ConceptDefinitionComponent> list) { 335 Collections.sort(list, new ConceptDefinitionComponentSorter()); 336 for (ConceptDefinitionComponent cd : list) { 337 if (cd.hasConcept()) { 338 sortAllCodes(cd.getConcept()); 339 } 340 } 341 } 342 343}