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}