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