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