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}