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