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 032 033 034import java.util.ArrayList; 035import java.util.Calendar; 036import java.util.List; 037 038import org.hl7.fhir.exceptions.FHIRException; 039import org.hl7.fhir.exceptions.FHIRFormatError; 040import org.hl7.fhir.r4.model.BooleanType; 041import org.hl7.fhir.r4.model.CanonicalType; 042import org.hl7.fhir.r4.model.CodeSystem; 043import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; 044import org.hl7.fhir.r4.model.CodeSystem.ConceptPropertyComponent; 045import org.hl7.fhir.r4.model.CodeSystem.PropertyComponent; 046import org.hl7.fhir.r4.model.CodeSystem.PropertyType; 047import org.hl7.fhir.r4.model.CodeType; 048import org.hl7.fhir.r4.model.DateTimeType; 049import org.hl7.fhir.r4.model.Enumerations.PublicationStatus; 050import org.hl7.fhir.r4.model.Identifier; 051import org.hl7.fhir.r4.model.Meta; 052import org.hl7.fhir.r4.model.Type; 053import org.hl7.fhir.r4.model.UriType; 054import org.hl7.fhir.r4.utils.ToolingExtensions; 055import org.hl7.fhir.utilities.StandardsStatus; 056import org.hl7.fhir.utilities.Utilities; 057 058public class CodeSystemUtilities { 059 060 public static boolean isNotSelectable(CodeSystem cs, ConceptDefinitionComponent def) { 061 for (ConceptPropertyComponent p : def.getProperty()) { 062 if (p.getCode().equals("notSelectable") && p.hasValue() && p.getValue() instanceof BooleanType) 063 return ((BooleanType) p.getValue()).getValue(); 064 } 065 return false; 066 } 067 068 public static void setNotSelectable(CodeSystem cs, ConceptDefinitionComponent concept) throws FHIRFormatError { 069 defineNotSelectableProperty(cs); 070 ConceptPropertyComponent p = getProperty(concept, "notSelectable"); 071 if (p != null) 072 p.setValue(new BooleanType(true)); 073 else 074 concept.addProperty().setCode("notSelectable").setValue(new BooleanType(true)); 075 } 076 077 public static void defineNotSelectableProperty(CodeSystem cs) { 078 defineCodeSystemProperty(cs, "notSelectable", "Indicates that the code is abstract - only intended to be used as a selector for other concepts", PropertyType.BOOLEAN); 079 } 080 081 082 public enum ConceptStatus { 083 Active, Experimental, Deprecated, Retired; 084 085 public String toCode() { 086 switch (this) { 087 case Active: return "active"; 088 case Experimental: return "experimental"; 089 case Deprecated: return "deprecated"; 090 case Retired: return "retired"; 091 default: return null; 092 } 093 } 094 } 095 096 public static void setStatus(CodeSystem cs, ConceptDefinitionComponent concept, ConceptStatus status) throws FHIRFormatError { 097 defineStatusProperty(cs); 098 ConceptPropertyComponent p = getProperty(concept, "status"); 099 if (p != null) 100 p.setValue(new CodeType(status.toCode())); 101 else 102 concept.addProperty().setCode("status").setValue(new CodeType(status.toCode())); 103 } 104 105 public static void defineStatusProperty(CodeSystem cs) { 106 defineCodeSystemProperty(cs, "status", "A property that indicates the status of the concept. One of active, experimental, deprecated, retired", PropertyType.CODE); 107 } 108 109 private static void defineDeprecatedProperty(CodeSystem cs) { 110 defineCodeSystemProperty(cs, "deprecationDate", "The date at which a concept was deprecated. Concepts that are deprecated but not inactive can still be used, but their use is discouraged", PropertyType.DATETIME); 111 } 112 113 public static void defineParentProperty(CodeSystem cs) { 114 defineCodeSystemProperty(cs, "parent", "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", PropertyType.CODE); 115 } 116 117 public static void defineChildProperty(CodeSystem cs) { 118 defineCodeSystemProperty(cs, "child", "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", PropertyType.CODE); 119 } 120 121 public static boolean isDeprecated(CodeSystem cs, ConceptDefinitionComponent def) { 122 try { 123 for (ConceptPropertyComponent p : def.getProperty()) { 124 if (p.getCode().equals("status") && p.hasValue() && p.hasValueCodeType() && p.getValueCodeType().getCode().equals("deprecated")) 125 return true; 126 // this, though status should also be set 127 if (p.getCode().equals("deprecationDate") && p.hasValue() && p.getValue() instanceof DateTimeType) 128 return ((DateTimeType) p.getValue()).before(new DateTimeType(Calendar.getInstance())); 129 // legacy 130 if (p.getCode().equals("deprecated") && p.hasValue() && p.getValue() instanceof BooleanType) 131 return ((BooleanType) p.getValue()).getValue(); 132 } 133 return false; 134 } catch (FHIRException e) { 135 return false; 136 } 137 } 138 139 public static void setDeprecated(CodeSystem cs, ConceptDefinitionComponent concept, DateTimeType date) throws FHIRFormatError { 140 setStatus(cs, concept, ConceptStatus.Deprecated); 141 defineDeprecatedProperty(cs); 142 concept.addProperty().setCode("deprecationDate").setValue(date); 143 } 144 145 public static boolean isInactive(CodeSystem cs, ConceptDefinitionComponent def) throws FHIRException { 146 for (ConceptPropertyComponent p : def.getProperty()) { 147 if (p.getCode().equals("status") && p.hasValueStringType()) 148 return "inactive".equals(p.getValueStringType()); 149 } 150 return false; 151 } 152 153 public static boolean isInactive(CodeSystem cs, String code) throws FHIRException { 154 ConceptDefinitionComponent def = findCode(cs.getConcept(), code); 155 if (def == null) 156 return true; 157 return isInactive(cs, def); 158 } 159 160 public static PropertyComponent defineCodeSystemProperty(CodeSystem cs, String code, String description, PropertyType type) { 161 for (PropertyComponent p : cs.getProperty()) { 162 if (p.getCode().equals(code)) 163 return p; 164 } 165 PropertyComponent p = cs.addProperty(); 166 p.setCode(code).setDescription(description).setType(type).setUri("http://hl7.org/fhir/concept-properties#"+code); 167 return p; 168 } 169 170 public static String getCodeDefinition(CodeSystem cs, String code) { 171 return getCodeDefinition(cs.getConcept(), code); 172 } 173 174 private static String getCodeDefinition(List<ConceptDefinitionComponent> list, String code) { 175 for (ConceptDefinitionComponent c : list) { 176 if (c.hasCode() && c.getCode().equals(code)) 177 return c.getDefinition(); 178 String s = getCodeDefinition(c.getConcept(), code); 179 if (s != null) 180 return s; 181 } 182 return null; 183 } 184 185 public static CodeSystem makeShareable(CodeSystem cs) { 186 if (!cs.hasMeta()) 187 cs.setMeta(new Meta()); 188 for (UriType t : cs.getMeta().getProfile()) 189 if (t.getValue().equals("http://hl7.org/fhir/StructureDefinition/shareablecodesystem")) 190 return cs; 191 cs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablecodesystem")); 192 return cs; 193 } 194 195 public static void setOID(CodeSystem cs, String oid) { 196 if (!oid.startsWith("urn:oid:")) 197 oid = "urn:oid:" + oid; 198 if (!cs.hasIdentifier()) 199 cs.addIdentifier(new Identifier().setSystem("urn:ietf:rfc:3986").setValue(oid)); 200 else if ("urn:ietf:rfc:3986".equals(cs.getIdentifierFirstRep().getSystem()) && cs.getIdentifierFirstRep().hasValue() && cs.getIdentifierFirstRep().getValue().startsWith("urn:oid:")) 201 cs.getIdentifierFirstRep().setValue(oid); 202 else 203 throw new Error("unable to set OID on code system"); 204 205 } 206 207 public static boolean hasOID(CodeSystem cs) { 208 return getOID(cs) != null; 209 } 210 211 public static String getOID(CodeSystem cs) { 212 if (cs.hasIdentifier() && "urn:ietf:rfc:3986".equals(cs.getIdentifierFirstRep().getSystem()) && cs.getIdentifierFirstRep().hasValue() && cs.getIdentifierFirstRep().getValue().startsWith("urn:oid:")) 213 return cs.getIdentifierFirstRep().getValue().substring(8); 214 return null; 215 } 216 217 private static ConceptDefinitionComponent findCode(List<ConceptDefinitionComponent> list, String code) { 218 for (ConceptDefinitionComponent c : list) { 219 if (c.getCode().equals(code)) 220 return c; 221 ConceptDefinitionComponent s = findCode(c.getConcept(), code); 222 if (s != null) 223 return s; 224 } 225 return null; 226 } 227 228 public static void markStatus(CodeSystem cs, String wg, StandardsStatus status, String pckage, String fmm, String normativeVersion) throws FHIRException { 229 if (wg != null) { 230 if (!ToolingExtensions.hasExtension(cs, ToolingExtensions.EXT_WORKGROUP) || 231 (Utilities.existsInList(ToolingExtensions.readStringExtension(cs, ToolingExtensions.EXT_WORKGROUP), "fhir", "vocab") && !Utilities.existsInList(wg, "fhir", "vocab"))) { 232 ToolingExtensions.setCodeExtension(cs, ToolingExtensions.EXT_WORKGROUP, wg); 233 } 234 } 235 if (status != null) { 236 StandardsStatus ss = ToolingExtensions.getStandardsStatus(cs); 237 if (ss == null || ss.isLowerThan(status)) 238 ToolingExtensions.setStandardsStatus(cs, status, normativeVersion); 239 if (pckage != null) { 240 if (!cs.hasUserData("ballot.package")) 241 cs.setUserData("ballot.package", pckage); 242 else if (!pckage.equals(cs.getUserString("ballot.package"))) 243 if (!"infrastructure".equals(cs.getUserString("ballot.package"))) 244 System.out.println("Code System "+cs.getUrl()+": ownership clash "+pckage+" vs "+cs.getUserString("ballot.package")); 245 } 246 if (status == StandardsStatus.NORMATIVE) { 247 cs.setExperimental(false); 248 cs.setStatus(PublicationStatus.ACTIVE); 249 } 250 } 251 if (fmm != null) { 252 String sfmm = ToolingExtensions.readStringExtension(cs, ToolingExtensions.EXT_FMM_LEVEL); 253 if (Utilities.noString(sfmm) || Integer.parseInt(sfmm) < Integer.parseInt(fmm)) 254 ToolingExtensions.setIntegerExtension(cs, ToolingExtensions.EXT_FMM_LEVEL, Integer.parseInt(fmm)); 255 } 256 } 257 258 259 public static Type readProperty(ConceptDefinitionComponent concept, String code) { 260 for (ConceptPropertyComponent p : concept.getProperty()) 261 if (p.getCode().equals(code)) 262 return p.getValue(); 263 return null; 264 } 265 266 public static ConceptPropertyComponent getProperty(ConceptDefinitionComponent concept, String code) { 267 for (ConceptPropertyComponent p : concept.getProperty()) 268 if (p.getCode().equals(code)) 269 return p; 270 return null; 271 } 272 273 // see http://hl7.org/fhir/R4/codesystem.html#hierachy 274 // returns additional parents not in the heirarchy 275 public static List<String> getOtherChildren(CodeSystem cs, ConceptDefinitionComponent c) { 276 List<String> res = new ArrayList<String>(); 277 for (ConceptPropertyComponent p : c.getProperty()) { 278 if ("parent".equals(p.getCode())) { 279 res.add(p.getValue().primitiveValue()); 280 } 281 } 282 return res; 283 } 284 285 // see http://hl7.org/fhir/R4/codesystem.html#hierachy 286 public static void addOtherChild(CodeSystem cs, ConceptDefinitionComponent owner, String code) { 287 defineChildProperty(cs); 288 owner.addProperty().setCode("child").setValue(new CodeType(code)); 289 } 290 291}