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}