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