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