001package org.hl7.fhir.convertors.misc.utg;
002
003import java.io.File;
004import java.io.FileInputStream;
005import java.io.FileOutputStream;
006import java.io.IOException;
007import java.net.URISyntaxException;
008import java.text.ParseException;
009import java.util.ArrayList;
010import java.util.Date;
011import java.util.HashMap;
012import java.util.List;
013import java.util.Map;
014
015import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50;
016import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_50;
017import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50;
018import org.hl7.fhir.exceptions.FHIRException;
019import org.hl7.fhir.r5.formats.IParser.OutputStyle;
020import org.hl7.fhir.r5.formats.JsonParser;
021import org.hl7.fhir.r5.formats.XmlParser;
022import org.hl7.fhir.r5.model.Base;
023import org.hl7.fhir.r5.model.Bundle;
024import org.hl7.fhir.r5.model.CanonicalResource;
025import org.hl7.fhir.r5.model.CodeSystem;
026import org.hl7.fhir.r5.model.Provenance;
027import org.hl7.fhir.r5.model.Provenance.ProvenanceAgentComponent;
028import org.hl7.fhir.r5.model.Resource;
029import org.hl7.fhir.r5.model.ValueSet;
030import org.hl7.fhir.r5.utils.ToolingExtensions;
031import org.hl7.fhir.utilities.Utilities;
032import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
033import org.hl7.fhir.utilities.npm.NpmPackage;
034import org.hl7.fhir.utilities.npm.NpmPackage.PackageResourceInformation;
035
036import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
037
038public class UTGVersionSorter {
039
040  private final Date runTime = new Date();
041  private FilesystemPackageCacheManager pcm;
042
043  public static void main(String[] args) throws FHIRException, IOException, ParseException, URISyntaxException {
044    new UTGVersionSorter().execute("C:\\work\\org.hl7.fhir.igs\\UTG\\input\\sourceOfTruth");
045  }
046
047  private void execute(String source) throws IOException {
048    List<CanonicalResourceAnalysis> list = new ArrayList<>();
049    System.out.println("Loading UTG");
050    loadFromSource(list, new File(source));
051
052    Map<String, CanonicalResource> r2 = loadPackageR2("hl7.fhir.r2.core");
053    Map<String, CanonicalResource> r3 = loadPackageR3("hl7.fhir.r3.core");
054    Map<String, CanonicalResource> r4 = loadPackageR4("hl7.fhir.r4.core");
055
056    System.out.println("Processing");
057    for (CanonicalResourceAnalysis cr : list) {
058      cr.analyse(r2, r3, r4);
059    }
060
061    Bundle b = (Bundle) new JsonParser().parse(new FileInputStream("C:\\work\\org.hl7.fhir.igs\\UTG\\input\\sourceOfTruth\\history\\utgrel1hx-1-0-6.json"));
062
063    System.out.println("Summary");
064    for (CanonicalResourceAnalysis cr : list) {
065      System.out.println(cr.summary());
066      Provenance p = new Provenance();
067      b.addEntry().setResource(p).setFullUrl("http://terminology.hl7.org/fhir/Provenance/fhir-1.0.51-" + cr.getId());
068      p.setId("hx-fhir-1.0.51-" + cr.getId());
069      p.addTarget().setReference(cr.fhirType() + "/" + cr.getId());
070      p.getOccurredPeriod().setEnd(runTime, TemporalPrecisionEnum.DAY);
071      p.setRecorded(runTime);
072      p.addAuthorization().getConcept().setText("Reset Version after migration to UTG").addCoding("http://terminology.hl7.org/CodeSystem/v3-ActReason", "METAMGT", null);
073      p.getActivity().addCoding("http://terminology.hl7.org/CodeSystem/v3-DataOperation", "UPDATE", null);
074      ProvenanceAgentComponent pa = p.addAgent();
075      pa.getType().addCoding("http://terminology.hl7.org/CodeSystem/provenance-participant-type", "author", null);
076      pa.getWho().setDisplay("Grahame Grieve");
077      pa = p.addAgent();
078      pa.getType().addCoding("http://terminology.hl7.org/CodeSystem/provenance-participant-type", "custodian", null);
079      pa.getWho().setDisplay("Vocabulary WG");
080      CanonicalResource res = cr.resource;
081      res.setVersion(cr.recommendation);
082      new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(cr.filename), res);
083    }
084    System.out.println();
085    new JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream("C:\\work\\org.hl7.fhir.igs\\UTG\\input\\sourceOfTruth\\history\\utgrel1hx-1-0-6.json"), b);
086    System.out.println("Done");
087  }
088
089  private Map<String, CanonicalResource> loadPackageR2(String id) throws IOException {
090    Map<String, CanonicalResource> res = new HashMap<>();
091    if (pcm == null) {
092      pcm = new FilesystemPackageCacheManager(org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager.FilesystemPackageCacheMode.USER);
093    }
094    System.out.println("Load " + id);
095    NpmPackage npm = pcm.loadPackage(id);
096    for (PackageResourceInformation p : npm.listIndexedResources("CodeSystem", "ValueSet")) {
097      CanonicalResource r = (CanonicalResource) VersionConvertorFactory_10_50.convertResource(new org.hl7.fhir.dstu2.formats.JsonParser().parse(npm.load(p)));
098      res.put(r.getUrl(), r);
099    }
100    return res;
101  }
102
103  private Map<String, CanonicalResource> loadPackageR3(String id) throws IOException {
104    Map<String, CanonicalResource> res = new HashMap<>();
105    if (pcm == null) {
106      pcm = new FilesystemPackageCacheManager(org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager.FilesystemPackageCacheMode.USER);
107    }
108    System.out.println("Load " + id);
109    NpmPackage npm = pcm.loadPackage(id);
110    for (PackageResourceInformation p : npm.listIndexedResources("CodeSystem", "ValueSet")) {
111      CanonicalResource r = (CanonicalResource) VersionConvertorFactory_30_50.convertResource(new org.hl7.fhir.dstu3.formats.JsonParser().parse(npm.load(p)));
112      res.put(r.getUrl(), r);
113    }
114    return res;
115  }
116
117  private Map<String, CanonicalResource> loadPackageR4(String id) throws IOException {
118    Map<String, CanonicalResource> res = new HashMap<>();
119    if (pcm == null) {
120      pcm = new FilesystemPackageCacheManager(org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager.FilesystemPackageCacheMode.USER);
121    }
122    System.out.println("Load " + id);
123    NpmPackage npm = pcm.loadPackage(id);
124    for (PackageResourceInformation p : npm.listIndexedResources("CodeSystem", "ValueSet")) {
125      CanonicalResource r = (CanonicalResource) VersionConvertorFactory_40_50.convertResource(new org.hl7.fhir.r4.formats.JsonParser().parse(npm.load(p)));
126      res.put(r.getUrl(), r);
127    }
128    return res;
129  }
130
131  private void loadFromSource(List<CanonicalResourceAnalysis> list, File source) {
132    for (File f : source.listFiles()) {
133      if (f.isDirectory()) {
134        loadFromSource(list, f);
135      } else if (f.getName().endsWith(".xml")) {
136        try {
137          Resource r = new XmlParser().parse(new FileInputStream(f));
138          if (r instanceof CanonicalResource) {
139            CanonicalResource cr = (CanonicalResource) r;
140            cr.setWebPath(f.getAbsolutePath());
141            if (cr.hasVersion() && cr.getVersion().startsWith("4.") && !Utilities.existsInList(cr.getId(), "v3-DataOperation", "v3-KnowledgeSubjectObservationValue")) {
142              list.add(new CanonicalResourceAnalysis(cr, f.getAbsolutePath()));
143            }
144          }
145        } catch (Exception e) {
146          System.out.println(f.getAbsolutePath() + " not a resource? " + e.getMessage());
147        }
148      }
149    }
150
151  }
152
153  public class CanonicalResourceAnalysis {
154
155    private final CanonicalResource resource;
156    private final String filename;
157    private CanonicalResource r2;
158    private CanonicalResource r3;
159    private CanonicalResource r4;
160    private String fmm;
161    private boolean normative;
162    private String recommendation;
163
164    public CanonicalResourceAnalysis(CanonicalResource cr, String filename) {
165      this.resource = cr;
166      this.filename = filename;
167    }
168
169    public String summary() {
170//      return "Relevant: "+resource.getUrl()+" [r2: "+r2Ver+"/"+r2Fmm+"]"+" [r3: "+r3Ver+"/"+r3Fmm+"]"+" [r4: "+r4Ver+"/"+r4Fmm+"/"+r4Normative+"] ---> "+recommendation;
171      return resource.getUrl() + " ---> " + recommendation + "   in " + filename;
172    }
173
174    public void analyse(Map<String, CanonicalResource> r2l, Map<String, CanonicalResource> r3l, Map<String, CanonicalResource> r4l) {
175      r2 = findMatch(r2l);
176      r3 = findMatch(r3l);
177      r4 = findMatch(r4l);
178
179      fmm = r4 != null ? ToolingExtensions.readStringExtension(r4, ToolingExtensions.EXT_FMM_LEVEL) : null;
180      normative = (r4 != null) && ToolingExtensions.readStringExtension(r4, ToolingExtensions.EXT_NORMATIVE_VERSION) != null;
181      if (normative) {
182        recommendation = "1.0.0";
183      } else if (Utilities.existsInList(fmm, "3", "4", "5")) {
184        recommendation = "0.5.0";
185      } else {
186        int i = 1;
187        if (r2 != null && r3 != null && !match(r2, r3, r2l, r3l)) {
188          i++;
189        }
190        if (r3 != null && r4 != null && !match(r3, r4, r3l, r4l)) {
191          i++;
192        }
193        recommendation = "0." + i + ".0";
194      }
195    }
196
197    private boolean match(CanonicalResource l, CanonicalResource r, Map<String, CanonicalResource> ll, Map<String, CanonicalResource> rl) {
198      if (l instanceof CodeSystem && r instanceof CodeSystem) {
199        return matchCS((CodeSystem) l, (CodeSystem) r);
200      } else if (l instanceof ValueSet && r instanceof ValueSet) {
201        return matchVS((ValueSet) l, (ValueSet) r, ll, rl);
202      } else {
203        return false;
204      }
205    }
206
207    private boolean matchVS(ValueSet l, ValueSet r, Map<String, CanonicalResource> ll, Map<String, CanonicalResource> rl) {
208      if (l.getCompose().getInclude().size() == 1 && l.getCompose().getExclude().isEmpty() && l.getCompose().getIncludeFirstRep().hasSystem() && !l.getCompose().getIncludeFirstRep().hasConcept() && !l.getCompose().getIncludeFirstRep().hasFilter() &&
209        r.getCompose().getInclude().size() == 1 && r.getCompose().getExclude().isEmpty() && r.getCompose().getIncludeFirstRep().hasSystem() && !r.getCompose().getIncludeFirstRep().hasConcept() && !r.getCompose().getIncludeFirstRep().hasFilter()) {
210        CodeSystem lc = (CodeSystem) ll.get(l.getCompose().getIncludeFirstRep().getSystem());
211        CodeSystem rc = (CodeSystem) rl.get(l.getCompose().getIncludeFirstRep().getSystem());
212        if (lc != null && rc != null) {
213          return matchCS(lc, rc);
214        }
215      }
216      return false;
217    }
218
219    private boolean matchCS(CodeSystem l, CodeSystem r) {
220      return Base.compareDeep(l.getConcept(), r.getConcept(), false);
221    }
222
223    public CanonicalResource findMatch(Map<String, CanonicalResource> r2) {
224      CanonicalResource r = r2.get(resource.getUrl());
225      if (r == null) {
226        r = r2.get(resource.getUrl().replaceAll("http://terminology.hl7.org/", "http://hl7.org/fhir/"));
227      }
228      if (r == null) {
229        r = r2.get(resource.getUrl().replaceAll("http://terminology.hl7.org/CodeSystem", "http://hl7.org/fhir/"));
230      }
231      return r;
232    }
233
234    public String getId() {
235      return resource.getId();
236    }
237
238    public String fhirType() {
239      return resource.fhirType();
240    }
241
242  }
243
244
245}