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