001package org.hl7.fhir.convertors; 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 org.hl7.fhir.convertors.loaders.loaderR3.R2ToR3Loader; 033import org.hl7.fhir.dstu3.context.SimpleWorkerContext; 034import org.hl7.fhir.dstu3.elementmodel.Manager; 035import org.hl7.fhir.dstu3.elementmodel.Manager.FhirFormat; 036import org.hl7.fhir.dstu3.formats.FormatUtilities; 037import org.hl7.fhir.dstu3.formats.IParser.OutputStyle; 038import org.hl7.fhir.dstu3.formats.JsonParser; 039import org.hl7.fhir.dstu3.model.*; 040import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionKind; 041import org.hl7.fhir.dstu3.utils.StructureMapUtilities; 042import org.hl7.fhir.dstu3.utils.StructureMapUtilities.ITransformerServices; 043import org.hl7.fhir.exceptions.FHIRException; 044import org.hl7.fhir.utilities.TextFile; 045 046import java.io.*; 047import java.util.*; 048import java.util.zip.ZipEntry; 049import java.util.zip.ZipInputStream; 050 051/** 052 * This class manages conversion from R2 to R3 and vice versa 053 * <p> 054 * To use this class, do the following: 055 * <p> 056 * - provide a stream or path (file or URL) that points to R2 definitions (from http://hl7.org/fhir/DSTU2/downloads.html) 057 * - provide a stream or a path (file or URL) that points to the R3 definitions (from http://hl7.org/fhir/STU3/downloads.html) 058 * - provide a stream or a path (file or URL) that points to R2/R3 map files (from http://hl7.org/fhir/r2r3maps.zip) 059 * <p> 060 * - call convert() (can call this more than once, but not multithread safe) 061 * 062 * @author Grahame Grieve 063 */ 064public class R2R3ConversionManager implements ITransformerServices { 065 066 private final Map<String, StructureMap> library = new HashMap<String, StructureMap>(); 067 private final List<Resource> extras = new ArrayList<Resource>(); 068 private SimpleWorkerContext contextR2; 069 private SimpleWorkerContext contextR3; 070 private boolean needPrepare = false; 071 private StructureMapUtilities smu3; 072 private StructureMapUtilities smu2; 073 private OutputStyle style = OutputStyle.PRETTY; 074 075 public static void main(String[] args) throws IOException, FHIRException { 076 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"))) { 077 System.out.println("R2 <--> R3 Convertor"); 078 System.out.println("===================="); 079 System.out.println(); 080 System.out.println("parameters: -d2 [r2 definitions] -d3 [r3 definitions] -maps [map source] -src [source] -dest [dest] -r2/3 - fmt [format]"); 081 System.out.println(); 082 System.out.println("d2: definitions from http://hl7.org/fhir/DSTU2/downloads.html"); 083 System.out.println("d3: definitions from http://hl7.org/fhir/STU3/downloads.html"); 084 System.out.println("maps: R2/R3 maps from http://hl7.org/fhir/r2r3maps.zip"); 085 System.out.println("src: filename for source to convert"); 086 System.out.println("dest: filename for destination of conversion"); 087 System.out.println("-r2: source is r2, convert to r3"); 088 System.out.println("-r3: source is r3, convert to r2"); 089 System.out.println("-fmt: xml | json (xml is default)"); 090 } else { 091 R2R3ConversionManager self = new R2R3ConversionManager(); 092 self.setR2Definitions(getNamedParam(args, "-d2")); 093 self.setR3Definitions(getNamedParam(args, "-d3")); 094 self.setMappingLibrary(getNamedParam(args, "-maps")); 095 FhirFormat fmt = hasParam(args, "-fmt") ? getNamedParam(args, "-fmt").equalsIgnoreCase("json") ? FhirFormat.JSON : FhirFormat.XML : FhirFormat.XML; 096 InputStream src = new FileInputStream(getNamedParam(args, "-src")); 097 OutputStream dst = new FileOutputStream(getNamedParam(args, "-dest")); 098 self.convert(src, dst, hasParam(args, "-r2"), fmt); 099 } 100 } 101 102 private static boolean hasParam(String[] args, String param) { 103 for (String a : args) 104 if (a.equals(param)) 105 return true; 106 return false; 107 } 108 109 private static String getNamedParam(String[] args, String param) { 110 boolean found = false; 111 for (String a : args) { 112 if (found) 113 return a; 114 if (a.equals(param)) { 115 found = true; 116 } 117 } 118 return null; 119 } 120 121 public OutputStyle getStyle() { 122 return style; 123 } 124 125 public void setStyle(OutputStyle style) { 126 this.style = style; 127 } 128 129 public List<Resource> getExtras() { 130 return extras; 131 } 132 133 // set up ------------------------------------------------------------------ 134 public void setR2Definitions(InputStream stream) throws IOException, FHIRException { 135 needPrepare = true; 136 R2ToR3Loader ldr = new R2ToR3Loader(); 137 ldr.setPatchUrls(true).setKillPrimitives(true); 138 Map<String, InputStream> files = readInputStream(stream); 139 contextR2 = new SimpleWorkerContext(); 140 contextR2.setAllowLoadingDuplicates(true); 141 contextR2.loadFromFile(files.get("profiles-types.xml"), "profiles-types.xml", ldr); 142 contextR2.loadFromFile(files.get("profiles-resources.xml"), "profiles-resources.xml", ldr); 143 contextR2.loadFromFile(files.get("valuesets.xml"), "valuesets.xml", ldr); 144 } 145 146 public void setR2Definitions(String source) throws IOException, FHIRException { 147 File f = new File(source); 148 if (f.exists()) 149 setR2Definitions(new FileInputStream(f)); 150 else 151 setR2Definitions(fetch(source)); 152 } 153 154 public void setR3Definitions(InputStream stream) throws IOException, FHIRException { 155 needPrepare = true; 156 Map<String, InputStream> files = readInputStream(stream); 157 contextR3 = new SimpleWorkerContext(); 158 contextR2.setAllowLoadingDuplicates(true); 159 contextR3.loadFromFile(files.get("profiles-types.xml"), "profiles-types.xml", null); 160 contextR3.loadFromFile(files.get("profiles-resources.xml"), "profiles-resources.xml", null); 161 contextR3.loadFromFile(files.get("extension-definitions.xml"), "extension-definitions.xml", null); 162 contextR3.loadFromFile(files.get("valuesets.xml"), "valuesets.xml", null); 163 contextR3.setCanRunWithoutTerminology(true); 164 } 165 166 public void setR3Definitions(String source) throws IOException, FHIRException { 167 File f = new File(source); 168 if (f.exists()) 169 setR3Definitions(new FileInputStream(f)); 170 else 171 setR3Definitions(fetch(source)); 172 } 173 174 public void setMappingLibrary(InputStream stream) throws IOException, FHIRException { 175 needPrepare = true; 176 Map<String, InputStream> files = readInputStream(stream); 177 for (InputStream s : files.values()) { 178 StructureMap sm = new StructureMapUtilities(contextR3).parse(TextFile.streamToString(s)); 179 library.put(sm.getUrl(), sm); 180 } 181 } 182 183 public void setMappingLibrary(String source) throws IOException, FHIRException { 184 File f = new File(source); 185 if (f.exists()) 186 setMappingLibrary(new FileInputStream(f)); 187 else 188 setMappingLibrary(fetch(source)); 189 } 190 191 // support 192 private InputStream fetch(String source) { 193 throw new Error("not done yet"); 194 } 195 196 private Map<String, InputStream> readInputStream(InputStream stream) throws IOException { 197 Map<String, InputStream> res = new HashMap<String, InputStream>(); 198 ZipInputStream zip = new ZipInputStream(stream); 199 ZipEntry ze = null; 200 while ((ze = zip.getNextEntry()) != null) { 201 String n = ze.getName(); 202 ByteArrayOutputStream bs = new ByteArrayOutputStream(); 203 for (int c = zip.read(); c != -1; c = zip.read()) { 204 bs.write(c); 205 } 206 bs.close(); 207 res.put(n, new ByteArrayInputStream(bs.toByteArray())); 208 zip.closeEntry(); 209 } 210 zip.close(); 211 return res; 212 } 213 214 private void prepare() throws FHIRException { 215 if (contextR2 == null) 216 throw new FHIRException("No R2 definitions provided"); 217 if (contextR3 == null) 218 throw new FHIRException("No R3 definitions provided"); 219 if (library == null) 220 throw new FHIRException("No R2/R# conversion maps provided"); 221 222 if (needPrepare) { 223 for (StructureDefinition sd : contextR2.allStructures()) { 224 StructureDefinition sdn = sd.copy(); 225 sdn.getExtension().clear(); 226 contextR3.seeResource(sdn.getUrl(), sdn); 227 } 228 229 for (StructureDefinition sd : contextR3.allStructures()) { 230 if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { 231 contextR2.seeResource(sd.getUrl(), sd); 232 StructureDefinition sdn = sd.copy(); 233 sdn.setUrl(sdn.getUrl().replace("http://hl7.org/fhir/", "http://hl7.org/fhir/DSTU2/")); 234 sdn.addExtension().setUrl("http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace").setValue(new UriType("http://hl7.org/fhir")); 235 contextR2.seeResource(sdn.getUrl(), sdn); 236 contextR3.seeResource(sdn.getUrl(), sdn); 237 } 238 } 239 240 contextR2.setExpansionProfile(new ExpansionProfile().setUrl("urn:uuid:" + UUID.randomUUID().toString().toLowerCase())); 241 contextR3.setExpansionProfile(new ExpansionProfile().setUrl("urn:uuid:" + UUID.randomUUID().toString().toLowerCase())); 242 243 smu3 = new StructureMapUtilities(contextR3, library, this); 244 smu2 = new StructureMapUtilities(contextR2, library, this); 245 246 needPrepare = false; 247 } 248 } 249 250 // execution 251 public byte[] convert(byte[] source, boolean r2ToR3, FhirFormat format) throws FHIRException, IOException { 252 prepare(); 253 ByteArrayOutputStream bs = new ByteArrayOutputStream(); 254 if (r2ToR3) 255 convertToR3(new ByteArrayInputStream(source), bs, format); 256 else 257 convertToR2(new ByteArrayInputStream(source), bs, format); 258 bs.close(); 259 return bs.toByteArray(); 260 } 261 262 public void convert(InputStream source, OutputStream dest, boolean r2ToR3, FhirFormat format) throws FHIRException, IOException { 263 prepare(); 264 if (r2ToR3) 265 convertToR3(source, dest, format); 266 else 267 convertToR2(source, dest, format); 268 } 269 270 public org.hl7.fhir.dstu2.model.Resource convert(org.hl7.fhir.dstu3.model.Resource source) throws IOException, FHIRException { 271 ByteArrayOutputStream bs = new ByteArrayOutputStream(); 272 new JsonParser().compose(bs, source); 273 bs.close(); 274 return new org.hl7.fhir.dstu2.formats.JsonParser().parse(convert(bs.toByteArray(), false, FhirFormat.JSON)); 275 } 276 277 public org.hl7.fhir.dstu3.model.Resource convert(org.hl7.fhir.dstu2.model.Resource source) throws IOException, FHIRException { 278 ByteArrayOutputStream bs = new ByteArrayOutputStream(); 279 new org.hl7.fhir.dstu2.formats.JsonParser().compose(bs, source); 280 bs.close(); 281 return new JsonParser().parse(convert(bs.toByteArray(), false, FhirFormat.JSON)); 282 } 283 284 private void convertToR3(InputStream source, OutputStream dest, FhirFormat format) throws FHIRException, IOException { 285 org.hl7.fhir.dstu3.elementmodel.Element r2 = new org.hl7.fhir.dstu3.elementmodel.XmlParser(contextR2).parse(source); 286 StructureMap map = library.get("http://hl7.org/fhir/StructureMap/" + r2.fhirType() + "2to3"); 287 if (map == null) 288 throw new FHIRException("No Map Found from R2 to R3 for " + r2.fhirType()); 289 String tn = smu3.getTargetType(map).getType(); 290 Resource r3 = ResourceFactory.createResource(tn); 291 smu3.transform(new TransformContextR2R3(contextR3, r2.getChildValue("id")), r2, map, r3); 292 FormatUtilities.makeParser(format).setOutputStyle(style).compose(dest, r3); 293 } 294 295 private void convertToR2(InputStream source, OutputStream dest, FhirFormat format) throws FHIRException, IOException { 296 org.hl7.fhir.dstu3.elementmodel.Element r3 = new org.hl7.fhir.dstu3.elementmodel.XmlParser(contextR3).parse(source); 297 StructureMap map = library.get("??"); 298 String tn = smu3.getTargetType(map).getType(); 299 StructureDefinition sd = smu2.getTargetType(map); 300 org.hl7.fhir.dstu3.elementmodel.Element r2 = Manager.build(contextR2, sd); 301 smu2.transform(contextR2, r3, map, r2); 302 org.hl7.fhir.dstu3.elementmodel.Manager.compose(contextR2, r2, dest, format, style, null); 303 } 304 305 @Override 306 public void log(String message) { 307// System.out.println(message); 308 } 309 310 @Override 311 public Base createType(Object appInfo, String name) throws FHIRException { 312 SimpleWorkerContext context = ((TransformContextR2R3) appInfo).getContext(); 313 if (context == contextR2) { 314 StructureDefinition sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/DSTU2/StructureDefinition/" + name); 315 if (sd == null) 316 throw new FHIRException("Type not found: '" + name + "'"); 317 return Manager.build(context, sd); 318 } else 319 return ResourceFactory.createResourceOrType(name); 320 } 321 322 @Override 323 public Base createResource(Object appInfo, Base res) { 324 if (res instanceof Resource && (res.fhirType().equals("CodeSystem") || res.fhirType().equals("CareTeam")) || res.fhirType().equals("PractitionerRole")) { 325 Resource r = (Resource) res; 326 extras.add(r); 327 r.setId(((TransformContextR2R3) appInfo).getId() + "-" + extras.size()); //todo: get this into appinfo 328 } 329 return res; 330 } 331 332 @Override 333 public Coding translate(Object appInfo, Coding source, String conceptMapUrl) throws FHIRException { 334 throw new Error("translate not done yet"); 335 } 336 337 @Override 338 public Base resolveReference(Object appContext, String url) { 339 for (Resource r : extras) { 340 if (r instanceof MetadataResource) { 341 MetadataResource mr = (MetadataResource) r; 342 if (url.equals(mr.getUrl())) 343 return mr; 344 } 345 if (url.equals(r.fhirType() + "/" + r.getId())) 346 return r; 347 } 348 349 return null; 350 } 351 352 @Override 353 public List<Base> performSearch(Object appContext, String url) { 354 List<Base> results = new ArrayList<Base>(); 355 String[] parts = url.split("\\?"); 356 if (parts.length == 2 && parts[0].substring(1).equals("PractitionerRole")) { 357 String[] vals = parts[1].split("\\="); 358 if (vals.length == 2 && vals[0].equals("practitioner")) 359 for (Resource r : extras) { 360 if (r instanceof PractitionerRole && ((PractitionerRole) r).getPractitioner().getReference().equals("Practitioner/" + vals[1])) { 361 results.add(r); 362 } 363 } 364 } 365 return results; 366 } 367}