001/* 002Copyright (c) 2011+, HL7, Inc 003All rights reserved. 004 005Redistribution and use in source and binary forms, with or without modification, 006are permitted provided that the following conditions are met: 007 008 * Redistributions of source code must retain the above copyright notice, this 009 list of conditions and the following disclaimer. 010 * Redistributions in binary form must reproduce the above copyright notice, 011 this list of conditions and the following disclaimer in the documentation 012 and/or other materials provided with the distribution. 013 * Neither the name of HL7 nor the names of its contributors may be used to 014 endorse or promote products derived from this software without specific 015 prior written permission. 016 017THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 018ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 019WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 020IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 021INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 022NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 023PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 024WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 025ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 026POSSIBILITY OF SUCH DAMAGE. 027 028 */ 029package org.hl7.fhir.r4.test.utils; 030 031/*- 032 * #%L 033 * org.hl7.fhir.r4 034 * %% 035 * Copyright (C) 2014 - 2019 Health Level 7 036 * %% 037 * Licensed under the Apache License, Version 2.0 (the "License"); 038 * you may not use this file except in compliance with the License. 039 * You may obtain a copy of the License at 040 * 041 * http://www.apache.org/licenses/LICENSE-2.0 042 * 043 * Unless required by applicable law or agreed to in writing, software 044 * distributed under the License is distributed on an "AS IS" BASIS, 045 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 046 * See the License for the specific language governing permissions and 047 * limitations under the License. 048 * #L% 049 */ 050 051 052import java.io.BufferedInputStream; 053import java.io.ByteArrayInputStream; 054import java.io.ByteArrayOutputStream; 055import java.io.File; 056import java.io.FileInputStream; 057import java.io.FileOutputStream; 058import java.io.IOException; 059import java.io.InputStream; 060import java.net.URL; 061import java.util.Collection; 062import java.util.HashMap; 063import java.util.List; 064import java.util.Map; 065import java.util.zip.ZipEntry; 066import java.util.zip.ZipInputStream; 067 068import org.apache.commons.io.FileUtils; 069import org.apache.commons.io.IOUtils; 070import org.apache.commons.lang3.NotImplementedException; 071import org.hl7.fhir.exceptions.FHIRException; 072import org.hl7.fhir.exceptions.FHIRFormatError; 073import org.hl7.fhir.r4.context.SimpleWorkerContext; 074import org.hl7.fhir.r4.formats.IParser.OutputStyle; 075import org.hl7.fhir.r4.formats.JsonParser; 076import org.hl7.fhir.r4.formats.RdfParser; 077import org.hl7.fhir.r4.formats.RdfParserBase; 078import org.hl7.fhir.r4.formats.XmlParser; 079import org.hl7.fhir.r4.model.Constants; 080import org.hl7.fhir.r4.model.Resource; 081import org.hl7.fhir.utilities.CSFile; 082import org.hl7.fhir.utilities.CSFileInputStream; 083import org.hl7.fhir.utilities.TextFile; 084import org.hl7.fhir.utilities.Utilities; 085import org.xmlpull.v1.XmlPullParser; 086import org.xmlpull.v1.XmlPullParserException; 087import org.xmlpull.v1.XmlPullParserFactory; 088 089public class ToolsHelper { 090 091 public static void main(String[] args) { 092 try { 093 ToolsHelper self = new ToolsHelper(); 094 if (args.length == 0) 095 throw new FHIRException("Missing Command Parameter. Valid Commands: round, json, version, fragments, snapshot-maker"); 096 if (args[0].equals("round")) 097 self.executeRoundTrip(args); 098 else if (args[0].equals("test")) 099 self.executeTest(args); 100 else if (args[0].equals("examples")) 101 self.executeExamples(args); 102 else if (args[0].equals("json")) 103 self.executeJson(args); 104 else if (args[0].equals("cxml")) 105 self.executeCanonicalXml(args); 106 else if (args[0].equals("version")) 107 self.executeVersion(args); 108 else if (args[0].equals("fragments")) 109 self.executeFragments(args); 110 else if (args[0].equals("snapshot-maker")) 111 self.generateSnapshots(args); 112 else 113 throw new FHIRException("Unknown command '"+args[0]+"'. Valid Commands: round, test, examples, json, cxml, version, fragments, snapshot-maker"); 114 } catch (Throwable e) { 115 try { 116 e.printStackTrace(); 117 TextFile.stringToFile(e.toString(), (args.length == 0 ? "tools" : args[0])+".err"); 118 } catch (Exception e1) { 119 e1.printStackTrace(); 120 } 121 } 122 } 123 124 private void executeExamples(String[] args) throws IOException { 125 try { 126 @SuppressWarnings("unchecked") 127 List<String> lines = FileUtils.readLines(new File(args[1]), "UTF-8"); 128 String srcDir = lines.get(0); 129 lines.remove(0); 130 processExamples(srcDir, lines); 131 TextFile.stringToFile("ok", Utilities.changeFileExt(args[1], ".out")); 132 } catch (Exception e) { 133 TextFile.stringToFile(e.getMessage(), Utilities.changeFileExt(args[1], ".out")); 134 } 135 } 136 137 private void generateSnapshots(String[] args) throws IOException, FHIRException { 138 if (args.length == 1) { 139 System.out.println("tools.jar snapshot-maker [source] -defn [definitions]"); 140 System.out.println(""); 141 System.out.println("Generates a snapshot from a differential. The nominated profile must have a single struture that has a differential"); 142 System.out.println(""); 143 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"); 144 System.out.println("definitions - filename for local copy of the validation.zip file"); 145 } 146 String address = args[1]; 147 String definitions = args[3]; 148 149 SimpleWorkerContext context = SimpleWorkerContext.fromDefinitions(getDefinitions(definitions)); 150 151 // if (address.startsWith("http:") || address.startsWith("http:")) { 152 // // this is on a restful interface 153 // String[] parts = address.split("\\/Profile\\/"); 154 // if (parts.length != 2) 155 // throw new FHIRException("Unable to understand address of profile"); 156 // StructureDefinition profile = context.fetchResource(StructureDefinition.class, parts[1]); 157 // ProfileUtilities utils = new ProfileUtilities(context); 158 // StructureDefinition base = utils.getProfile(profile, profile.getBase()); 159 // if (base == null) 160 // throw new FHIRException("Unable to resolve profile "+profile.getBase()); 161 // utils.generateSnapshot(base, profile, address, profile.getName(), null, null); 162 // // client.update(StructureDefinition.class, profile, parts[1]); 163 // } else { 164 throw new NotImplementedException("generating snapshots not done yet (address = "+address+")"); 165 // } 166 } 167 168 private Map<String, byte[]> getDefinitions(String definitions) throws IOException, FHIRException { 169 Map<String, byte[]> results = new HashMap<String, byte[]>(); 170 readDefinitions(results, loadDefinitions(definitions)); 171 return results; 172 } 173 174 private void readDefinitions(Map<String, byte[]> map, byte[] defn) throws IOException { 175 ZipInputStream zip = new ZipInputStream(new ByteArrayInputStream(defn)); 176 ZipEntry ze; 177 while ((ze = zip.getNextEntry()) != null) { 178 if (!ze.getName().endsWith(".zip") && !ze.getName().endsWith(".jar") ) { // skip saxon .zip 179 String name = ze.getName(); 180 InputStream in = zip; 181 ByteArrayOutputStream b = new ByteArrayOutputStream(); 182 int n; 183 byte[] buf = new byte[1024]; 184 while ((n = in.read(buf, 0, 1024)) > -1) { 185 b.write(buf, 0, n); 186 } 187 map.put(name, b.toByteArray()); 188 } 189 zip.closeEntry(); 190 } 191 zip.close(); 192 } 193 194 private byte[] loadDefinitions(String definitions) throws FHIRException, IOException { 195 byte[] defn; 196 // if (Utilities.noString(definitions)) { 197 // defn = loadFromUrl(MASTER_SOURCE); 198 // } else 199 if (definitions.startsWith("https:") || definitions.startsWith("http:")) { 200 defn = loadFromUrl(definitions); 201 } else if (new File(definitions).exists()) { 202 defn = loadFromFile(definitions); 203 } else 204 throw new FHIRException("Unable to find FHIR validation Pack (source = "+definitions+")"); 205 return defn; 206 } 207 208 private byte[] loadFromUrl(String src) throws IOException { 209 URL url = new URL(src); 210 byte[] str = IOUtils.toByteArray(url.openStream()); 211 return str; 212 } 213 214 private byte[] loadFromFile(String src) throws IOException { 215 FileInputStream in = new FileInputStream(src); 216 byte[] b = new byte[in.available()]; 217 in.read(b); 218 in.close(); 219 return b; 220 } 221 222 223 protected XmlPullParser loadXml(InputStream stream) throws XmlPullParserException, IOException { 224 BufferedInputStream input = new BufferedInputStream(stream); 225 XmlPullParserFactory factory = XmlPullParserFactory.newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null); 226 factory.setNamespaceAware(true); 227 XmlPullParser xpp = factory.newPullParser(); 228 xpp.setInput(input, "UTF-8"); 229 xpp.next(); 230 return xpp; 231 } 232 233 protected int nextNoWhitespace(XmlPullParser xpp) throws XmlPullParserException, IOException { 234 int eventType = xpp.getEventType(); 235 while (eventType == XmlPullParser.TEXT && xpp.isWhitespace()) 236 eventType = xpp.next(); 237 return eventType; 238 } 239 240 public void executeFragments(String[] args) throws IOException { 241 try { 242 File source = new CSFile(args[1]); 243 if (!source.exists()) 244 throw new FHIRException("Source File \""+source.getAbsolutePath()+"\" not found"); 245 XmlPullParser xpp = loadXml(new FileInputStream(source)); 246 nextNoWhitespace(xpp); 247 if (!xpp.getName().equals("tests")) 248 throw new FHIRFormatError("Unable to parse file - starts with "+xpp.getName()); 249 xpp.next(); 250 nextNoWhitespace(xpp); 251 StringBuilder s = new StringBuilder(); 252 s.append("<results>\r\n"); 253 int fail = 0; 254 while (xpp.getEventType() == XmlPullParser.START_TAG && xpp.getName().equals("test")) { 255 String id = xpp.getAttributeValue(null, "id"); 256 String type = xpp.getAttributeValue(null, "type"); 257 // test 258 xpp.next(); 259 nextNoWhitespace(xpp); 260 // pre 261 xpp.next(); 262 nextNoWhitespace(xpp); 263 XmlParser p = new XmlParser(); 264 try { 265 p.parseFragment(xpp, type); 266 s.append("<result id=\""+id+"\" outcome=\"ok\"/>\r\n"); 267 nextNoWhitespace(xpp); 268 } catch (Exception e) { 269 s.append("<result id=\""+id+"\" outcome=\"error\" msg=\""+Utilities.escapeXml(e.getMessage())+"\"/>\r\n"); 270 fail++; 271 } 272 while (xpp.getEventType() != XmlPullParser.END_TAG || !xpp.getName().equals("pre")) 273 xpp.next(); 274 xpp.next(); 275 nextNoWhitespace(xpp); 276 xpp.next(); 277 nextNoWhitespace(xpp); 278 } 279 s.append("</results>\r\n"); 280 281 TextFile.stringToFile(s.toString(), args[2]); 282 } catch (Exception e) { 283 e.printStackTrace(); 284 TextFile.stringToFile(e.getMessage(), args[2]); 285 } 286 } 287 288 public void executeRoundTrip(String[] args) throws IOException, FHIRException { 289 FileInputStream in; 290 File source = new CSFile(args[1]); 291 File dest = new CSFile(args[2]); 292 if (args.length >= 4) { 293 Utilities.copyFile(args[1], args[3]); 294 } 295 296 if (!source.exists()) 297 throw new FHIRException("Source File \""+source.getAbsolutePath()+"\" not found"); 298 in = new CSFileInputStream(source); 299 XmlParser p = new XmlParser(); 300 JsonParser parser = new JsonParser(); 301 JsonParser pj = parser; 302 Resource rf = p.parse(in); 303 ByteArrayOutputStream json = new ByteArrayOutputStream(); 304 parser.setOutputStyle(OutputStyle.PRETTY); 305 parser.compose(json, rf); 306 json.close(); 307 TextFile.stringToFile(new String(json.toByteArray()), Utilities.changeFileExt(dest.getAbsolutePath(), ".json")); 308 rf = pj.parse(new ByteArrayInputStream(json.toByteArray())); 309 FileOutputStream s = new FileOutputStream(dest); 310 new XmlParser().compose(s, rf, true); 311 s.close(); 312 } 313 314 public String executeJson(String[] args) throws IOException, FHIRException { 315 FileInputStream in; 316 File source = new CSFile(args[1]); 317 File dest = new CSFile(args[2]); 318 File destc = new CSFile(Utilities.changeFileExt(args[2], ".canonical.json")); 319 File destt = new CSFile(args[2]+".tmp"); 320 File destr = new CSFile(Utilities.changeFileExt(args[2], ".ttl")); 321 322 if (!source.exists()) 323 throw new FHIRException("Source File \""+source.getAbsolutePath()+"\" not found"); 324 in = new CSFileInputStream(source); 325 XmlParser p = new XmlParser(); 326 Resource rf = p.parse(in); 327 JsonParser json = new JsonParser(); 328 json.setOutputStyle(OutputStyle.PRETTY); 329 FileOutputStream s = new FileOutputStream(dest); 330 json.compose(s, rf); 331 s.close(); 332 json.setOutputStyle(OutputStyle.CANONICAL); 333 s = new FileOutputStream(destc); 334 json.compose(s, rf); 335 s.close(); 336 json.setSuppressXhtml("Snipped for Brevity"); 337 json.setOutputStyle(OutputStyle.PRETTY); 338 s = new FileOutputStream(destt); 339 json.compose(s, rf); 340 s.close(); 341 342 RdfParserBase rdf = new RdfParser(); 343 s = new FileOutputStream(destr); 344 rdf.compose(s, rf); 345 s.close(); 346 347 return TextFile.fileToString(destt.getAbsolutePath()); 348 } 349 350 public void executeCanonicalXml(String[] args) throws FHIRException, IOException { 351 FileInputStream in; 352 File source = new CSFile(args[1]); 353 File dest = new CSFile(args[2]); 354 355 if (!source.exists()) 356 throw new FHIRException("Source File \""+source.getAbsolutePath()+"\" not found"); 357 in = new CSFileInputStream(source); 358 XmlParser p = new XmlParser(); 359 Resource rf = p.parse(in); 360 XmlParser cxml = new XmlParser(); 361 cxml.setOutputStyle(OutputStyle.NORMAL); 362 cxml.compose(new FileOutputStream(dest), rf); 363 } 364 365 private void executeVersion(String[] args) throws IOException { 366 TextFile.stringToFile(org.hl7.fhir.r4.utils.Version.VERSION+":"+Constants.VERSION, args[1]); 367 } 368 369 public void processExamples(String rootDir, Collection<String> list) throws FHIRException { 370 for (String n : list) { 371 try { 372 String filename = rootDir + n + ".xml"; 373 // 1. produce canonical XML 374 CSFileInputStream source = new CSFileInputStream(filename); 375 FileOutputStream dest = new FileOutputStream(Utilities.changeFileExt(filename, ".canonical.xml")); 376 XmlParser p = new XmlParser(); 377 Resource r = p.parse(source); 378 XmlParser cxml = new XmlParser(); 379 cxml.setOutputStyle(OutputStyle.CANONICAL); 380 cxml.compose(dest, r); 381 382 // 2. produce JSON 383 source = new CSFileInputStream(filename); 384 dest = new FileOutputStream(Utilities.changeFileExt(filename, ".json")); 385 r = p.parse(source); 386 JsonParser json = new JsonParser(); 387 json.setOutputStyle(OutputStyle.PRETTY); 388 json.compose(dest, r); 389 json = new JsonParser(); 390 json.setOutputStyle(OutputStyle.CANONICAL); 391 dest = new FileOutputStream(Utilities.changeFileExt(filename, ".canonical.json")); 392 json.compose(dest, r); 393 394 // 2. produce JSON 395 dest = new FileOutputStream(Utilities.changeFileExt(filename, ".ttl")); 396 RdfParserBase rdf = new RdfParser(); 397 rdf.compose(dest, r); 398 } catch (Exception e) { 399 e.printStackTrace(); 400 throw new FHIRException("Error Processing "+n+".xml: "+e.getMessage(), e); 401 } 402 } 403 } 404 405 public void testRoundTrip(String rootDir, String tmpDir, Collection<String> names) throws Throwable { 406 try { 407 System.err.println("Round trip from "+rootDir+" to "+tmpDir+":"+Integer.toString(names.size())+" files"); 408 for (String n : names) { 409 System.err.print(" "+n); 410 String source = rootDir + n + ".xml"; 411 // String tmpJson = tmpDir + n + ".json"; 412 String tmp = tmpDir + n.replace(File.separator, "-") + ".tmp"; 413 String dest = tmpDir + n.replace(File.separator, "-") + ".java.xml"; 414 415 FileInputStream in = new FileInputStream(source); 416 XmlParser xp = new XmlParser(); 417 Resource r = xp.parse(in); 418 System.err.print("."); 419 JsonParser jp = new JsonParser(); 420 FileOutputStream out = new FileOutputStream(tmp); 421 jp.setOutputStyle(OutputStyle.PRETTY); 422 jp.compose(out, r); 423 out.close(); 424 r = null; 425 System.err.print("."); 426 427 in = new FileInputStream(tmp); 428 System.err.print(","); 429 r = jp.parse(in); 430 System.err.print("."); 431 out = new FileOutputStream(dest); 432 new XmlParser().compose(out, r, true); 433 System.err.println("!"); 434 out.close(); 435 r = null; 436 System.gc(); 437 } 438 } catch (Throwable e) { 439 System.err.println("Error: "+e.getMessage()); 440 throw e; 441 } 442 } 443 444 private void executeTest(String[] args) throws Throwable { 445 try { 446 @SuppressWarnings("unchecked") 447 List<String> lines = FileUtils.readLines(new File(args[1]), "UTF-8"); 448 String srcDir = lines.get(0); 449 lines.remove(0); 450 String dstDir = lines.get(0).trim(); 451 lines.remove(0); 452 testRoundTrip(srcDir, dstDir, lines); 453 TextFile.stringToFile("ok", Utilities.changeFileExt(args[1], ".out")); 454 } catch (Exception e) { 455 TextFile.stringToFile(e.getMessage(), Utilities.changeFileExt(args[1], ".out")); 456 } 457 } 458}