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 032import java.io.ByteArrayInputStream; 033import java.io.FileNotFoundException; 034import java.io.IOException; 035import java.io.InputStream; 036import java.net.URISyntaxException; 037import java.util.ArrayList; 038import java.util.Collections; 039import java.util.HashMap; 040import java.util.List; 041import java.util.Map; 042import java.util.zip.ZipEntry; 043import java.util.zip.ZipInputStream; 044 045import org.hl7.fhir.dstu2.formats.IParser; 046import org.hl7.fhir.dstu2.formats.JsonParser; 047import org.hl7.fhir.dstu2.formats.ParserType; 048import org.hl7.fhir.dstu2.formats.XmlParser; 049import org.hl7.fhir.dstu2.model.Bundle; 050import org.hl7.fhir.dstu2.model.Bundle.BundleEntryComponent; 051import org.hl7.fhir.dstu2.model.ConceptMap; 052import org.hl7.fhir.dstu2.model.ElementDefinition.ElementDefinitionBindingComponent; 053import org.hl7.fhir.dstu2.model.Resource; 054import org.hl7.fhir.dstu2.model.StructureDefinition; 055import org.hl7.fhir.dstu2.model.StructureDefinition.StructureDefinitionKind; 056import org.hl7.fhir.dstu2.model.ValueSet; 057import org.hl7.fhir.dstu2.terminologies.ValueSetExpansionCache; 058import org.hl7.fhir.dstu2.utils.ProfileUtilities.ProfileKnowledgeProvider; 059import org.hl7.fhir.dstu2.utils.client.FHIRToolingClient; 060import org.hl7.fhir.dstu2.utils.validation.IResourceValidator; 061import org.hl7.fhir.exceptions.DefinitionException; 062import org.hl7.fhir.exceptions.FHIRException; 063import org.hl7.fhir.utilities.Utilities; 064import org.hl7.fhir.utilities.filesystem.CSFileInputStream; 065import org.hl7.fhir.utilities.validation.ValidationMessage; 066import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 067 068/* 069 * This is a stand alone implementation of worker context for use inside a tool. 070 * It loads from the validation package (validation-min.xml.zip), and has a 071 * very light cient to connect to an open unauthenticated terminology service 072 */ 073 074public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerContext, ProfileKnowledgeProvider { 075 076 // all maps are to the full URI 077 private Map<String, StructureDefinition> structures = new HashMap<String, StructureDefinition>(); 078 079 // -- Initializations 080 /** 081 * Load the working context from the validation pack 082 * 083 * @param path filename of the validation pack 084 * @return 085 * @throws IOException 086 * @throws FileNotFoundException 087 * @throws FHIRException 088 * @throws Exception 089 */ 090 public static SimpleWorkerContext fromPack(String path) throws FileNotFoundException, IOException, FHIRException { 091 SimpleWorkerContext res = new SimpleWorkerContext(); 092 res.loadFromPack(path); 093 return res; 094 } 095 096 public static SimpleWorkerContext fromClassPath() throws IOException, FHIRException { 097 SimpleWorkerContext res = new SimpleWorkerContext(); 098 res.loadFromStream(SimpleWorkerContext.class.getResourceAsStream("validation.zip")); 099 return res; 100 } 101 102 public static SimpleWorkerContext fromDefinitions(Map<String, byte[]> source) throws IOException, FHIRException { 103 SimpleWorkerContext res = new SimpleWorkerContext(); 104 for (String name : source.keySet()) { 105 if (name.endsWith(".xml")) { 106 res.loadFromFile(new ByteArrayInputStream(source.get(name)), name); 107 } 108 } 109 return res; 110 } 111 112 public void connectToTSServer(String url, String userAgent) throws URISyntaxException { 113 txServer = new FHIRToolingClient(url, userAgent); 114 } 115 116 private void loadFromFile(InputStream stream, String name) throws IOException, FHIRException { 117 XmlParser xml = new XmlParser(); 118 Bundle f = (Bundle) xml.parse(stream); 119 for (BundleEntryComponent e : f.getEntry()) { 120 121 if (e.getFullUrl() == null) { 122 System.out.println("unidentified resource in " + name + " (no fullUrl)"); 123 } 124 seeResource(e.getFullUrl(), e.getResource()); 125 } 126 } 127 128 public void seeResource(String url, Resource r) throws FHIRException { 129 if (r instanceof StructureDefinition) 130 seeProfile(url, (StructureDefinition) r); 131 else if (r instanceof ValueSet) 132 seeValueSet(url, (ValueSet) r); 133 else if (r instanceof ConceptMap) 134 maps.put(((ConceptMap) r).getUrl(), (ConceptMap) r); 135 } 136 137 private void seeValueSet(String url, ValueSet vs) throws DefinitionException { 138 if (Utilities.noString(url)) 139 url = vs.getUrl(); 140 if (valueSets.containsKey(vs.getUrl())) 141 throw new DefinitionException("Duplicate Profile " + vs.getUrl()); 142 valueSets.put(vs.getId(), vs); 143 valueSets.put(vs.getUrl(), vs); 144 if (!vs.getUrl().equals(url)) 145 valueSets.put(url, vs); 146 if (vs.hasCodeSystem()) { 147 codeSystems.put(vs.getCodeSystem().getSystem().toString(), vs); 148 } 149 } 150 151 private void seeProfile(String url, StructureDefinition p) throws FHIRException { 152 if (Utilities.noString(url)) 153 url = p.getUrl(); 154 if (!p.hasSnapshot()) { 155 if (!p.hasBase()) 156 throw new DefinitionException("Profile " + p.getName() + " (" + p.getUrl() + ") has no base and no snapshot"); 157 StructureDefinition sd = fetchResource(StructureDefinition.class, p.getBase()); 158 if (sd == null) 159 throw new DefinitionException( 160 "Profile " + p.getName() + " (" + p.getUrl() + ") base " + p.getBase() + " could not be resolved"); 161 List<ValidationMessage> msgs = new ArrayList<ValidationMessage>(); 162 ProfileUtilities pu = new ProfileUtilities(this, msgs, this); 163 pu.generateSnapshot(sd, p, p.getUrl(), p.getName()); 164 for (ValidationMessage msg : msgs) { 165 if (msg.getLevel() == IssueSeverity.ERROR || msg.getLevel() == IssueSeverity.FATAL) 166 throw new DefinitionException( 167 "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 @Override 199 public IParser getParser(ParserType type) { 200 switch (type) { 201 case JSON: 202 return newJsonParser(); 203 case XML: 204 return newXmlParser(); 205 default: 206 throw new Error("Parser Type " + type.toString() + " not supported"); 207 } 208 } 209 210 @Override 211 public IParser getParser(String type) { 212 if (type.equalsIgnoreCase("JSON")) 213 return new JsonParser(); 214 if (type.equalsIgnoreCase("XML")) 215 return new XmlParser(); 216 throw new Error("Parser Type " + type.toString() + " not supported"); 217 } 218 219 @Override 220 public IParser newJsonParser() { 221 return new JsonParser(); 222 } 223 224 @Override 225 public IParser newXmlParser() { 226 return new XmlParser(); 227 } 228 229 @Override 230 public <T extends Resource> boolean hasResource(Class<T> class_, String uri) { 231 try { 232 return fetchResource(class_, uri) != null; 233 } catch (Exception e) { 234 return false; 235 } 236 } 237 238 @Override 239 public INarrativeGenerator getNarrativeGenerator(String prefix, String basePath) { 240 return new NarrativeGenerator(prefix, basePath, this); 241 } 242 243 @Override 244 public IResourceValidator newValidator() { 245 throw new Error("not supported at this time"); 246 } 247 248 @SuppressWarnings("unchecked") 249 @Override 250 public <T extends Resource> T fetchResource(Class<T> class_, String uri) { 251 if (class_ == StructureDefinition.class && !uri.contains("/")) 252 uri = "http://hl7.org/fhir/StructureDefinition/" + uri; 253 254 if (uri.startsWith("http:")) { 255 if (uri.contains("#")) 256 uri = uri.substring(0, uri.indexOf("#")); 257 if (class_ == StructureDefinition.class) { 258 if (structures.containsKey(uri)) 259 return (T) structures.get(uri); 260 else 261 return null; 262 } else if (class_ == ValueSet.class) { 263 if (valueSets.containsKey(uri)) 264 return (T) valueSets.get(uri); 265 else if (codeSystems.containsKey(uri)) 266 return (T) codeSystems.get(uri); 267 else 268 return null; 269 } 270 } 271 if (class_ == null && uri.contains("/")) { 272 return null; 273 } 274 275 throw new Error("not done yet"); 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}