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