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