001package org.hl7.fhir.r4.test.utils;
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 java.io.BufferedInputStream;
033import java.io.ByteArrayInputStream;
034import java.io.ByteArrayOutputStream;
035import java.io.File;
036import java.io.FileInputStream;
037import java.io.FileOutputStream;
038import java.io.IOException;
039import java.io.InputStream;
040import java.net.URL;
041import java.util.Collection;
042import java.util.HashMap;
043import java.util.List;
044import java.util.Map;
045import java.util.zip.ZipEntry;
046import java.util.zip.ZipInputStream;
047
048import org.apache.commons.io.FileUtils;
049import org.apache.commons.io.IOUtils;
050import org.apache.commons.lang3.NotImplementedException;
051import org.hl7.fhir.exceptions.FHIRException;
052import org.hl7.fhir.exceptions.FHIRFormatError;
053import org.hl7.fhir.r4.context.SimpleWorkerContext;
054import org.hl7.fhir.r4.formats.IParser.OutputStyle;
055import org.hl7.fhir.r4.formats.JsonParser;
056import org.hl7.fhir.r4.formats.RdfParser;
057import org.hl7.fhir.r4.formats.RdfParserBase;
058import org.hl7.fhir.r4.formats.XmlParser;
059import org.hl7.fhir.r4.model.Constants;
060import org.hl7.fhir.r4.model.Resource;
061import org.hl7.fhir.utilities.CSFile;
062import org.hl7.fhir.utilities.CSFileInputStream;
063import org.hl7.fhir.utilities.TextFile;
064import org.hl7.fhir.utilities.Utilities;
065import org.xmlpull.v1.XmlPullParser;
066import org.xmlpull.v1.XmlPullParserException;
067import org.xmlpull.v1.XmlPullParserFactory;
068
069public class ToolsHelper {
070
071  public static void main(String[] args) {
072    try {
073      ToolsHelper self = new ToolsHelper();
074      if (args.length == 0)
075        throw new FHIRException(
076            "Missing Command Parameter. Valid Commands: round, json, version, fragments, snapshot-maker");
077      if (args[0].equals("round"))
078        self.executeRoundTrip(args);
079      else if (args[0].equals("test"))
080        self.executeTest(args);
081      else if (args[0].equals("examples"))
082        self.executeExamples(args);
083      else if (args[0].equals("json"))
084        self.executeJson(args);
085      else if (args[0].equals("cxml"))
086        self.executeCanonicalXml(args);
087      else if (args[0].equals("version"))
088        self.executeVersion(args);
089      else if (args[0].equals("fragments"))
090        self.executeFragments(args);
091      else if (args[0].equals("snapshot-maker"))
092        self.generateSnapshots(args);
093      else
094        throw new FHIRException("Unknown command '" + args[0]
095            + "'. Valid Commands: round, test, examples, json, cxml, version, fragments, snapshot-maker");
096    } catch (Throwable e) {
097      try {
098        e.printStackTrace();
099        TextFile.stringToFile(e.toString(), (args.length == 0 ? "tools" : args[0]) + ".err");
100      } catch (Exception e1) {
101        e1.printStackTrace();
102      }
103    }
104  }
105
106  private void executeExamples(String[] args) throws IOException {
107    try {
108      @SuppressWarnings("unchecked")
109      List<String> lines = FileUtils.readLines(new File(args[1]), "UTF-8");
110      String srcDir = lines.get(0);
111      lines.remove(0);
112      processExamples(srcDir, lines);
113      TextFile.stringToFile("ok", Utilities.changeFileExt(args[1], ".out"));
114    } catch (Exception e) {
115      TextFile.stringToFile(e.getMessage(), Utilities.changeFileExt(args[1], ".out"));
116    }
117  }
118
119  private void generateSnapshots(String[] args) throws IOException, FHIRException {
120    if (args.length == 1) {
121      System.out.println("tools.jar snapshot-maker [source] -defn [definitions]");
122      System.out.println("");
123      System.out.println(
124          "Generates a snapshot from a differential. The nominated profile must have a single struture that has a differential");
125      System.out.println("");
126      System.out.println(
127          "source - the profile to generate the snapshot for. Maybe a file name, or a URL reference to a server running FHIR RESTful API");
128      System.out.println("definitions - filename for local copy of the validation.zip file");
129    }
130    String address = args[1];
131    String definitions = args[3];
132
133    SimpleWorkerContext context = SimpleWorkerContext.fromDefinitions(getDefinitions(definitions));
134
135    // if (address.startsWith("http:") || address.startsWith("http:")) {
136    // // this is on a restful interface
137    // String[] parts = address.split("\\/Profile\\/");
138    // if (parts.length != 2)
139    // throw new FHIRException("Unable to understand address of profile");
140    // StructureDefinition profile =
141    // context.fetchResource(StructureDefinition.class, parts[1]);
142    // ProfileUtilities utils = new ProfileUtilities(context);
143    // StructureDefinition base = utils.getProfile(profile, profile.getBase());
144    // if (base == null)
145    // throw new FHIRException("Unable to resolve profile "+profile.getBase());
146    // utils.generateSnapshot(base, profile, address, profile.getName(), null,
147    // null);
148    // // client.update(StructureDefinition.class, profile, parts[1]);
149    // } else {
150    throw new NotImplementedException("generating snapshots not done yet (address = " + address + ")");
151    // }
152  }
153
154  private Map<String, byte[]> getDefinitions(String definitions) throws IOException, FHIRException {
155    Map<String, byte[]> results = new HashMap<String, byte[]>();
156    readDefinitions(results, loadDefinitions(definitions));
157    return results;
158  }
159
160  private void readDefinitions(Map<String, byte[]> map, byte[] defn) throws IOException {
161    ZipInputStream zip = new ZipInputStream(new ByteArrayInputStream(defn));
162    ZipEntry ze;
163    while ((ze = zip.getNextEntry()) != null) {
164      if (!ze.getName().endsWith(".zip") && !ze.getName().endsWith(".jar")) { // skip saxon .zip
165        String name = ze.getName();
166        InputStream in = zip;
167        ByteArrayOutputStream b = new ByteArrayOutputStream();
168        int n;
169        byte[] buf = new byte[1024];
170        while ((n = in.read(buf, 0, 1024)) > -1) {
171          b.write(buf, 0, n);
172        }
173        map.put(name, b.toByteArray());
174      }
175      zip.closeEntry();
176    }
177    zip.close();
178  }
179
180  private byte[] loadDefinitions(String definitions) throws FHIRException, IOException {
181    byte[] defn;
182    // if (Utilities.noString(definitions)) {
183    // defn = loadFromUrl(MASTER_SOURCE);
184    // } else
185    if (definitions.startsWith("https:") || definitions.startsWith("http:")) {
186      defn = loadFromUrl(definitions);
187    } else if (new File(definitions).exists()) {
188      defn = loadFromFile(definitions);
189    } else
190      throw new FHIRException("Unable to find FHIR validation Pack (source = " + definitions + ")");
191    return defn;
192  }
193
194  private byte[] loadFromUrl(String src) throws IOException {
195    URL url = new URL(src);
196    byte[] str = IOUtils.toByteArray(url.openStream());
197    return str;
198  }
199
200  private byte[] loadFromFile(String src) throws IOException {
201    FileInputStream in = new FileInputStream(src);
202    byte[] b = new byte[in.available()];
203    in.read(b);
204    in.close();
205    return b;
206  }
207
208  protected XmlPullParser loadXml(InputStream stream) throws XmlPullParserException, IOException {
209    BufferedInputStream input = new BufferedInputStream(stream);
210    XmlPullParserFactory factory = XmlPullParserFactory
211        .newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null);
212    factory.setNamespaceAware(true);
213    XmlPullParser xpp = factory.newPullParser();
214    xpp.setInput(input, "UTF-8");
215    xpp.next();
216    return xpp;
217  }
218
219  protected int nextNoWhitespace(XmlPullParser xpp) throws XmlPullParserException, IOException {
220    int eventType = xpp.getEventType();
221    while (eventType == XmlPullParser.TEXT && xpp.isWhitespace())
222      eventType = xpp.next();
223    return eventType;
224  }
225
226  public void executeFragments(String[] args) throws IOException {
227    try {
228      File source = new CSFile(args[1]);
229      if (!source.exists())
230        throw new FHIRException("Source File \"" + source.getAbsolutePath() + "\" not found");
231      XmlPullParser xpp = loadXml(new FileInputStream(source));
232      nextNoWhitespace(xpp);
233      if (!xpp.getName().equals("tests"))
234        throw new FHIRFormatError("Unable to parse file - starts with " + xpp.getName());
235      xpp.next();
236      nextNoWhitespace(xpp);
237      StringBuilder s = new StringBuilder();
238      s.append("<results>\r\n");
239      int fail = 0;
240      while (xpp.getEventType() == XmlPullParser.START_TAG && xpp.getName().equals("test")) {
241        String id = xpp.getAttributeValue(null, "id");
242        String type = xpp.getAttributeValue(null, "type");
243        // test
244        xpp.next();
245        nextNoWhitespace(xpp);
246        // pre
247        xpp.next();
248        nextNoWhitespace(xpp);
249        XmlParser p = new XmlParser();
250        try {
251          p.parseFragment(xpp, type);
252          s.append("<result id=\"" + id + "\" outcome=\"ok\"/>\r\n");
253          nextNoWhitespace(xpp);
254        } catch (Exception e) {
255          s.append(
256              "<result id=\"" + id + "\" outcome=\"error\" msg=\"" + Utilities.escapeXml(e.getMessage()) + "\"/>\r\n");
257          fail++;
258        }
259        while (xpp.getEventType() != XmlPullParser.END_TAG || !xpp.getName().equals("pre"))
260          xpp.next();
261        xpp.next();
262        nextNoWhitespace(xpp);
263        xpp.next();
264        nextNoWhitespace(xpp);
265      }
266      s.append("</results>\r\n");
267
268      TextFile.stringToFile(s.toString(), args[2]);
269    } catch (Exception e) {
270      e.printStackTrace();
271      TextFile.stringToFile(e.getMessage(), args[2]);
272    }
273  }
274
275  public void executeRoundTrip(String[] args) throws IOException, FHIRException {
276    FileInputStream in;
277    File source = new CSFile(args[1]);
278    File dest = new CSFile(args[2]);
279    if (args.length >= 4) {
280      Utilities.copyFile(args[1], args[3]);
281    }
282
283    if (!source.exists())
284      throw new FHIRException("Source File \"" + source.getAbsolutePath() + "\" not found");
285    in = new CSFileInputStream(source);
286    XmlParser p = new XmlParser();
287    JsonParser parser = new JsonParser();
288    JsonParser pj = parser;
289    Resource rf = p.parse(in);
290    ByteArrayOutputStream json = new ByteArrayOutputStream();
291    parser.setOutputStyle(OutputStyle.PRETTY);
292    parser.compose(json, rf);
293    json.close();
294    TextFile.stringToFile(new String(json.toByteArray()), Utilities.changeFileExt(dest.getAbsolutePath(), ".json"));
295    rf = pj.parse(new ByteArrayInputStream(json.toByteArray()));
296    FileOutputStream s = new FileOutputStream(dest);
297    new XmlParser().compose(s, rf, true);
298    s.close();
299  }
300
301  public String executeJson(String[] args) throws IOException, FHIRException {
302    FileInputStream in;
303    File source = new CSFile(args[1]);
304    File dest = new CSFile(args[2]);
305    File destc = new CSFile(Utilities.changeFileExt(args[2], ".canonical.json"));
306    File destt = new CSFile(args[2] + ".tmp");
307    File destr = new CSFile(Utilities.changeFileExt(args[2], ".ttl"));
308
309    if (!source.exists())
310      throw new FHIRException("Source File \"" + source.getAbsolutePath() + "\" not found");
311    in = new CSFileInputStream(source);
312    XmlParser p = new XmlParser();
313    Resource rf = p.parse(in);
314    JsonParser json = new JsonParser();
315    json.setOutputStyle(OutputStyle.PRETTY);
316    FileOutputStream s = new FileOutputStream(dest);
317    json.compose(s, rf);
318    s.close();
319    json.setOutputStyle(OutputStyle.CANONICAL);
320    s = new FileOutputStream(destc);
321    json.compose(s, rf);
322    s.close();
323    json.setSuppressXhtml("Snipped for Brevity");
324    json.setOutputStyle(OutputStyle.PRETTY);
325    s = new FileOutputStream(destt);
326    json.compose(s, rf);
327    s.close();
328
329    RdfParserBase rdf = new RdfParser();
330    s = new FileOutputStream(destr);
331    rdf.compose(s, rf);
332    s.close();
333
334    return TextFile.fileToString(destt.getAbsolutePath());
335  }
336
337  public void executeCanonicalXml(String[] args) throws FHIRException, IOException {
338    FileInputStream in;
339    File source = new CSFile(args[1]);
340    File dest = new CSFile(args[2]);
341
342    if (!source.exists())
343      throw new FHIRException("Source File \"" + source.getAbsolutePath() + "\" not found");
344    in = new CSFileInputStream(source);
345    XmlParser p = new XmlParser();
346    Resource rf = p.parse(in);
347    XmlParser cxml = new XmlParser();
348    cxml.setOutputStyle(OutputStyle.NORMAL);
349    cxml.compose(new FileOutputStream(dest), rf);
350  }
351
352  private void executeVersion(String[] args) throws IOException {
353    TextFile.stringToFile(org.hl7.fhir.r4.utils.Version.VERSION + ":" + Constants.VERSION, args[1]);
354  }
355
356  public void processExamples(String rootDir, Collection<String> list) throws FHIRException {
357    for (String n : list) {
358      try {
359        String filename = rootDir + n + ".xml";
360        // 1. produce canonical XML
361        CSFileInputStream source = new CSFileInputStream(filename);
362        FileOutputStream dest = new FileOutputStream(Utilities.changeFileExt(filename, ".canonical.xml"));
363        XmlParser p = new XmlParser();
364        Resource r = p.parse(source);
365        XmlParser cxml = new XmlParser();
366        cxml.setOutputStyle(OutputStyle.CANONICAL);
367        cxml.compose(dest, r);
368
369        // 2. produce JSON
370        source = new CSFileInputStream(filename);
371        dest = new FileOutputStream(Utilities.changeFileExt(filename, ".json"));
372        r = p.parse(source);
373        JsonParser json = new JsonParser();
374        json.setOutputStyle(OutputStyle.PRETTY);
375        json.compose(dest, r);
376        json = new JsonParser();
377        json.setOutputStyle(OutputStyle.CANONICAL);
378        dest = new FileOutputStream(Utilities.changeFileExt(filename, ".canonical.json"));
379        json.compose(dest, r);
380
381        // 2. produce JSON
382        dest = new FileOutputStream(Utilities.changeFileExt(filename, ".ttl"));
383        RdfParserBase rdf = new RdfParser();
384        rdf.compose(dest, r);
385      } catch (Exception e) {
386        e.printStackTrace();
387        throw new FHIRException("Error Processing " + n + ".xml: " + e.getMessage(), e);
388      }
389    }
390  }
391
392  public void testRoundTrip(String rootDir, String tmpDir, Collection<String> names) throws Throwable {
393    try {
394      System.err
395          .println("Round trip from " + rootDir + " to " + tmpDir + ":" + Integer.toString(names.size()) + " files");
396      for (String n : names) {
397        System.err.print("  " + n);
398        String source = rootDir + n + ".xml";
399        // String tmpJson = tmpDir + n + ".json";
400        String tmp = tmpDir + n.replace(File.separator, "-") + ".tmp";
401        String dest = tmpDir + n.replace(File.separator, "-") + ".java.xml";
402
403        FileInputStream in = new FileInputStream(source);
404        XmlParser xp = new XmlParser();
405        Resource r = xp.parse(in);
406        System.err.print(".");
407        JsonParser jp = new JsonParser();
408        FileOutputStream out = new FileOutputStream(tmp);
409        jp.setOutputStyle(OutputStyle.PRETTY);
410        jp.compose(out, r);
411        out.close();
412        r = null;
413        System.err.print(".");
414
415        in = new FileInputStream(tmp);
416        System.err.print(",");
417        r = jp.parse(in);
418        System.err.print(".");
419        out = new FileOutputStream(dest);
420        new XmlParser().compose(out, r, true);
421        System.err.println("!");
422        out.close();
423        r = null;
424        System.gc();
425      }
426    } catch (Throwable e) {
427      System.err.println("Error: " + e.getMessage());
428      throw e;
429    }
430  }
431
432  private void executeTest(String[] args) throws Throwable {
433    try {
434      @SuppressWarnings("unchecked")
435      List<String> lines = FileUtils.readLines(new File(args[1]), "UTF-8");
436      String srcDir = lines.get(0);
437      lines.remove(0);
438      String dstDir = lines.get(0).trim();
439      lines.remove(0);
440      testRoundTrip(srcDir, dstDir, lines);
441      TextFile.stringToFile("ok", Utilities.changeFileExt(args[1], ".out"));
442    } catch (Exception e) {
443      TextFile.stringToFile(e.getMessage(), Utilities.changeFileExt(args[1], ".out"));
444    }
445  }
446}