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