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        protected XmlPullParser loadXml(InputStream stream) throws XmlPullParserException, IOException {
150                BufferedInputStream input = new BufferedInputStream(stream);
151                XmlPullParserFactory factory = XmlPullParserFactory.newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null);
152                factory.setNamespaceAware(true);
153                XmlPullParser xpp = factory.newPullParser();
154                xpp.setInput(input, "UTF-8");
155                xpp.next();
156                return xpp;
157        }
158
159        protected int nextNoWhitespace(XmlPullParser xpp) throws XmlPullParserException, IOException {
160                int eventType = xpp.getEventType();
161                while (eventType == XmlPullParser.TEXT && xpp.isWhitespace())
162                        eventType = xpp.next();
163                return eventType;
164        }
165
166        public void executeFragments(String[] args) throws IOException {
167                try {
168                        File source = new CSFile(args[1]);
169                        if (!source.exists())        
170                                throw new FHIRException("Source File \""+source.getAbsolutePath()+"\" not found");
171                        XmlPullParser xpp = loadXml(new FileInputStream(source));
172                        nextNoWhitespace(xpp);
173                        if (!xpp.getName().equals("tests"))
174                                throw new FHIRFormatError("Unable to parse file - starts with "+xpp.getName());
175                        xpp.next();
176                        nextNoWhitespace(xpp);
177                        StringBuilder s = new StringBuilder();
178                        s.append("<results>\r\n");
179                        int fail = 0;
180                        while (xpp.getEventType() == XmlPullParser.START_TAG && xpp.getName().equals("test")) {
181                                String id = xpp.getAttributeValue(null, "id");
182                                String type = xpp.getAttributeValue(null, "type");
183                                // test
184                                xpp.next();
185                                nextNoWhitespace(xpp);
186                                // pre
187                                xpp.next();
188                                nextNoWhitespace(xpp);
189                                XmlParser p = new XmlParser();
190                                try {
191                                        p.parseFragment(xpp, type);
192                                        s.append("<result id=\""+id+"\" outcome=\"ok\"/>\r\n");
193                                        nextNoWhitespace(xpp);
194                                } catch (Exception e) {
195                                        s.append("<result id=\""+id+"\" outcome=\"error\" msg=\""+Utilities.escapeXml(e.getMessage())+"\"/>\r\n");
196                                        fail++;
197                                }
198                                while (xpp.getEventType() != XmlPullParser.END_TAG || !xpp.getName().equals("pre")) 
199                                        xpp.next();
200                                xpp.next();
201                                nextNoWhitespace(xpp);
202                                xpp.next();
203                                nextNoWhitespace(xpp);
204                        }
205                        s.append("</results>\r\n");
206
207                        TextFile.stringToFile(s.toString(), args[2]);
208                } catch (Exception e) {
209                        e.printStackTrace();
210                        TextFile.stringToFile(e.getMessage(), args[2]);
211                }
212        }
213
214        public void executeRoundTrip(String[] args) throws IOException, FHIRException {
215                FileInputStream in;
216                File source = new CSFile(args[1]);
217                File dest = new CSFile(args[2]);
218                if (args.length >= 4) {
219                        Utilities.copyFile(args[1], args[3]);
220                }
221
222                if (!source.exists())        
223                        throw new FHIRException("Source File \""+source.getAbsolutePath()+"\" not found");
224                in = new CSFileInputStream(source);
225                XmlParser p = new XmlParser();
226                JsonParser parser = new JsonParser();
227                JsonParser pj = parser;
228                Resource rf = p.parse(in);
229                ByteArrayOutputStream json = new ByteArrayOutputStream();
230                parser.setOutputStyle(OutputStyle.PRETTY);
231                parser.compose(json, rf);
232                json.close();
233                TextFile.stringToFile(new String(json.toByteArray()), Utilities.changeFileExt(dest.getAbsolutePath(), ".json"));
234                rf = pj.parse(new ByteArrayInputStream(json.toByteArray()));
235                FileOutputStream s = new FileOutputStream(dest);
236                new XmlParser().compose(s, rf, true);
237                s.close();
238        }
239
240        public String executeJson(String[] args) throws IOException, FHIRException {
241                FileInputStream in;
242                File source = new CSFile(args[1]);
243                File dest = new CSFile(args[2]);
244                File destc = new CSFile(Utilities.changeFileExt(args[2], ".canonical.json"));
245                File destt = new CSFile(args[2]+".tmp");
246                File destr = new CSFile(Utilities.changeFileExt(args[2], ".ttl"));
247
248                if (!source.exists())        
249                        throw new FHIRException("Source File \""+source.getAbsolutePath()+"\" not found");
250                in = new CSFileInputStream(source);
251                XmlParser p = new XmlParser();
252                Resource rf = p.parse(in);
253                JsonParser json = new JsonParser();
254                json.setOutputStyle(OutputStyle.PRETTY);
255                FileOutputStream s = new FileOutputStream(dest);
256                json.compose(s, rf);
257                s.close();
258                json.setOutputStyle(OutputStyle.CANONICAL);
259                s = new FileOutputStream(destc);
260                json.compose(s, rf);
261                s.close();
262                json.setSuppressXhtml("Snipped for Brevity");
263                json.setOutputStyle(OutputStyle.PRETTY);
264                s = new FileOutputStream(destt);
265                json.compose(s, rf);
266                s.close();    
267
268                RdfParserBase rdf = new RdfParser();
269                s = new FileOutputStream(destr);
270                rdf.compose(s, rf);
271                s.close();
272                
273                return TextFile.fileToString(destt.getAbsolutePath());
274        }
275
276        public void executeCanonicalXml(String[] args) throws FHIRException, IOException {
277                FileInputStream in;
278                File source = new CSFile(args[1]);
279                File dest = new CSFile(args[2]);
280
281                if (!source.exists())        
282                        throw new FHIRException("Source File \""+source.getAbsolutePath()+"\" not found");
283                in = new CSFileInputStream(source);
284                XmlParser p = new XmlParser();
285                Resource rf = p.parse(in);
286                XmlParser cxml = new XmlParser();
287                cxml.setOutputStyle(OutputStyle.NORMAL);
288                cxml.compose(new FileOutputStream(dest), rf);
289        }
290
291        private void executeVersion(String[] args) throws IOException {
292                TextFile.stringToFile(org.hl7.fhir.r5.utils.Version.VERSION+":"+Constants.VERSION, args[1]);
293        }
294
295        public void processExamples(String rootDir, Collection<String> list) throws FHIRException  {
296                for (String n : list) {
297                        try {
298                                String filename = rootDir + n + ".xml";
299                                // 1. produce canonical XML
300                                CSFileInputStream source = new CSFileInputStream(filename);
301                                FileOutputStream dest = new FileOutputStream(Utilities.changeFileExt(filename, ".canonical.xml"));
302                                XmlParser p = new XmlParser();
303                                Resource r = p.parse(source);
304                                XmlParser cxml = new XmlParser();
305                                cxml.setOutputStyle(OutputStyle.CANONICAL);
306                                cxml.compose(dest, r);
307
308                                // 2. produce JSON
309                                source = new CSFileInputStream(filename);
310                                dest = new FileOutputStream(Utilities.changeFileExt(filename, ".json"));
311                                r = p.parse(source);
312                                JsonParser json = new JsonParser();
313                                json.setOutputStyle(OutputStyle.PRETTY);
314                                json.compose(dest, r);
315                                json = new JsonParser();
316                                json.setOutputStyle(OutputStyle.CANONICAL);
317                                dest = new FileOutputStream(Utilities.changeFileExt(filename, ".canonical.json"));
318                                json.compose(dest, r);
319                                
320                                // 2. produce JSON
321                                dest = new FileOutputStream(Utilities.changeFileExt(filename, ".ttl"));
322                                RdfParserBase rdf = new RdfParser();
323                                rdf.compose(dest, r);
324                        } catch (Exception e) {
325                                e.printStackTrace();
326                                throw new FHIRException("Error Processing "+n+".xml: "+e.getMessage(), e);
327                        }
328                }
329        }
330
331        public void testRoundTrip(String rootDir, String tmpDir, Collection<String> names) throws Throwable {
332                try {
333                        System.err.println("Round trip from "+rootDir+" to "+tmpDir+":"+Integer.toString(names.size())+" files");
334                        for (String n : names) {
335                                System.err.print("  "+n);
336                                String source = rootDir + n + ".xml";
337                                // String tmpJson = tmpDir + n + ".json";
338                                String tmp = tmpDir + n.replace(File.separator, "-") + ".tmp";
339                                String dest = tmpDir + n.replace(File.separator, "-") + ".java.xml";
340
341                                FileInputStream in = new FileInputStream(source);
342                                XmlParser xp = new XmlParser();
343                                Resource r = xp.parse(in);
344                                System.err.print(".");
345                                JsonParser jp = new JsonParser();
346                                FileOutputStream out = new FileOutputStream(tmp);
347                                jp.setOutputStyle(OutputStyle.PRETTY);
348                                jp.compose(out, r);
349                                out.close();
350                                r = null;
351                                System.err.print(".");
352
353                                in = new FileInputStream(tmp);
354                                System.err.print(",");
355                                r = jp.parse(in);
356                                System.err.print(".");
357                                out = new FileOutputStream(dest);
358                                new XmlParser().compose(out, r, true);
359                                System.err.println("!");
360                                out.close();
361                                r = null;
362                                System.gc();
363                        }
364                } catch (Throwable e) {
365                        System.err.println("Error: "+e.getMessage());
366                        throw e;
367                }
368        }
369
370        private void executeTest(String[] args) throws Throwable {
371                try {
372                        @SuppressWarnings("unchecked")
373                        List<String> lines = FileUtils.readLines(new File(args[1]), "UTF-8");
374                        String srcDir = lines.get(0);
375                        lines.remove(0);
376                        String dstDir = lines.get(0).trim();
377                        lines.remove(0);
378                        testRoundTrip(srcDir, dstDir, lines);
379                        TextFile.stringToFile("ok", Utilities.changeFileExt(args[1], ".out"));
380                } catch (Exception e) {
381                        TextFile.stringToFile(e.getMessage(), Utilities.changeFileExt(args[1], ".out"));                        
382                }
383        }
384        
385 
386}