001package org.hl7.fhir.convertors; 002 003import java.io.ByteArrayInputStream; 004import java.io.ByteArrayOutputStream; 005import java.io.File; 006import java.io.FileInputStream; 007import java.io.FileOutputStream; 008import java.io.IOException; 009import java.io.InputStream; 010import java.io.OutputStream; 011import java.util.ArrayList; 012import java.util.HashMap; 013import java.util.List; 014import java.util.Map; 015import java.util.UUID; 016import java.util.zip.ZipEntry; 017import java.util.zip.ZipInputStream; 018 019/* 020 Copyright (c) 2011+, HL7, Inc. 021 All rights reserved. 022 023 Redistribution and use in source and binary forms, with or without modification, 024 are permitted provided that the following conditions are met: 025 026 * Redistributions of source code must retain the above copyright notice, this 027 list of conditions and the following disclaimer. 028 * Redistributions in binary form must reproduce the above copyright notice, 029 this list of conditions and the following disclaimer in the documentation 030 and/or other materials provided with the distribution. 031 * Neither the name of HL7 nor the names of its contributors may be used to 032 endorse or promote products derived from this software without specific 033 prior written permission. 034 035 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 036 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 037 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 038 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 039 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 040 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 041 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 042 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 043 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 044 POSSIBILITY OF SUCH DAMAGE. 045 046 */ 047 048import org.hl7.fhir.convertors.loaders.loaderR3.R2ToR3Loader; 049import org.hl7.fhir.dstu3.context.SimpleWorkerContext; 050import org.hl7.fhir.dstu3.elementmodel.Manager; 051import org.hl7.fhir.dstu3.elementmodel.Manager.FhirFormat; 052import org.hl7.fhir.dstu3.formats.FormatUtilities; 053import org.hl7.fhir.dstu3.formats.IParser.OutputStyle; 054import org.hl7.fhir.dstu3.formats.JsonParser; 055import org.hl7.fhir.dstu3.model.Base; 056import org.hl7.fhir.dstu3.model.Coding; 057import org.hl7.fhir.dstu3.model.ExpansionProfile; 058import org.hl7.fhir.dstu3.model.MetadataResource; 059import org.hl7.fhir.dstu3.model.PractitionerRole; 060import org.hl7.fhir.dstu3.model.Resource; 061import org.hl7.fhir.dstu3.model.ResourceFactory; 062import org.hl7.fhir.dstu3.model.StructureDefinition; 063import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionKind; 064import org.hl7.fhir.dstu3.model.StructureMap; 065import org.hl7.fhir.dstu3.model.UriType; 066import org.hl7.fhir.dstu3.utils.StructureMapUtilities; 067import org.hl7.fhir.dstu3.utils.StructureMapUtilities.ITransformerServices; 068import org.hl7.fhir.exceptions.FHIRException; 069import org.hl7.fhir.utilities.TextFile; 070 071/** 072 * This class manages conversion from R2 to R3 and vice versa 073 * <p> 074 * To use this class, do the following: 075 * <p> 076 * - provide a stream or path (file or URL) that points to R2 definitions (from http://hl7.org/fhir/DSTU2/downloads.html) 077 * - provide a stream or a path (file or URL) that points to the R3 definitions (from http://hl7.org/fhir/STU3/downloads.html) 078 * - provide a stream or a path (file or URL) that points to R2/R3 map files (from http://hl7.org/fhir/r2r3maps.zip) 079 * <p> 080 * - call convert() (can call this more than once, but not multithread safe) 081 * 082 * @author Grahame Grieve 083 */ 084public class R2R3ConversionManager implements ITransformerServices { 085 086 private final Map<String, StructureMap> library = new HashMap<String, StructureMap>(); 087 private final List<Resource> extras = new ArrayList<Resource>(); 088 private SimpleWorkerContext contextR2; 089 private SimpleWorkerContext contextR3; 090 private boolean needPrepare = false; 091 private StructureMapUtilities smu3; 092 private StructureMapUtilities smu2; 093 private OutputStyle style = OutputStyle.PRETTY; 094 095 public static void main(String[] args) throws IOException, FHIRException { 096 if (args.length == 0 || !hasParam(args, "-d2") || !hasParam(args, "-d3") || !hasParam(args, "-maps") || !hasParam(args, "-src") || !hasParam(args, "-dest") || (!hasParam(args, "-r2") && !hasParam(args, "-r3"))) { 097 System.out.println("R2 <--> R3 Convertor"); 098 System.out.println("===================="); 099 System.out.println(); 100 System.out.println("parameters: -d2 [r2 definitions] -d3 [r3 definitions] -maps [map source] -src [source] -dest [dest] -r2/3 - fmt [format]"); 101 System.out.println(); 102 System.out.println("d2: definitions from http://hl7.org/fhir/DSTU2/downloads.html"); 103 System.out.println("d3: definitions from http://hl7.org/fhir/STU3/downloads.html"); 104 System.out.println("maps: R2/R3 maps from http://hl7.org/fhir/r2r3maps.zip"); 105 System.out.println("src: filename for source to convert"); 106 System.out.println("dest: filename for destination of conversion"); 107 System.out.println("-r2: source is r2, convert to r3"); 108 System.out.println("-r3: source is r3, convert to r2"); 109 System.out.println("-fmt: xml | json (xml is default)"); 110 } else { 111 R2R3ConversionManager self = new R2R3ConversionManager(); 112 self.setR2Definitions(getNamedParam(args, "-d2")); 113 self.setR3Definitions(getNamedParam(args, "-d3")); 114 self.setMappingLibrary(getNamedParam(args, "-maps")); 115 FhirFormat fmt = hasParam(args, "-fmt") ? getNamedParam(args, "-fmt").equalsIgnoreCase("json") ? FhirFormat.JSON : FhirFormat.XML : FhirFormat.XML; 116 InputStream src = new FileInputStream(getNamedParam(args, "-src")); 117 OutputStream dst = new FileOutputStream(getNamedParam(args, "-dest")); 118 self.convert(src, dst, hasParam(args, "-r2"), fmt); 119 } 120 } 121 122 private static boolean hasParam(String[] args, String param) { 123 for (String a : args) 124 if (a.equals(param)) 125 return true; 126 return false; 127 } 128 129 private static String getNamedParam(String[] args, String param) { 130 boolean found = false; 131 for (String a : args) { 132 if (found) 133 return a; 134 if (a.equals(param)) { 135 found = true; 136 } 137 } 138 return null; 139 } 140 141 public OutputStyle getStyle() { 142 return style; 143 } 144 145 public void setStyle(OutputStyle style) { 146 this.style = style; 147 } 148 149 public List<Resource> getExtras() { 150 return extras; 151 } 152 153 // set up ------------------------------------------------------------------ 154 public void setR2Definitions(InputStream stream) throws IOException, FHIRException { 155 needPrepare = true; 156 R2ToR3Loader ldr = new R2ToR3Loader(); 157 ldr.setPatchUrls(true).setKillPrimitives(true); 158 Map<String, InputStream> files = readInputStream(stream); 159 contextR2 = new SimpleWorkerContext(); 160 contextR2.setAllowLoadingDuplicates(true); 161 contextR2.loadFromFile(files.get("profiles-types.xml"), "profiles-types.xml", ldr); 162 contextR2.loadFromFile(files.get("profiles-resources.xml"), "profiles-resources.xml", ldr); 163 contextR2.loadFromFile(files.get("valuesets.xml"), "valuesets.xml", ldr); 164 } 165 166 public void setR2Definitions(String source) throws IOException, FHIRException { 167 File f = new File(source); 168 if (f.exists()) 169 setR2Definitions(new FileInputStream(f)); 170 else 171 setR2Definitions(fetch(source)); 172 } 173 174 public void setR3Definitions(InputStream stream) throws IOException, FHIRException { 175 needPrepare = true; 176 Map<String, InputStream> files = readInputStream(stream); 177 contextR3 = new SimpleWorkerContext(); 178 contextR2.setAllowLoadingDuplicates(true); 179 contextR3.loadFromFile(files.get("profiles-types.xml"), "profiles-types.xml", null); 180 contextR3.loadFromFile(files.get("profiles-resources.xml"), "profiles-resources.xml", null); 181 contextR3.loadFromFile(files.get("extension-definitions.xml"), "extension-definitions.xml", null); 182 contextR3.loadFromFile(files.get("valuesets.xml"), "valuesets.xml", null); 183 contextR3.setCanRunWithoutTerminology(true); 184 } 185 186 public void setR3Definitions(String source) throws IOException, FHIRException { 187 File f = new File(source); 188 if (f.exists()) 189 setR3Definitions(new FileInputStream(f)); 190 else 191 setR3Definitions(fetch(source)); 192 } 193 194 public void setMappingLibrary(InputStream stream) throws IOException, FHIRException { 195 needPrepare = true; 196 Map<String, InputStream> files = readInputStream(stream); 197 for (InputStream s : files.values()) { 198 StructureMap sm = new StructureMapUtilities(contextR3).parse(TextFile.streamToString(s)); 199 library.put(sm.getUrl(), sm); 200 } 201 } 202 203 public void setMappingLibrary(String source) throws IOException, FHIRException { 204 File f = new File(source); 205 if (f.exists()) 206 setMappingLibrary(new FileInputStream(f)); 207 else 208 setMappingLibrary(fetch(source)); 209 } 210 211 // support 212 private InputStream fetch(String source) { 213 throw new Error("not done yet"); 214 } 215 216 private Map<String, InputStream> readInputStream(InputStream stream) throws IOException { 217 Map<String, InputStream> res = new HashMap<String, InputStream>(); 218 ZipInputStream zip = new ZipInputStream(stream); 219 ZipEntry ze = null; 220 while ((ze = zip.getNextEntry()) != null) { 221 String n = ze.getName(); 222 ByteArrayOutputStream bs = new ByteArrayOutputStream(); 223 for (int c = zip.read(); c != -1; c = zip.read()) { 224 bs.write(c); 225 } 226 bs.close(); 227 res.put(n, new ByteArrayInputStream(bs.toByteArray())); 228 zip.closeEntry(); 229 } 230 zip.close(); 231 return res; 232 } 233 234 private void prepare() throws FHIRException { 235 if (contextR2 == null) 236 throw new FHIRException("No R2 definitions provided"); 237 if (contextR3 == null) 238 throw new FHIRException("No R3 definitions provided"); 239 if (library == null) 240 throw new FHIRException("No R2/R# conversion maps provided"); 241 242 if (needPrepare) { 243 for (StructureDefinition sd : contextR2.allStructures()) { 244 StructureDefinition sdn = sd.copy(); 245 sdn.getExtension().clear(); 246 contextR3.seeResource(sdn.getUrl(), sdn); 247 } 248 249 for (StructureDefinition sd : contextR3.allStructures()) { 250 if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { 251 contextR2.seeResource(sd.getUrl(), sd); 252 StructureDefinition sdn = sd.copy(); 253 sdn.setUrl(sdn.getUrl().replace("http://hl7.org/fhir/", "http://hl7.org/fhir/DSTU2/")); 254 sdn.addExtension().setUrl("http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace").setValue(new UriType("http://hl7.org/fhir")); 255 contextR2.seeResource(sdn.getUrl(), sdn); 256 contextR3.seeResource(sdn.getUrl(), sdn); 257 } 258 } 259 260 contextR2.setExpansionProfile(new ExpansionProfile().setUrl("urn:uuid:" + UUID.randomUUID().toString().toLowerCase())); 261 contextR3.setExpansionProfile(new ExpansionProfile().setUrl("urn:uuid:" + UUID.randomUUID().toString().toLowerCase())); 262 263 smu3 = new StructureMapUtilities(contextR3, library, this); 264 smu2 = new StructureMapUtilities(contextR2, library, this); 265 266 needPrepare = false; 267 } 268 } 269 270 // execution 271 public byte[] convert(byte[] source, boolean r2ToR3, FhirFormat format) throws FHIRException, IOException { 272 prepare(); 273 ByteArrayOutputStream bs = new ByteArrayOutputStream(); 274 if (r2ToR3) 275 convertToR3(new ByteArrayInputStream(source), bs, format); 276 else 277 convertToR2(new ByteArrayInputStream(source), bs, format); 278 bs.close(); 279 return bs.toByteArray(); 280 } 281 282 public void convert(InputStream source, OutputStream dest, boolean r2ToR3, FhirFormat format) throws FHIRException, IOException { 283 prepare(); 284 if (r2ToR3) 285 convertToR3(source, dest, format); 286 else 287 convertToR2(source, dest, format); 288 } 289 290 public org.hl7.fhir.dstu2.model.Resource convert(org.hl7.fhir.dstu3.model.Resource source) throws IOException, FHIRException { 291 ByteArrayOutputStream bs = new ByteArrayOutputStream(); 292 new JsonParser().compose(bs, source); 293 bs.close(); 294 return new org.hl7.fhir.dstu2.formats.JsonParser().parse(convert(bs.toByteArray(), false, FhirFormat.JSON)); 295 } 296 297 public org.hl7.fhir.dstu3.model.Resource convert(org.hl7.fhir.dstu2.model.Resource source) throws IOException, FHIRException { 298 ByteArrayOutputStream bs = new ByteArrayOutputStream(); 299 new org.hl7.fhir.dstu2.formats.JsonParser().compose(bs, source); 300 bs.close(); 301 return new JsonParser().parse(convert(bs.toByteArray(), false, FhirFormat.JSON)); 302 } 303 304 private void convertToR3(InputStream source, OutputStream dest, FhirFormat format) throws FHIRException, IOException { 305 org.hl7.fhir.dstu3.elementmodel.Element r2 = new org.hl7.fhir.dstu3.elementmodel.XmlParser(contextR2).parse(source); 306 StructureMap map = library.get("http://hl7.org/fhir/StructureMap/" + r2.fhirType() + "2to3"); 307 if (map == null) 308 throw new FHIRException("No Map Found from R2 to R3 for " + r2.fhirType()); 309 String tn = smu3.getTargetType(map).getType(); 310 Resource r3 = ResourceFactory.createResource(tn); 311 smu3.transform(new TransformContextR2R3(contextR3, r2.getChildValue("id")), r2, map, r3); 312 FormatUtilities.makeParser(format).setOutputStyle(style).compose(dest, r3); 313 } 314 315 private void convertToR2(InputStream source, OutputStream dest, FhirFormat format) throws FHIRException, IOException { 316 org.hl7.fhir.dstu3.elementmodel.Element r3 = new org.hl7.fhir.dstu3.elementmodel.XmlParser(contextR3).parse(source); 317 StructureMap map = library.get("??"); 318 String tn = smu3.getTargetType(map).getType(); 319 StructureDefinition sd = smu2.getTargetType(map); 320 org.hl7.fhir.dstu3.elementmodel.Element r2 = Manager.build(contextR2, sd); 321 smu2.transform(contextR2, r3, map, r2); 322 org.hl7.fhir.dstu3.elementmodel.Manager.compose(contextR2, r2, dest, format, style, null); 323 } 324 325 @Override 326 public void log(String message) { 327// System.out.println(message); 328 } 329 330 @Override 331 public Base createType(Object appInfo, String name) throws FHIRException { 332 SimpleWorkerContext context = ((TransformContextR2R3) appInfo).getContext(); 333 if (context == contextR2) { 334 StructureDefinition sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/DSTU2/StructureDefinition/" + name); 335 if (sd == null) 336 throw new FHIRException("Type not found: '" + name + "'"); 337 return Manager.build(context, sd); 338 } else 339 return ResourceFactory.createResourceOrType(name); 340 } 341 342 @Override 343 public Base createResource(Object appInfo, Base res) { 344 if (res instanceof Resource && (res.fhirType().equals("CodeSystem") || res.fhirType().equals("CareTeam")) || res.fhirType().equals("PractitionerRole")) { 345 Resource r = (Resource) res; 346 extras.add(r); 347 r.setId(((TransformContextR2R3) appInfo).getId() + "-" + extras.size()); //todo: get this into appinfo 348 } 349 return res; 350 } 351 352 @Override 353 public Coding translate(Object appInfo, Coding source, String conceptMapUrl) throws FHIRException { 354 throw new Error("translate not done yet"); 355 } 356 357 @Override 358 public Base resolveReference(Object appContext, String url) { 359 for (Resource r : extras) { 360 if (r instanceof MetadataResource) { 361 MetadataResource mr = (MetadataResource) r; 362 if (url.equals(mr.getUrl())) 363 return mr; 364 } 365 if (url.equals(r.fhirType() + "/" + r.getId())) 366 return r; 367 } 368 369 return null; 370 } 371 372 @Override 373 public List<Base> performSearch(Object appContext, String url) { 374 List<Base> results = new ArrayList<Base>(); 375 String[] parts = url.split("\\?"); 376 if (parts.length == 2 && parts[0].substring(1).equals("PractitionerRole")) { 377 String[] vals = parts[1].split("\\="); 378 if (vals.length == 2 && vals[0].equals("practitioner")) 379 for (Resource r : extras) { 380 if (r instanceof PractitionerRole && ((PractitionerRole) r).getPractitioner().getReference().equals("Practitioner/" + vals[1])) { 381 results.add(r); 382 } 383 } 384 } 385 return results; 386 } 387}