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