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}