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.TextFile; 063import org.hl7.fhir.utilities.Utilities; 064import org.hl7.fhir.utilities.filesystem.CSFile; 065import org.hl7.fhir.utilities.filesystem.CSFileInputStream; 066import org.hl7.fhir.utilities.filesystem.ManagedFileAccess; 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(ManagedFileAccess.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 protected XmlPullParser loadXml(InputStream stream) throws XmlPullParserException, IOException { 151 BufferedInputStream input = new BufferedInputStream(stream); 152 XmlPullParserFactory factory = XmlPullParserFactory.newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null); 153 factory.setNamespaceAware(true); 154 XmlPullParser xpp = factory.newPullParser(); 155 xpp.setInput(input, "UTF-8"); 156 xpp.next(); 157 return xpp; 158 } 159 160 protected int nextNoWhitespace(XmlPullParser xpp) throws XmlPullParserException, IOException { 161 int eventType = xpp.getEventType(); 162 while (eventType == XmlPullParser.TEXT && xpp.isWhitespace()) 163 eventType = xpp.next(); 164 return eventType; 165 } 166 167 public void executeFragments(String[] args) throws IOException { 168 try { 169 File source = ManagedFileAccess.csfile(args[1]); 170 if (!source.exists()) 171 throw new FHIRException("Source File \""+source.getAbsolutePath()+"\" not found"); 172 XmlPullParser xpp = loadXml(ManagedFileAccess.inStream(source)); 173 nextNoWhitespace(xpp); 174 if (!xpp.getName().equals("tests")) 175 throw new FHIRFormatError("Unable to parse file - starts with "+xpp.getName()); 176 xpp.next(); 177 nextNoWhitespace(xpp); 178 StringBuilder s = new StringBuilder(); 179 s.append("<results>\r\n"); 180 int fail = 0; 181 while (xpp.getEventType() == XmlPullParser.START_TAG && xpp.getName().equals("test")) { 182 String id = xpp.getAttributeValue(null, "id"); 183 String type = xpp.getAttributeValue(null, "type"); 184 // test 185 xpp.next(); 186 nextNoWhitespace(xpp); 187 // pre 188 xpp.next(); 189 nextNoWhitespace(xpp); 190 XmlParser p = new XmlParser(); 191 try { 192 p.parseFragment(xpp, type); 193 s.append("<result id=\""+id+"\" outcome=\"ok\"/>\r\n"); 194 nextNoWhitespace(xpp); 195 } catch (Exception e) { 196 s.append("<result id=\""+id+"\" outcome=\"error\" msg=\""+Utilities.escapeXml(e.getMessage())+"\"/>\r\n"); 197 fail++; 198 } 199 while (xpp.getEventType() != XmlPullParser.END_TAG || !xpp.getName().equals("pre")) 200 xpp.next(); 201 xpp.next(); 202 nextNoWhitespace(xpp); 203 xpp.next(); 204 nextNoWhitespace(xpp); 205 } 206 s.append("</results>\r\n"); 207 208 TextFile.stringToFile(s.toString(), args[2]); 209 } catch (Exception e) { 210 e.printStackTrace(); 211 TextFile.stringToFile(e.getMessage(), args[2]); 212 } 213 } 214 215 public void executeRoundTrip(String[] args) throws IOException, FHIRException { 216 FileInputStream in; 217 File source = ManagedFileAccess.csfile(args[1]); 218 File dest = ManagedFileAccess.csfile(args[2]); 219 if (args.length >= 4) { 220 Utilities.copyFile(args[1], args[3]); 221 } 222 223 if (!source.exists()) 224 throw new FHIRException("Source File \""+source.getAbsolutePath()+"\" not found"); 225 in = new CSFileInputStream(source); 226 XmlParser p = new XmlParser(); 227 JsonParser parser = new JsonParser(); 228 JsonParser pj = parser; 229 Resource rf = p.parse(in); 230 ByteArrayOutputStream json = new ByteArrayOutputStream(); 231 parser.setOutputStyle(OutputStyle.PRETTY); 232 parser.compose(json, rf); 233 json.close(); 234 TextFile.stringToFile(new String(json.toByteArray()), Utilities.changeFileExt(dest.getAbsolutePath(), ".json")); 235 rf = pj.parse(new ByteArrayInputStream(json.toByteArray())); 236 FileOutputStream s = ManagedFileAccess.outStream(dest); 237 new XmlParser().compose(s, rf, true); 238 s.close(); 239 } 240 241 public String executeJson(String[] args) throws IOException, FHIRException { 242 FileInputStream in; 243 File source = ManagedFileAccess.csfile(args[1]); 244 File dest = ManagedFileAccess.csfile(args[2]); 245 File destc = ManagedFileAccess.csfile(Utilities.changeFileExt(args[2], ".canonical.json")); 246 File destt = ManagedFileAccess.csfile(args[2]+".tmp"); 247 File destr = ManagedFileAccess.csfile(Utilities.changeFileExt(args[2], ".ttl")); 248 249 if (!source.exists()) 250 throw new FHIRException("Source File \""+source.getAbsolutePath()+"\" not found"); 251 in = new CSFileInputStream(source); 252 XmlParser p = new XmlParser(); 253 Resource rf = p.parse(in); 254 JsonParser json = new JsonParser(); 255 json.setOutputStyle(OutputStyle.PRETTY); 256 FileOutputStream s = ManagedFileAccess.outStream(dest); 257 json.compose(s, rf); 258 s.close(); 259 json.setOutputStyle(OutputStyle.CANONICAL); 260 s = ManagedFileAccess.outStream(destc); 261 json.compose(s, rf); 262 s.close(); 263 json.setSuppressXhtml("Snipped for Brevity"); 264 json.setOutputStyle(OutputStyle.PRETTY); 265 s = ManagedFileAccess.outStream(destt); 266 json.compose(s, rf); 267 s.close(); 268 269 RdfParserBase rdf = new RdfParser(); 270 s = ManagedFileAccess.outStream(destr); 271 rdf.compose(s, rf); 272 s.close(); 273 274 return TextFile.fileToString(destt.getAbsolutePath()); 275 } 276 277 public void executeCanonicalXml(String[] args) throws FHIRException, IOException { 278 FileInputStream in; 279 File source = ManagedFileAccess.csfile(args[1]); 280 File dest = ManagedFileAccess.csfile(args[2]); 281 282 if (!source.exists()) 283 throw new FHIRException("Source File \""+source.getAbsolutePath()+"\" not found"); 284 in = new CSFileInputStream(source); 285 XmlParser p = new XmlParser(); 286 Resource rf = p.parse(in); 287 XmlParser cxml = new XmlParser(); 288 cxml.setOutputStyle(OutputStyle.NORMAL); 289 cxml.compose(ManagedFileAccess.outStream(dest), rf); 290 } 291 292 private void executeVersion(String[] args) throws IOException { 293 TextFile.stringToFile(org.hl7.fhir.r5.utils.Version.VERSION+":"+Constants.VERSION, args[1]); 294 } 295 296 public void processExamples(String rootDir, Collection<String> list) throws FHIRException { 297 for (String n : list) { 298 try { 299 String filename = rootDir + n + ".xml"; 300 // 1. produce canonical XML 301 CSFileInputStream source = new CSFileInputStream(filename); 302 FileOutputStream dest = ManagedFileAccess.outStream(Utilities.changeFileExt(filename, ".canonical.xml")); 303 XmlParser p = new XmlParser(); 304 Resource r = p.parse(source); 305 XmlParser cxml = new XmlParser(); 306 cxml.setOutputStyle(OutputStyle.CANONICAL); 307 cxml.compose(dest, r); 308 309 // 2. produce JSON 310 source = new CSFileInputStream(filename); 311 dest = ManagedFileAccess.outStream(Utilities.changeFileExt(filename, ".json")); 312 r = p.parse(source); 313 JsonParser json = new JsonParser(); 314 json.setOutputStyle(OutputStyle.PRETTY); 315 json.compose(dest, r); 316 json = new JsonParser(); 317 json.setOutputStyle(OutputStyle.CANONICAL); 318 dest = ManagedFileAccess.outStream(Utilities.changeFileExt(filename, ".canonical.json")); 319 json.compose(dest, r); 320 321 // 2. produce JSON 322 dest = ManagedFileAccess.outStream(Utilities.changeFileExt(filename, ".ttl")); 323 RdfParserBase rdf = new RdfParser(); 324 rdf.compose(dest, r); 325 } catch (Exception e) { 326 e.printStackTrace(); 327 throw new FHIRException("Error Processing "+n+".xml: "+e.getMessage(), e); 328 } 329 } 330 } 331 332 public void testRoundTrip(String rootDir, String tmpDir, Collection<String> names) throws Throwable { 333 try { 334 System.err.println("Round trip from "+rootDir+" to "+tmpDir+":"+Integer.toString(names.size())+" files"); 335 for (String n : names) { 336 System.err.print(" "+n); 337 String source = rootDir + n + ".xml"; 338 // String tmpJson = tmpDir + n + ".json"; 339 String tmp = tmpDir + n.replace(File.separator, "-") + ".tmp"; 340 String dest = tmpDir + n.replace(File.separator, "-") + ".java.xml"; 341 342 FileInputStream in = ManagedFileAccess.inStream(source); 343 XmlParser xp = new XmlParser(); 344 Resource r = xp.parse(in); 345 System.err.print("."); 346 JsonParser jp = new JsonParser(); 347 FileOutputStream out = ManagedFileAccess.outStream(tmp); 348 jp.setOutputStyle(OutputStyle.PRETTY); 349 jp.compose(out, r); 350 out.close(); 351 r = null; 352 System.err.print("."); 353 354 in = ManagedFileAccess.inStream(tmp); 355 System.err.print(","); 356 r = jp.parse(in); 357 System.err.print("."); 358 out = ManagedFileAccess.outStream(dest); 359 new XmlParser().compose(out, r, true); 360 System.err.println("!"); 361 out.close(); 362 r = null; 363 System.gc(); 364 } 365 } catch (Throwable e) { 366 System.err.println("Error: "+e.getMessage()); 367 throw e; 368 } 369 } 370 371 private void executeTest(String[] args) throws Throwable { 372 try { 373 @SuppressWarnings("unchecked") 374 List<String> lines = FileUtils.readLines(ManagedFileAccess.file(args[1]), "UTF-8"); 375 String srcDir = lines.get(0); 376 lines.remove(0); 377 String dstDir = lines.get(0).trim(); 378 lines.remove(0); 379 testRoundTrip(srcDir, dstDir, lines); 380 TextFile.stringToFile("ok", Utilities.changeFileExt(args[1], ".out")); 381 } catch (Exception e) { 382 TextFile.stringToFile(e.getMessage(), Utilities.changeFileExt(args[1], ".out")); 383 } 384 } 385 386 387}