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