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
032import org.hl7.fhir.convertors.loaders.loaderR3.R2ToR3Loader;
033import org.hl7.fhir.dstu3.context.SimpleWorkerContext;
034import org.hl7.fhir.dstu3.elementmodel.Manager;
035import org.hl7.fhir.dstu3.elementmodel.Manager.FhirFormat;
036import org.hl7.fhir.dstu3.formats.FormatUtilities;
037import org.hl7.fhir.dstu3.formats.IParser.OutputStyle;
038import org.hl7.fhir.dstu3.formats.JsonParser;
039import org.hl7.fhir.dstu3.model.*;
040import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionKind;
041import org.hl7.fhir.dstu3.utils.StructureMapUtilities;
042import org.hl7.fhir.dstu3.utils.StructureMapUtilities.ITransformerServices;
043import org.hl7.fhir.exceptions.FHIRException;
044import org.hl7.fhir.utilities.TextFile;
045
046import java.io.*;
047import java.util.*;
048import java.util.zip.ZipEntry;
049import java.util.zip.ZipInputStream;
050
051/**
052 * This class manages conversion from R2 to R3 and vice versa
053 * <p>
054 * To use this class, do the following:
055 * <p>
056 * - provide a stream or path (file or URL) that points to R2 definitions (from http://hl7.org/fhir/DSTU2/downloads.html)
057 * - provide a stream or a path (file or URL) that points to the R3 definitions  (from http://hl7.org/fhir/STU3/downloads.html)
058 * - provide a stream or a path (file or URL) that points to R2/R3 map files (from http://hl7.org/fhir/r2r3maps.zip)
059 * <p>
060 * - call convert() (can call this more than once, but not multithread safe)
061 *
062 * @author Grahame Grieve
063 */
064public class R2R3ConversionManager implements ITransformerServices {
065
066  private final Map<String, StructureMap> library = new HashMap<String, StructureMap>();
067  private final List<Resource> extras = new ArrayList<Resource>();
068  private SimpleWorkerContext contextR2;
069  private SimpleWorkerContext contextR3;
070  private boolean needPrepare = false;
071  private StructureMapUtilities smu3;
072  private StructureMapUtilities smu2;
073  private OutputStyle style = OutputStyle.PRETTY;
074
075  public static void main(String[] args) throws IOException, FHIRException {
076    if (args.length == 0 || !hasParam(args, "-d2") || !hasParam(args, "-d3") || !hasParam(args, "-maps") || !hasParam(args, "-src") || !hasParam(args, "-dest") || (!hasParam(args, "-r2") && !hasParam(args, "-r3"))) {
077      System.out.println("R2 <--> R3 Convertor");
078      System.out.println("====================");
079      System.out.println();
080      System.out.println("parameters: -d2 [r2 definitions] -d3 [r3 definitions] -maps [map source] -src [source] -dest [dest] -r2/3 - fmt [format]");
081      System.out.println();
082      System.out.println("d2: definitions from http://hl7.org/fhir/DSTU2/downloads.html");
083      System.out.println("d3: definitions from http://hl7.org/fhir/STU3/downloads.html");
084      System.out.println("maps: R2/R3 maps from http://hl7.org/fhir/r2r3maps.zip");
085      System.out.println("src: filename for source to convert");
086      System.out.println("dest: filename for destination of conversion");
087      System.out.println("-r2: source is r2, convert to r3");
088      System.out.println("-r3: source is r3, convert to r2");
089      System.out.println("-fmt: xml | json (xml is default)");
090    } else {
091      R2R3ConversionManager self = new R2R3ConversionManager();
092      self.setR2Definitions(getNamedParam(args, "-d2"));
093      self.setR3Definitions(getNamedParam(args, "-d3"));
094      self.setMappingLibrary(getNamedParam(args, "-maps"));
095      FhirFormat fmt = hasParam(args, "-fmt") ? getNamedParam(args, "-fmt").equalsIgnoreCase("json") ? FhirFormat.JSON : FhirFormat.XML : FhirFormat.XML;
096      InputStream src = new FileInputStream(getNamedParam(args, "-src"));
097      OutputStream dst = new FileOutputStream(getNamedParam(args, "-dest"));
098      self.convert(src, dst, hasParam(args, "-r2"), fmt);
099    }
100  }
101
102  private static boolean hasParam(String[] args, String param) {
103    for (String a : args)
104      if (a.equals(param))
105        return true;
106    return false;
107  }
108
109  private static String getNamedParam(String[] args, String param) {
110    boolean found = false;
111    for (String a : args) {
112      if (found)
113        return a;
114      if (a.equals(param)) {
115        found = true;
116      }
117    }
118    return null;
119  }
120
121  public OutputStyle getStyle() {
122    return style;
123  }
124
125  public void setStyle(OutputStyle style) {
126    this.style = style;
127  }
128
129  public List<Resource> getExtras() {
130    return extras;
131  }
132
133  // set up ------------------------------------------------------------------
134  public void setR2Definitions(InputStream stream) throws IOException, FHIRException {
135    needPrepare = true;
136    R2ToR3Loader ldr = new R2ToR3Loader();
137    ldr.setPatchUrls(true).setKillPrimitives(true);
138    Map<String, InputStream> files = readInputStream(stream);
139    contextR2 = new SimpleWorkerContext();
140    contextR2.setAllowLoadingDuplicates(true);
141    contextR2.loadFromFile(files.get("profiles-types.xml"), "profiles-types.xml", ldr);
142    contextR2.loadFromFile(files.get("profiles-resources.xml"), "profiles-resources.xml", ldr);
143    contextR2.loadFromFile(files.get("valuesets.xml"), "valuesets.xml", ldr);
144  }
145
146  public void setR2Definitions(String source) throws IOException, FHIRException {
147    File f = new File(source);
148    if (f.exists())
149      setR2Definitions(new FileInputStream(f));
150    else
151      setR2Definitions(fetch(source));
152  }
153
154  public void setR3Definitions(InputStream stream) throws IOException, FHIRException {
155    needPrepare = true;
156    Map<String, InputStream> files = readInputStream(stream);
157    contextR3 = new SimpleWorkerContext();
158    contextR2.setAllowLoadingDuplicates(true);
159    contextR3.loadFromFile(files.get("profiles-types.xml"), "profiles-types.xml", null);
160    contextR3.loadFromFile(files.get("profiles-resources.xml"), "profiles-resources.xml", null);
161    contextR3.loadFromFile(files.get("extension-definitions.xml"), "extension-definitions.xml", null);
162    contextR3.loadFromFile(files.get("valuesets.xml"), "valuesets.xml", null);
163    contextR3.setCanRunWithoutTerminology(true);
164  }
165
166  public void setR3Definitions(String source) throws IOException, FHIRException {
167    File f = new File(source);
168    if (f.exists())
169      setR3Definitions(new FileInputStream(f));
170    else
171      setR3Definitions(fetch(source));
172  }
173
174  public void setMappingLibrary(InputStream stream) throws IOException, FHIRException {
175    needPrepare = true;
176    Map<String, InputStream> files = readInputStream(stream);
177    for (InputStream s : files.values()) {
178      StructureMap sm = new StructureMapUtilities(contextR3).parse(TextFile.streamToString(s));
179      library.put(sm.getUrl(), sm);
180    }
181  }
182
183  public void setMappingLibrary(String source) throws IOException, FHIRException {
184    File f = new File(source);
185    if (f.exists())
186      setMappingLibrary(new FileInputStream(f));
187    else
188      setMappingLibrary(fetch(source));
189  }
190
191  // support
192  private InputStream fetch(String source) {
193    throw new Error("not done yet");
194  }
195
196  private Map<String, InputStream> readInputStream(InputStream stream) throws IOException {
197    Map<String, InputStream> res = new HashMap<String, InputStream>();
198    ZipInputStream zip = new ZipInputStream(stream);
199    ZipEntry ze = null;
200    while ((ze = zip.getNextEntry()) != null) {
201      String n = ze.getName();
202      ByteArrayOutputStream bs = new ByteArrayOutputStream();
203      for (int c = zip.read(); c != -1; c = zip.read()) {
204        bs.write(c);
205      }
206      bs.close();
207      res.put(n, new ByteArrayInputStream(bs.toByteArray()));
208      zip.closeEntry();
209    }
210    zip.close();
211    return res;
212  }
213
214  private void prepare() throws FHIRException {
215    if (contextR2 == null)
216      throw new FHIRException("No R2 definitions provided");
217    if (contextR3 == null)
218      throw new FHIRException("No R3 definitions provided");
219    if (library == null)
220      throw new FHIRException("No R2/R# conversion maps provided");
221
222    if (needPrepare) {
223      for (StructureDefinition sd : contextR2.allStructures()) {
224        StructureDefinition sdn = sd.copy();
225        sdn.getExtension().clear();
226        contextR3.seeResource(sdn.getUrl(), sdn);
227      }
228
229      for (StructureDefinition sd : contextR3.allStructures()) {
230        if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) {
231          contextR2.seeResource(sd.getUrl(), sd);
232          StructureDefinition sdn = sd.copy();
233          sdn.setUrl(sdn.getUrl().replace("http://hl7.org/fhir/", "http://hl7.org/fhir/DSTU2/"));
234          sdn.addExtension().setUrl("http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace").setValue(new UriType("http://hl7.org/fhir"));
235          contextR2.seeResource(sdn.getUrl(), sdn);
236          contextR3.seeResource(sdn.getUrl(), sdn);
237        }
238      }
239
240      contextR2.setExpansionProfile(new ExpansionProfile().setUrl("urn:uuid:" + UUID.randomUUID().toString().toLowerCase()));
241      contextR3.setExpansionProfile(new ExpansionProfile().setUrl("urn:uuid:" + UUID.randomUUID().toString().toLowerCase()));
242
243      smu3 = new StructureMapUtilities(contextR3, library, this);
244      smu2 = new StructureMapUtilities(contextR2, library, this);
245
246      needPrepare = false;
247    }
248  }
249
250  // execution
251  public byte[] convert(byte[] source, boolean r2ToR3, FhirFormat format) throws FHIRException, IOException {
252    prepare();
253    ByteArrayOutputStream bs = new ByteArrayOutputStream();
254    if (r2ToR3)
255      convertToR3(new ByteArrayInputStream(source), bs, format);
256    else
257      convertToR2(new ByteArrayInputStream(source), bs, format);
258    bs.close();
259    return bs.toByteArray();
260  }
261
262  public void convert(InputStream source, OutputStream dest, boolean r2ToR3, FhirFormat format) throws FHIRException, IOException {
263    prepare();
264    if (r2ToR3)
265      convertToR3(source, dest, format);
266    else
267      convertToR2(source, dest, format);
268  }
269
270  public org.hl7.fhir.dstu2.model.Resource convert(org.hl7.fhir.dstu3.model.Resource source) throws IOException, FHIRException {
271    ByteArrayOutputStream bs = new ByteArrayOutputStream();
272    new JsonParser().compose(bs, source);
273    bs.close();
274    return new org.hl7.fhir.dstu2.formats.JsonParser().parse(convert(bs.toByteArray(), false, FhirFormat.JSON));
275  }
276
277  public org.hl7.fhir.dstu3.model.Resource convert(org.hl7.fhir.dstu2.model.Resource source) throws IOException, FHIRException {
278    ByteArrayOutputStream bs = new ByteArrayOutputStream();
279    new org.hl7.fhir.dstu2.formats.JsonParser().compose(bs, source);
280    bs.close();
281    return new JsonParser().parse(convert(bs.toByteArray(), false, FhirFormat.JSON));
282  }
283
284  private void convertToR3(InputStream source, OutputStream dest, FhirFormat format) throws FHIRException, IOException {
285    org.hl7.fhir.dstu3.elementmodel.Element r2 = new org.hl7.fhir.dstu3.elementmodel.XmlParser(contextR2).parse(source);
286    StructureMap map = library.get("http://hl7.org/fhir/StructureMap/" + r2.fhirType() + "2to3");
287    if (map == null)
288      throw new FHIRException("No Map Found from R2 to R3 for " + r2.fhirType());
289    String tn = smu3.getTargetType(map).getType();
290    Resource r3 = ResourceFactory.createResource(tn);
291    smu3.transform(new TransformContextR2R3(contextR3, r2.getChildValue("id")), r2, map, r3);
292    FormatUtilities.makeParser(format).setOutputStyle(style).compose(dest, r3);
293  }
294
295  private void convertToR2(InputStream source, OutputStream dest, FhirFormat format) throws FHIRException, IOException {
296    org.hl7.fhir.dstu3.elementmodel.Element r3 = new org.hl7.fhir.dstu3.elementmodel.XmlParser(contextR3).parse(source);
297    StructureMap map = library.get("??");
298    String tn = smu3.getTargetType(map).getType();
299    StructureDefinition sd = smu2.getTargetType(map);
300    org.hl7.fhir.dstu3.elementmodel.Element r2 = Manager.build(contextR2, sd);
301    smu2.transform(contextR2, r3, map, r2);
302    org.hl7.fhir.dstu3.elementmodel.Manager.compose(contextR2, r2, dest, format, style, null);
303  }
304
305  @Override
306  public void log(String message) {
307//    System.out.println(message);
308  }
309
310  @Override
311  public Base createType(Object appInfo, String name) throws FHIRException {
312    SimpleWorkerContext context = ((TransformContextR2R3) appInfo).getContext();
313    if (context == contextR2) {
314      StructureDefinition sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/DSTU2/StructureDefinition/" + name);
315      if (sd == null)
316        throw new FHIRException("Type not found: '" + name + "'");
317      return Manager.build(context, sd);
318    } else
319      return ResourceFactory.createResourceOrType(name);
320  }
321
322  @Override
323  public Base createResource(Object appInfo, Base res) {
324    if (res instanceof Resource && (res.fhirType().equals("CodeSystem") || res.fhirType().equals("CareTeam")) || res.fhirType().equals("PractitionerRole")) {
325      Resource r = (Resource) res;
326      extras.add(r);
327      r.setId(((TransformContextR2R3) appInfo).getId() + "-" + extras.size()); //todo: get this into appinfo
328    }
329    return res;
330  }
331
332  @Override
333  public Coding translate(Object appInfo, Coding source, String conceptMapUrl) throws FHIRException {
334    throw new Error("translate not done yet");
335  }
336
337  @Override
338  public Base resolveReference(Object appContext, String url) {
339    for (Resource r : extras) {
340      if (r instanceof MetadataResource) {
341        MetadataResource mr = (MetadataResource) r;
342        if (url.equals(mr.getUrl()))
343          return mr;
344      }
345      if (url.equals(r.fhirType() + "/" + r.getId()))
346        return r;
347    }
348
349    return null;
350  }
351
352  @Override
353  public List<Base> performSearch(Object appContext, String url) {
354    List<Base> results = new ArrayList<Base>();
355    String[] parts = url.split("\\?");
356    if (parts.length == 2 && parts[0].substring(1).equals("PractitionerRole")) {
357      String[] vals = parts[1].split("\\=");
358      if (vals.length == 2 && vals[0].equals("practitioner"))
359        for (Resource r : extras) {
360          if (r instanceof PractitionerRole && ((PractitionerRole) r).getPractitioner().getReference().equals("Practitioner/" + vals[1])) {
361            results.add(r);
362          }
363        }
364    }
365    return results;
366  }
367}