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(org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager.FilesystemPackageCacheMode.USER);
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}