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