
001package org.hl7.fhir.convertors; 002 003import java.io.IOException; 004import java.io.InputStream; 005import java.text.SimpleDateFormat; 006import java.util.ArrayList; 007import java.util.Calendar; 008import java.util.GregorianCalendar; 009import java.util.HashSet; 010import java.util.List; 011import java.util.Locale; 012import java.util.Set; 013import java.util.TimeZone; 014 015import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_10_40; 016import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_30_40; 017import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_40; 018import org.hl7.fhir.convertors.factory.VersionConvertorFactory_14_40; 019import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_40; 020import org.hl7.fhir.convertors.loaders.loaderR4.R2016MayToR4Loader; 021import org.hl7.fhir.convertors.loaders.loaderR4.R2ToR4Loader; 022import org.hl7.fhir.convertors.loaders.loaderR4.R3ToR4Loader; 023import org.hl7.fhir.convertors.misc.IGR2ConvertorAdvisor; 024import org.hl7.fhir.exceptions.FHIRException; 025import org.hl7.fhir.r4.conformance.ProfileUtilities; 026import org.hl7.fhir.r4.context.BaseWorkerContext; 027import org.hl7.fhir.r4.context.SimpleWorkerContext; 028import org.hl7.fhir.r4.formats.JsonParser; 029import org.hl7.fhir.r4.model.ElementDefinition; 030import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; 031import org.hl7.fhir.r4.model.Enumerations.FHIRVersion; 032import org.hl7.fhir.r4.model.Enumerations.PublicationStatus; 033import org.hl7.fhir.r4.model.ImplementationGuide.SPDXLicense; 034import org.hl7.fhir.r4.model.Resource; 035import org.hl7.fhir.r4.model.StructureDefinition; 036import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind; 037import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule; 038import org.hl7.fhir.r4.model.UriType; 039import org.hl7.fhir.r4.utils.NPMPackageGenerator; 040import org.hl7.fhir.r4.utils.NPMPackageGenerator.Category; 041import org.hl7.fhir.utilities.Utilities; 042import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager; 043import org.hl7.fhir.utilities.npm.NpmPackage; 044import org.hl7.fhir.utilities.npm.PackageGenerator.PackageType; 045import org.hl7.fhir.utilities.npm.ToolsVersion; 046 047/* 048 Copyright (c) 2011+, HL7, Inc. 049 All rights reserved. 050 051 Redistribution and use in source and binary forms, with or without modification, 052 are permitted provided that the following conditions are met: 053 054 * Redistributions of source code must retain the above copyright notice, this 055 list of conditions and the following disclaimer. 056 * Redistributions in binary form must reproduce the above copyright notice, 057 this list of conditions and the following disclaimer in the documentation 058 and/or other materials provided with the distribution. 059 * Neither the name of HL7 nor the names of its contributors may be used to 060 endorse or promote products derived from this software without specific 061 prior written permission. 062 063 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 064 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 065 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 066 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 067 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 068 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 069 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 070 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 071 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 072 POSSIBILITY OF SUCH DAMAGE. 073 074 */ 075 076 077import com.google.gson.JsonArray; 078import com.google.gson.JsonObject; 079 080@SuppressWarnings("checkstyle:systemout") 081public class ExtensionDefinitionGenerator { 082 083 private FHIRVersion sourceVersion; 084 private FHIRVersion targetVersion; 085 private String filename; 086 private StructureDefinition extbase; 087 private ElementDefinition extv; 088 private ProfileUtilities pu; 089 private BaseWorkerContext context; 090 091 public static void main(String[] args) throws IOException, FHIRException { 092 if (args.length == 0) { 093 System.out.println("Extension Generator"); 094 System.out.println("==================="); 095 System.out.println(); 096 System.out.println("See http://hl7.org/fhir/versions.html#extensions. This generates the packages"); 097 System.out.println(); 098 System.out.println("parameters: -srcver [version] -tgtver [version] -package [filename]"); 099 System.out.println(); 100 System.out.println("srcver: the source version to load"); 101 System.out.println("tgtver: the version to generate extension definitions for"); 102 System.out.println("package: the package to produce"); 103 } else { 104 ExtensionDefinitionGenerator self = new ExtensionDefinitionGenerator(); 105 self.setSourceVersion(FHIRVersion.fromCode(getNamedParam(args, "-srcver"))); 106 self.setTargetVersion(FHIRVersion.fromCode(getNamedParam(args, "-tgtver"))); 107 self.setFilename(getNamedParam(args, "-package")); 108 self.generate(); 109 } 110 } 111 112 private static String getNamedParam(String[] args, String param) { 113 boolean found = false; 114 for (String a : args) { 115 if (found) 116 return a; 117 if (a.equals(param)) { 118 found = true; 119 } 120 } 121 throw new Error("Unable to find parameter " + param); 122 } 123 124 public FHIRVersion getSourceVersion() { 125 return sourceVersion; 126 } 127 128 public void setSourceVersion(FHIRVersion sourceVersion) { 129 this.sourceVersion = sourceVersion; 130 } 131 132 public FHIRVersion getTargetVersion() { 133 return targetVersion; 134 } 135 136 public void setTargetVersion(FHIRVersion targetVersion) { 137 this.targetVersion = targetVersion; 138 } 139 140 public String getFilename() { 141 return filename; 142 } 143 144 public void setFilename(String filename) { 145 this.filename = filename; 146 } 147 148 149 private void generate() throws IOException, FHIRException { 150 List<StructureDefinition> definitions = loadSource(); 151 List<StructureDefinition> extensions = buildExtensions(definitions); 152 for (StructureDefinition ext : extensions) 153 pu.generateSnapshot(extbase, ext, ext.getUrl(), "http://hl7.org/fhir/R4", ext.getName()); 154 savePackage(extensions); 155 156 } 157 158 private List<StructureDefinition> buildExtensions(List<StructureDefinition> definitions) throws FHIRException { 159 Set<String> types = new HashSet<>(); 160 List<StructureDefinition> list = new ArrayList<>(); 161 for (StructureDefinition type : definitions) 162 if (type.getDerivation() == TypeDerivationRule.SPECIALIZATION && !type.getName().contains(".") && !types.contains(type.getName()) && type.getKind() != StructureDefinitionKind.PRIMITIVETYPE && !Utilities.existsInList(type.getName(), "Extension", "Narrative")) { 163 types.add(type.getName()); 164 buildExtensions(type, list); 165 } 166 return list; 167 } 168 169 170 private void buildExtensions(StructureDefinition type, List<StructureDefinition> list) throws FHIRException { 171 for (ElementDefinition ed : type.getDifferential().getElement()) { 172 if (ed.getPath().contains(".")) { 173 if (!ed.getPath().endsWith(".extension") && !ed.getPath().endsWith(".modifierExtension")) { 174 StructureDefinition ext = generateExtension(type, ed); 175 if (ext != null) { 176 list.add(ext); 177 context.cacheResource(ext); 178 } 179 } 180 } 181 } 182 } 183 184 private StructureDefinition generateExtension(StructureDefinition type, ElementDefinition ed) throws FHIRException { 185 StructureDefinition ext = new StructureDefinition(); 186 ext.setId("extension-" + ed.getPath().replace("[x]", "")); 187 ext.setUrl("http://hl7.org/fhir/" + sourceVersion.toCode().substring(0, 3) + "/StructureDefinition/" + ext.getId()); 188 if (ext.getId().length() > 64) 189 ext.setId(contract(ext.getId())); 190 ext.setVersion(sourceVersion.toCode()); 191 ext.setName("ExtensionR" + sourceVersion.toCode().substring(0, 1) + ed.getPath().replace(".", "")); 192 ext.setTitle("Extension definition for R" + sourceVersion.toCode().substring(0, 1) + " element " + ed.getPath()); 193 ext.setStatus(PublicationStatus.ACTIVE); 194 ext.setDate(type.getDate()); 195 ext.setFhirVersion(type.getFhirVersion()); 196 ext.setDescription(ed.getDefinition()); 197 ext.setKind(StructureDefinitionKind.COMPLEXTYPE); 198 ext.setBaseDefinition("http://hl7.org/fhir/StructureDefinition/Extension"); 199 ext.setDerivation(TypeDerivationRule.CONSTRAINT); 200 if (ed.hasType() && ("Element".equals(ed.getType().get(0).getCode()) || "BackboneElement".equals(ed.getType().get(0).getCode()))) { 201 ElementDefinition v = ed.copy(); 202 v.setPath("Extension"); 203 v.getType().clear(); 204 v.setIsSummaryElement(null); 205 ext.getDifferential().addElement(v); 206 List<ElementDefinition> children = ProfileUtilities.getChildList(type, ed); 207 for (ElementDefinition child : children) { 208 String n = tail(child.getPath()); 209 if (!Utilities.existsInList(n, "id", "extension", "modifierExtension") && !hasNonValidType(child)) { 210 v = child.copy(); 211 v.setId("Extension.extension:" + n); 212 v.setPath("Extension.extension"); 213 v.setSliceName(n); 214 v.getType().clear(); 215 v.setIsSummaryElement(null); 216 v.addType().setCode("Extension").addProfile("http://hl7.org/fhir/" + sourceVersion.toCode().substring(0, 3) + "/StructureDefinition/extension-" + child.getPath().replace("[x]", "")); 217 ext.getDifferential().addElement(v); 218 } 219 } 220 ext.getDifferential().addElement(genElement("Extension.url").setFixed(new UriType(ext.getUrl()))); 221 ext.getDifferential().addElement(genElement("Extension.value[x]").setMax("0")); 222 223 } else if (ed.hasType() && Utilities.existsInList(ed.getType().get(0).getCode(), "Resource", "Narrative")) { 224 return null; 225 } else if (ed.hasType() && !goesInExtension(ed.getType().get(0).getCode())) { 226 ElementDefinition v = ed.copy(); 227 v.setPath("Extension"); 228 v.getType().clear(); 229 v.setIsSummaryElement(null); 230 ext.getDifferential().addElement(v); 231 List<ElementDefinition> children = ProfileUtilities.getChildList(type, ed); 232 for (ElementDefinition child : children) { 233 String n = tail(child.getPath()); 234 if (!Utilities.existsInList(n, "id", "extension", "modifierExtension") && !hasNonValidType(child)) { 235 v = child.copy(); 236 v.setId("Extension.extension:" + n); 237 v.setPath("Extension.extension"); 238 v.setSliceName(n); 239 v.getType().clear(); 240 v.setIsSummaryElement(null); 241 v.addType().setCode("Extension").addProfile("http://hl7.org/fhir/" + sourceVersion.toCode().substring(0, 3) + "/StructureDefinition/extension-" + child.getPath().replace("[x]", "")); 242 ext.getDifferential().addElement(v); 243 } 244 } 245 ext.getDifferential().addElement(genElement("Extension.url").setFixed(new UriType(ext.getUrl()))); 246 ext.getDifferential().addElement(genElement("Extension.value[x]").setMax("0")); 247 } else { 248 // simple type... 249 ElementDefinition v = ed.copy(); 250 v.setPath("Extension"); 251 v.getType().clear(); 252 v.setIsSummaryElement(null); 253 ext.getDifferential().addElement(v); 254 ext.getDifferential().addElement(genElement("Extension.extension").setMax("0")); 255 ext.getDifferential().addElement(genElement("Extension.url").setFixed(new UriType(ext.getUrl()))); 256 v = ed.copy(); 257 v.setPath("Extension.value[x]"); 258 v.setId("Extension.value"); 259 v.setMax("1"); 260 v.setIsSummaryElement(null); 261 ext.getDifferential().addElement(v); 262 } 263 return ext; 264 } 265 266 private boolean hasNonValidType(ElementDefinition ed) { 267 return ed.hasType() && Utilities.existsInList(ed.getType().get(0).getCode(), "Resource", "Narrative"); 268 } 269 270 271 private boolean goesInExtension(String code) { 272 if (code == null) 273 return true; 274 for (TypeRefComponent tr : extv.getType()) { 275 if (code.equals(tr.getCode())) 276 return true; 277 } 278 return false; 279 } 280 281 282 private String tail(String path) { 283 return path.substring(path.lastIndexOf(".") + 1); 284 } 285 286 287 private ElementDefinition genElement(String path) { 288 return new ElementDefinition().setPath(path); 289 } 290 291 292 private String contract(String id) { 293 List<StringReplacement> abbrevs = new ArrayList<>(); 294 abbrevs.add(new StringReplacement("AdverseEvent", "AE")); 295 abbrevs.add(new StringReplacement("CoverageEligibilityResponse", "CERsp")); 296 abbrevs.add(new StringReplacement("CoverageEligibilityRequest", "CEReq")); 297 abbrevs.add(new StringReplacement("EffectEvidenceSynthesis", "EES")); 298 abbrevs.add(new StringReplacement("ExplanationOfBenefit", "EoB")); 299 abbrevs.add(new StringReplacement("ImmunizationRecommendation", "IR")); 300 abbrevs.add(new StringReplacement("MeasureReport", "MR")); 301 abbrevs.add(new StringReplacement("MedicationKnowledge", "MK")); 302 abbrevs.add(new StringReplacement("CapabilityStatement", "CS")); 303 abbrevs.add(new StringReplacement("ChargeItemDefinition", "CID")); 304 abbrevs.add(new StringReplacement("ClaimResponse", "CR")); 305 abbrevs.add(new StringReplacement("InsurancePlan", "IP")); 306 abbrevs.add(new StringReplacement("MedicationRequest", "MR")); 307 abbrevs.add(new StringReplacement("MedicationOrder", "MO")); 308 abbrevs.add(new StringReplacement("MedicationDispense", "MD")); 309 abbrevs.add(new StringReplacement("NutritionOrder", "NO")); 310 abbrevs.add(new StringReplacement("MedicinalProductAuthorization", "MPA")); 311 abbrevs.add(new StringReplacement("MedicinalProductContraindication", "MPC")); 312 abbrevs.add(new StringReplacement("MedicinalProductIngredient", "MPI")); 313 abbrevs.add(new StringReplacement("MedicinalProductPharmaceutical", "MPP")); 314 abbrevs.add(new StringReplacement("MedicinalProduct", "MP")); 315 abbrevs.add(new StringReplacement("ResearchElementDefinition", "RED")); 316 abbrevs.add(new StringReplacement("RiskEvidenceSynthesis", "RES")); 317 abbrevs.add(new StringReplacement("ObservationDefinition", "OD")); 318 abbrevs.add(new StringReplacement("SubstanceReferenceInformation", "SRI")); 319 abbrevs.add(new StringReplacement("SubstanceSourceMaterial", "SSM")); 320 abbrevs.add(new StringReplacement("SpecimenDefinition", "SD")); 321 abbrevs.add(new StringReplacement("SubstanceSpecification", "SS")); 322 abbrevs.add(new StringReplacement("SubstancePolymer", "SP")); 323 abbrevs.add(new StringReplacement("TerminologyCapabilities", "TC")); 324 abbrevs.add(new StringReplacement("VerificationResult", "VR")); 325 abbrevs.add(new StringReplacement("EligibilityResponse", "ERsp")); 326 abbrevs.add(new StringReplacement("ExpansionProfile", "EP")); 327 abbrevs.add(new StringReplacement("ImagingObjectSelection", "IOS")); 328 329 330 abbrevs.add(new StringReplacement("administrationGuidelines.patientCharacteristics", "ag.pc")); 331 abbrevs.add(new StringReplacement("manufacturingBusinessOperation", "mbo")); 332 abbrevs.add(new StringReplacement("strength.referenceStrength", "strength.rs")); 333 abbrevs.add(new StringReplacement("MPP.routeOfAdministration", "MPP.roa")); 334 abbrevs.add(new StringReplacement("supportingInformation", "si")); 335 abbrevs.add(new StringReplacement("structuralRepresentation", "sr")); 336 abbrevs.add(new StringReplacement("compareToSourceExpression", "ctse")); 337 abbrevs.add(new StringReplacement("TestScript.setup.action.assert", "TestScript.s.a.a")); 338 339 for (StringReplacement s : abbrevs) 340 if (id.contains(s.getSource())) 341 id = id.replace(s.getSource(), s.getReplacement()); 342 if (id.length() > 64) 343 throw new Error("Still too long: " + id); 344 return id; 345 } 346 347 348 private String timezone() { 349 TimeZone tz = TimeZone.getDefault(); 350 Calendar cal = GregorianCalendar.getInstance(tz); 351 int offsetInMillis = tz.getOffset(cal.getTimeInMillis()); 352 353 String offset = String.format("%02d:%02d", Math.abs(offsetInMillis / 3600000), Math.abs((offsetInMillis / 60000) % 60)); 354 offset = (offsetInMillis >= 0 ? "+" : "-") + offset; 355 356 return offset; 357 } 358 359 private void savePackage(List<StructureDefinition> extensions) throws FHIRException, IOException { 360 JsonObject npm = new JsonObject(); 361 npm.addProperty("name", "hl7.fhir.extensions.r" + sourceVersion.toCode().substring(0, 1)); 362 npm.addProperty("version", targetVersion.toCode().substring(0, 3)); 363 npm.addProperty("tools-version", ToolsVersion.TOOLS_VERSION); 364 npm.addProperty("type", PackageType.IG.getCode()); 365 npm.addProperty("license", SPDXLicense.CC0_1_0.toCode()); 366 npm.addProperty("canonical", "http://hl7.org/fhir/" + sourceVersion.toCode().substring(0, 3) + "/extensions/" + targetVersion.toCode().substring(0, 3)); 367 npm.addProperty("url", "http://hl7.org/fhir/" + sourceVersion.toCode().substring(0, 3) + "/extensions/" + targetVersion.toCode().substring(0, 3)); 368 npm.addProperty("title", "Extension Definitions for representing elements from " + sourceVersion.toCode() + " in " + targetVersion.toCode()); 369 npm.addProperty("description", "Extension Definitions for representing elements from " + sourceVersion.toCode() + " in " + targetVersion.toCode() + " built " + new SimpleDateFormat("EEE, MMM d, yyyy HH:mmZ", new Locale("en", "US")).format(Calendar.getInstance().getTime()) + timezone() + ")"); 370 JsonObject dep = new JsonObject(); 371 npm.add("dependencies", dep); 372 dep.addProperty("hl7.fhir.core", targetVersion.toCode()); 373 npm.addProperty("author", "FHIR Project"); 374 JsonArray m = new JsonArray(); 375 JsonObject md = new JsonObject(); 376 m.add(md); 377 md.addProperty("name", "FHIR Project"); 378 md.addProperty("url", "http://hl7.org/fhir"); 379 NPMPackageGenerator pi = new NPMPackageGenerator(filename, npm); 380 for (StructureDefinition sd : extensions) { 381 byte[] cnt = saveResource(sd, targetVersion); 382 pi.addFile(Category.RESOURCE, "StructureDefinition-" + sd.getId() + ".json", cnt); 383 } 384 pi.finish(); 385 386 } 387 388 389 private List<StructureDefinition> loadSource() throws IOException, FHIRException { 390 List<StructureDefinition> list = new ArrayList<>(); 391 FilesystemPackageCacheManager pcm = new FilesystemPackageCacheManager.Builder().build(); 392 NpmPackage npm = pcm.loadPackage("hl7.fhir.core", sourceVersion.toCode()); 393 if (sourceVersion == FHIRVersion._4_0_0) 394 context = SimpleWorkerContext.fromPackage(npm); 395 else if (sourceVersion == FHIRVersion._3_0_1) 396 context = SimpleWorkerContext.fromPackage(npm, new R3ToR4Loader()); 397 else if (sourceVersion == FHIRVersion._1_4_0) 398 context = SimpleWorkerContext.fromPackage(npm, new R2016MayToR4Loader()); 399 else if (sourceVersion == FHIRVersion._1_0_2) 400 context = SimpleWorkerContext.fromPackage(npm, new R2ToR4Loader()); 401 pu = new ProfileUtilities(context, null, null); 402 for (String fn : npm.listResources("StructureDefinition")) { 403 list.add((StructureDefinition) loadResource(npm.load("package", fn), sourceVersion)); 404 } 405 for (StructureDefinition sd : list) 406 if (sd.getName().equals("Extension")) { 407 extbase = sd; 408 extv = extbase.getSnapshot().getElement().get(extbase.getSnapshot().getElement().size() - 1); 409 } 410 return list; 411 } 412 413 private byte[] saveResource(Resource resource, FHIRVersion v) throws IOException, FHIRException { 414 if (v == FHIRVersion._3_0_1) { 415 org.hl7.fhir.dstu3.model.Resource res = VersionConvertorFactory_30_40.convertResource(resource, new BaseAdvisor_30_40(false)); 416 return new org.hl7.fhir.dstu3.formats.JsonParser().composeBytes(res); 417 } else if (v == FHIRVersion._1_4_0) { 418 org.hl7.fhir.dstu2016may.model.Resource res = VersionConvertorFactory_14_40.convertResource(resource); 419 return new org.hl7.fhir.dstu2016may.formats.JsonParser().composeBytes(res); 420 } else if (v == FHIRVersion._1_0_2) { 421 BaseAdvisor_10_40 advisor = new IGR2ConvertorAdvisor(); 422 org.hl7.fhir.dstu2.model.Resource res = VersionConvertorFactory_10_40.convertResource(resource, advisor); 423 return new org.hl7.fhir.dstu2.formats.JsonParser().composeBytes(res); 424 } else if (v == FHIRVersion._4_0_0) { 425 return new JsonParser().composeBytes(resource); 426 } else 427 throw new Error("Unsupported version " + v); 428 } 429 430 private Resource loadResource(InputStream inputStream, FHIRVersion v) throws IOException, FHIRException { 431 if (v == FHIRVersion._3_0_1) { 432 org.hl7.fhir.dstu3.model.Resource res = new org.hl7.fhir.dstu3.formats.JsonParser().parse(inputStream); 433 return VersionConvertorFactory_30_40.convertResource(res, new BaseAdvisor_30_40(false)); 434 } else if (v == FHIRVersion._1_4_0) { 435 org.hl7.fhir.dstu2016may.model.Resource res = new org.hl7.fhir.dstu2016may.formats.JsonParser().parse(inputStream); 436 return VersionConvertorFactory_14_40.convertResource(res); 437 } else if (v == FHIRVersion._1_0_2) { 438 org.hl7.fhir.dstu2.model.Resource res = new org.hl7.fhir.dstu2.formats.JsonParser().parse(inputStream); 439 BaseAdvisor_10_40 advisor = new IGR2ConvertorAdvisor(); 440 return VersionConvertorFactory_10_40.convertResource(res, advisor); 441 } else if (v == FHIRVersion._4_0_0) { 442 return new JsonParser().parse(inputStream); 443 } else 444 throw new Error("Unsupported version " + v); 445 } 446}