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