001package org.hl7.fhir.dstu2.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.ByteArrayInputStream; 035import java.io.FileNotFoundException; 036import java.io.IOException; 037import java.io.InputStream; 038import java.net.URISyntaxException; 039import java.util.ArrayList; 040import java.util.Collections; 041import java.util.HashMap; 042import java.util.List; 043import java.util.Map; 044import java.util.zip.ZipEntry; 045import java.util.zip.ZipInputStream; 046 047import org.hl7.fhir.dstu2.formats.IParser; 048import org.hl7.fhir.dstu2.formats.JsonParser; 049import org.hl7.fhir.dstu2.formats.ParserType; 050import org.hl7.fhir.dstu2.formats.XmlParser; 051import org.hl7.fhir.dstu2.model.Bundle; 052import org.hl7.fhir.dstu2.model.Bundle.BundleEntryComponent; 053import org.hl7.fhir.dstu2.model.ConceptMap; 054import org.hl7.fhir.dstu2.model.ElementDefinition.ElementDefinitionBindingComponent; 055import org.hl7.fhir.dstu2.model.Resource; 056import org.hl7.fhir.dstu2.model.StructureDefinition; 057import org.hl7.fhir.dstu2.model.StructureDefinition.StructureDefinitionKind; 058import org.hl7.fhir.dstu2.model.ValueSet; 059import org.hl7.fhir.dstu2.terminologies.ValueSetExpansionCache; 060import org.hl7.fhir.dstu2.utils.ProfileUtilities.ProfileKnowledgeProvider; 061import org.hl7.fhir.dstu2.utils.client.FHIRToolingClient; 062import org.hl7.fhir.exceptions.DefinitionException; 063import org.hl7.fhir.exceptions.FHIRException; 064import org.hl7.fhir.utilities.CSFileInputStream; 065import org.hl7.fhir.utilities.Utilities; 066import org.hl7.fhir.utilities.validation.ValidationMessage; 067import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 068 069/* 070 * This is a stand alone implementation of worker context for use inside a tool. 071 * It loads from the validation package (validation-min.xml.zip), and has a 072 * very light cient to connect to an open unauthenticated terminology service 073 */ 074 075public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerContext, ProfileKnowledgeProvider { 076 077 // all maps are to the full URI 078 private Map<String, StructureDefinition> structures = new HashMap<String, StructureDefinition>(); 079 080 // -- Initializations 081 /** 082 * Load the working context from the validation pack 083 * 084 * @param path 085 * filename of the validation pack 086 * @return 087 * @throws IOException 088 * @throws FileNotFoundException 089 * @throws FHIRException 090 * @throws Exception 091 */ 092 public static SimpleWorkerContext fromPack(String path) throws FileNotFoundException, IOException, FHIRException { 093 SimpleWorkerContext res = new SimpleWorkerContext(); 094 res.loadFromPack(path); 095 return res; 096 } 097 098 public static SimpleWorkerContext fromClassPath() throws IOException, FHIRException { 099 SimpleWorkerContext res = new SimpleWorkerContext(); 100 res.loadFromStream(SimpleWorkerContext.class.getResourceAsStream("validation.zip")); 101 return res; 102 } 103 104 public static SimpleWorkerContext fromDefinitions(Map<String, byte[]> source) throws IOException, FHIRException { 105 SimpleWorkerContext res = new SimpleWorkerContext(); 106 for (String name : source.keySet()) { 107 if (name.endsWith(".xml")) { 108 res.loadFromFile(new ByteArrayInputStream(source.get(name)), name); 109 } 110 } 111 return res; 112 } 113 114 public void connectToTSServer(String url) throws URISyntaxException { 115 txServer = new FHIRToolingClient(url); 116 } 117 118 private void loadFromFile(InputStream stream, String name) throws IOException, FHIRException { 119 XmlParser xml = new XmlParser(); 120 Bundle f = (Bundle) xml.parse(stream); 121 for (BundleEntryComponent e : f.getEntry()) { 122 123 if (e.getFullUrl() == null) { 124 System.out.println("unidentified resource in " + name+" (no fullUrl)"); 125 } 126 seeResource(e.getFullUrl(), e.getResource()); 127 } 128 } 129 130 public void seeResource(String url, Resource r) throws FHIRException { 131 if (r instanceof StructureDefinition) 132 seeProfile(url, (StructureDefinition) r); 133 else if (r instanceof ValueSet) 134 seeValueSet(url, (ValueSet) r); 135 else if (r instanceof ConceptMap) 136 maps.put(((ConceptMap) r).getUrl(), (ConceptMap) r); 137 } 138 139 private void seeValueSet(String url, ValueSet vs) throws DefinitionException { 140 if (Utilities.noString(url)) 141 url = vs.getUrl(); 142 if (valueSets.containsKey(vs.getUrl())) 143 throw new DefinitionException("Duplicate Profile " + vs.getUrl()); 144 valueSets.put(vs.getId(), vs); 145 valueSets.put(vs.getUrl(), vs); 146 if (!vs.getUrl().equals(url)) 147 valueSets.put(url, vs); 148 if (vs.hasCodeSystem()) { 149 codeSystems.put(vs.getCodeSystem().getSystem().toString(), vs); 150 } 151 } 152 153 private void seeProfile(String url, StructureDefinition p) throws FHIRException { 154 if (Utilities.noString(url)) 155 url = p.getUrl(); 156 if (!p.hasSnapshot()) { 157 if (!p.hasBase()) 158 throw new DefinitionException("Profile "+p.getName()+" ("+p.getUrl()+") has no base and no snapshot"); 159 StructureDefinition sd = fetchResource(StructureDefinition.class, p.getBase()); 160 if (sd == null) 161 throw new DefinitionException("Profile "+p.getName()+" ("+p.getUrl()+") base "+p.getBase()+" could not be resolved"); 162 List<ValidationMessage> msgs = new ArrayList<ValidationMessage>(); 163 ProfileUtilities pu = new ProfileUtilities(this, msgs, this); 164 pu.generateSnapshot(sd, p, p.getUrl(), p.getName()); 165 for (ValidationMessage msg : msgs) { 166 if (msg.getLevel() == IssueSeverity.ERROR || msg.getLevel() == IssueSeverity.FATAL) 167 throw new DefinitionException("Profile "+p.getName()+" ("+p.getUrl()+"). Error generating snapshot: "+msg.getMessage()); 168 } 169 if (!p.hasSnapshot()) 170 throw new DefinitionException("Profile "+p.getName()+" ("+p.getUrl()+"). Error generating snapshot"); 171 pu = null; 172 } 173 if (structures.containsKey(p.getUrl())) 174 throw new DefinitionException("Duplicate structures " + p.getUrl()); 175 structures.put(p.getId(), p); 176 structures.put(p.getUrl(), p); 177 if (!p.getUrl().equals(url)) 178 structures.put(url, p); 179 } 180 181 private void loadFromPack(String path) throws FileNotFoundException, IOException, FHIRException { 182 loadFromStream(new CSFileInputStream(path)); 183 } 184 185 private void loadFromStream(InputStream stream) throws IOException, FHIRException { 186 ZipInputStream zip = new ZipInputStream(stream); 187 ZipEntry ze; 188 while ((ze = zip.getNextEntry()) != null) { 189 if (ze.getName().endsWith(".xml")) { 190 String name = ze.getName(); 191 loadFromFile(zip, name); 192 } 193 zip.closeEntry(); 194 } 195 zip.close(); 196 } 197 198 199 @Override 200 public IParser getParser(ParserType type) { 201 switch (type) { 202 case JSON: return newJsonParser(); 203 case XML: return newXmlParser(); 204 default: 205 throw new Error("Parser Type "+type.toString()+" not supported"); 206 } 207 } 208 209 @Override 210 public IParser getParser(String type) { 211 if (type.equalsIgnoreCase("JSON")) 212 return new JsonParser(); 213 if (type.equalsIgnoreCase("XML")) 214 return new XmlParser(); 215 throw new Error("Parser Type "+type.toString()+" not supported"); 216 } 217 218 @Override 219 public IParser newJsonParser() { 220 return new JsonParser(); 221 } 222 @Override 223 public IParser newXmlParser() { 224 return new XmlParser(); 225 } 226 227 @Override 228 public <T extends Resource> boolean hasResource(Class<T> class_, String uri) { 229 try { 230 return fetchResource(class_, uri) != null; 231 } catch (Exception e) { 232 return false; 233 } 234 } 235 236 @Override 237 public INarrativeGenerator getNarrativeGenerator(String prefix, String basePath) { 238 return new NarrativeGenerator(prefix, basePath, this); 239 } 240 241 @Override 242 public IResourceValidator newValidator() { 243 throw new Error("not supported at this time"); 244 } 245 246 @SuppressWarnings("unchecked") 247 @Override 248 public <T extends Resource> T fetchResource(Class<T> class_, String uri) { 249 if (class_ == StructureDefinition.class && !uri.contains("/")) 250 uri = "http://hl7.org/fhir/StructureDefinition/"+uri; 251 252 if (uri.startsWith("http:")) { 253 if (uri.contains("#")) 254 uri = uri.substring(0, uri.indexOf("#")); 255 if (class_ == StructureDefinition.class) { 256 if (structures.containsKey(uri)) 257 return (T) structures.get(uri); 258 else 259 return null; 260 } else if (class_ == ValueSet.class) { 261 if (valueSets.containsKey(uri)) 262 return (T) valueSets.get(uri); 263 else if (codeSystems.containsKey(uri)) 264 return (T) codeSystems.get(uri); 265 else 266 return null; 267 } 268 } 269 if (class_ == null && uri.contains("/")) { 270 return null; 271 } 272 273 throw new Error("not done yet"); 274 } 275 276 277 278 public int totalCount() { 279 return valueSets.size() + maps.size() + structures.size(); 280 } 281 282 public void setCache(ValueSetExpansionCache cache) { 283 this.expansionCache = cache; 284 } 285 286 @Override 287 public List<String> getResourceNames() { 288 List<String> result = new ArrayList<String>(); 289 for (StructureDefinition sd : structures.values()) { 290 if (sd.getKind() == StructureDefinitionKind.RESOURCE && !sd.hasConstrainedType()) 291 result.add(sd.getName()); 292 } 293 Collections.sort(result); 294 return result; 295 } 296 297 @Override 298 public String getAbbreviation(String name) { 299 return "xxx"; 300 } 301 302 @Override 303 public boolean isDatatype(String typeSimple) { 304 // TODO Auto-generated method stub 305 return false; 306 } 307 308 @Override 309 public boolean isResource(String t) { 310 StructureDefinition sd; 311 try { 312 sd = fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+t); 313 } catch (Exception e) { 314 return false; 315 } 316 if (sd == null) 317 return false; 318 if (sd.hasConstrainedType()) 319 return false; 320 return sd.getKind() == StructureDefinitionKind.RESOURCE; 321 } 322 323 @Override 324 public boolean hasLinkFor(String typeSimple) { 325 return false; 326 } 327 328 @Override 329 public String getLinkFor(String typeSimple) { 330 return null; 331 } 332 333 @Override 334 public BindingResolution resolveBinding(ElementDefinitionBindingComponent binding) { 335 return null; 336 } 337 338 @Override 339 public String getLinkForProfile(StructureDefinition profile, String url) { 340 return null; 341 } 342 343 @Override 344 public List<StructureDefinition> allStructures() { 345 List<StructureDefinition> res = new ArrayList<StructureDefinition>(); 346 res.addAll(structures.values()); 347 return res ; 348 } 349 350 351 352}