001package org.hl7.fhir.dstu2016may.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.File; 036import java.io.FileInputStream; 037import java.io.FileNotFoundException; 038import java.io.IOException; 039import java.io.InputStream; 040import java.net.URISyntaxException; 041import java.util.ArrayList; 042import java.util.Arrays; 043import java.util.Collections; 044import java.util.HashMap; 045import java.util.HashSet; 046import java.util.List; 047import java.util.Map; 048import java.util.Set; 049import java.util.zip.ZipEntry; 050import java.util.zip.ZipInputStream; 051 052import org.hl7.fhir.dstu2016may.formats.IParser; 053import org.hl7.fhir.dstu2016may.formats.JsonParser; 054import org.hl7.fhir.dstu2016may.formats.ParserType; 055import org.hl7.fhir.dstu2016may.formats.XmlParser; 056import org.hl7.fhir.dstu2016may.model.Bundle; 057import org.hl7.fhir.dstu2016may.model.Bundle.BundleEntryComponent; 058import org.hl7.fhir.dstu2016may.model.CodeSystem; 059import org.hl7.fhir.dstu2016may.model.ConceptMap; 060import org.hl7.fhir.dstu2016may.model.ElementDefinition.ElementDefinitionBindingComponent; 061import org.hl7.fhir.dstu2016may.model.NamingSystem; 062import org.hl7.fhir.dstu2016may.model.NamingSystem.NamingSystemIdentifierType; 063import org.hl7.fhir.dstu2016may.model.NamingSystem.NamingSystemUniqueIdComponent; 064import org.hl7.fhir.dstu2016may.model.Questionnaire; 065import org.hl7.fhir.dstu2016may.model.Resource; 066import org.hl7.fhir.dstu2016may.model.ResourceType; 067import org.hl7.fhir.dstu2016may.model.StructureDefinition; 068import org.hl7.fhir.dstu2016may.model.StructureDefinition.StructureDefinitionKind; 069import org.hl7.fhir.dstu2016may.model.ValueSet; 070import org.hl7.fhir.dstu2016may.terminologies.ValueSetExpansionCache; 071import org.hl7.fhir.dstu2016may.utils.ProfileUtilities.ProfileKnowledgeProvider; 072import org.hl7.fhir.dstu2016may.utils.client.FHIRToolingClient; 073import org.hl7.fhir.exceptions.DefinitionException; 074import org.hl7.fhir.exceptions.FHIRException; 075import org.hl7.fhir.exceptions.FHIRFormatError; 076import org.hl7.fhir.utilities.CSFileInputStream; 077import org.hl7.fhir.utilities.OIDUtils; 078import org.hl7.fhir.utilities.Utilities; 079import org.hl7.fhir.utilities.validation.ValidationMessage; 080import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 081 082/* 083 * This is a stand alone implementation of worker context for use inside a tool. 084 * It loads from the validation package (validation-min.xml.zip), and has a 085 * very light cient to connect to an open unauthenticated terminology service 086 */ 087 088public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerContext, ProfileKnowledgeProvider { 089 090 // all maps are to the full URI 091 private Map<String, StructureDefinition> structures = new HashMap<String, StructureDefinition>(); 092 private List<NamingSystem> systems = new ArrayList<NamingSystem>(); 093 private Questionnaire questionnaire; 094 095 // -- Initializations 096 /** 097 * Load the working context from the validation pack 098 * 099 * @param path 100 * filename of the validation pack 101 * @return 102 * @throws IOException 103 * @throws FileNotFoundException 104 * @throws FHIRException 105 * @throws Exception 106 */ 107 public static SimpleWorkerContext fromPack(String path) throws FileNotFoundException, IOException, FHIRException { 108 SimpleWorkerContext res = new SimpleWorkerContext(); 109 res.loadFromPack(path); 110 return res; 111 } 112 113 public static SimpleWorkerContext fromClassPath() throws IOException, FHIRException { 114 SimpleWorkerContext res = new SimpleWorkerContext(); 115 res.loadFromStream(SimpleWorkerContext.class.getResourceAsStream("validation.zip")); 116 return res; 117 } 118 119 public static SimpleWorkerContext fromDefinitions(Map<String, byte[]> source) throws IOException, FHIRException { 120 SimpleWorkerContext res = new SimpleWorkerContext(); 121 for (String name : source.keySet()) { 122 if (name.endsWith(".xml")) { 123 res.loadFromFile(new ByteArrayInputStream(source.get(name)), name); 124 } 125 } 126 return res; 127 } 128 129 public void connectToTSServer(String url) throws URISyntaxException { 130 txServer = new FHIRToolingClient(url); 131 } 132 133 private void loadFromFile(InputStream stream, String name) throws IOException, FHIRException { 134 XmlParser xml = new XmlParser(); 135 Bundle f; 136 try { 137 f = (Bundle) xml.parse(stream); 138 } catch (FHIRFormatError e1) { 139 throw new org.hl7.fhir.exceptions.FHIRFormatError(e1.getMessage(), e1); 140 } 141 for (BundleEntryComponent e : f.getEntry()) { 142 143 if (e.getFullUrl() == null) { 144 System.out.println("unidentified resource in " + name+" (no fullUrl)"); 145 } 146 seeResource(e.getFullUrl(), e.getResource()); 147 } 148 } 149 150 public void seeResource(String url, Resource r) throws FHIRException { 151 if (r instanceof StructureDefinition) 152 seeProfile(url, (StructureDefinition) r); 153 else if (r instanceof ValueSet) 154 seeValueSet(url, (ValueSet) r); 155 else if (r instanceof CodeSystem) 156 seeCodeSystem(url, (CodeSystem) r); 157 else if (r instanceof ConceptMap) 158 maps.put(((ConceptMap) r).getUrl(), (ConceptMap) r); 159 else if (r instanceof NamingSystem) 160 systems.add((NamingSystem) r); 161 } 162 163 private void seeValueSet(String url, ValueSet vs) throws DefinitionException { 164 if (Utilities.noString(url)) 165 url = vs.getUrl(); 166 if (valueSets.containsKey(vs.getUrl())) 167 throw new DefinitionException("Duplicate Profile " + vs.getUrl()); 168 valueSets.put(vs.getId(), vs); 169 valueSets.put(vs.getUrl(), vs); 170 if (!vs.getUrl().equals(url)) 171 valueSets.put(url, vs); 172 } 173 174 private void seeCodeSystem(String url, CodeSystem cs) throws DefinitionException { 175 codeSystems.put(cs.getUrl(), cs); 176 } 177 178 private void seeProfile(String url, StructureDefinition p) throws FHIRException { 179 if (Utilities.noString(url)) 180 url = p.getUrl(); 181 if (!p.hasSnapshot()) { 182 if (!p.hasBaseDefinition()) 183 throw new DefinitionException("Profile "+p.getName()+" ("+p.getUrl()+") has no base and no snapshot"); 184 StructureDefinition sd = fetchResource(StructureDefinition.class, p.getBaseDefinition()); 185 if (sd == null) 186 throw new DefinitionException("Profile "+p.getName()+" ("+p.getUrl()+") base "+p.getBaseDefinition()+" could not be resolved"); 187 List<ValidationMessage> msgs = new ArrayList<ValidationMessage>(); 188 ProfileUtilities pu = new ProfileUtilities(this, msgs, this); 189 pu.generateSnapshot(sd, p, p.getUrl(), p.getName()); 190 for (ValidationMessage msg : msgs) { 191 if (msg.getLevel() == IssueSeverity.ERROR || msg.getLevel() == IssueSeverity.FATAL) 192 throw new DefinitionException("Profile "+p.getName()+" ("+p.getUrl()+"). Error generating snapshot: "+msg.getMessage()); 193 } 194 if (!p.hasSnapshot()) 195 throw new DefinitionException("Profile "+p.getName()+" ("+p.getUrl()+"). Error generating snapshot"); 196 pu = null; 197 } 198 if (structures.containsKey(p.getUrl())) 199 throw new DefinitionException("Duplicate structures " + p.getUrl()); 200 structures.put(p.getId(), p); 201 structures.put(p.getUrl(), p); 202 if (!p.getUrl().equals(url)) 203 structures.put(url, p); 204 } 205 206 private void loadFromPack(String path) throws FileNotFoundException, IOException, FHIRException { 207 loadFromStream(new CSFileInputStream(path)); 208 } 209 210 private void loadFromStream(InputStream stream) throws IOException, FHIRException { 211 ZipInputStream zip = new ZipInputStream(stream); 212 ZipEntry ze; 213 while ((ze = zip.getNextEntry()) != null) { 214 if (ze.getName().endsWith(".xml")) { 215 String name = ze.getName(); 216 loadFromFile(zip, name); 217 } 218 zip.closeEntry(); 219 } 220 zip.close(); 221 } 222 223 224 @Override 225 public IParser getParser(ParserType type) { 226 switch (type) { 227 case JSON: return newJsonParser(); 228 case XML: return newXmlParser(); 229 default: 230 throw new Error("Parser Type "+type.toString()+" not supported"); 231 } 232 } 233 234 @Override 235 public IParser getParser(String type) { 236 if (type.equalsIgnoreCase("JSON")) 237 return new JsonParser(); 238 if (type.equalsIgnoreCase("XML")) 239 return new XmlParser(); 240 throw new Error("Parser Type "+type.toString()+" not supported"); 241 } 242 243 @Override 244 public IParser newJsonParser() { 245 return new JsonParser(); 246 } 247 @Override 248 public IParser newXmlParser() { 249 return new XmlParser(); 250 } 251 252 @Override 253 public <T extends Resource> boolean hasResource(Class<T> class_, String uri) { 254 try { 255 return fetchResource(class_, uri) != null; 256 } catch (Exception e) { 257 return false; 258 } 259 } 260 261 @Override 262 public INarrativeGenerator getNarrativeGenerator(String prefix, String basePath) { 263 return new NarrativeGenerator(prefix, basePath, this); 264 } 265 266 267 @SuppressWarnings("unchecked") 268 @Override 269 public <T extends Resource> T fetchResource(Class<T> class_, String uri) { 270 if (class_ == Questionnaire.class) 271 return (T) questionnaire; 272 273 if (class_ == StructureDefinition.class && !uri.contains("/")) 274 uri = "http://hl7.org/fhir/StructureDefinition/"+uri; 275 276 if (uri.startsWith("http:")) { 277 if (uri.contains("#")) 278 uri = uri.substring(0, uri.indexOf("#")); 279 if (class_ == StructureDefinition.class) { 280 if (structures.containsKey(uri)) 281 return (T) structures.get(uri); 282 else 283 return null; 284 } else if (class_ == ValueSet.class) { 285 if (valueSets.containsKey(uri)) 286 return (T) valueSets.get(uri); 287 else 288 return null; 289 } else if (class_ == CodeSystem.class) { 290 if (codeSystems.containsKey(uri)) 291 return (T) codeSystems.get(uri); 292 else 293 return null; 294 } else if (class_ == ConceptMap.class) { 295 if (maps.containsKey(uri)) 296 return (T) maps.get(uri); 297 else 298 return null; 299 } 300 } 301 if (class_ == null && uri.contains("/")) { 302 return null; 303 } 304 305 throw new Error("not done yet"); 306 } 307 308 309 310 public int totalCount() { 311 return valueSets.size() + maps.size() + structures.size(); 312 } 313 314 public void setCache(ValueSetExpansionCache cache) { 315 this.expansionCache = cache; 316 } 317 318 @Override 319 public List<String> getResourceNames() { 320 List<String> result = new ArrayList<String>(); 321 for (StructureDefinition sd : structures.values()) { 322 if (sd.getKind() == StructureDefinitionKind.RESOURCE && !sd.hasBaseType()) 323 result.add(sd.getName()); 324 } 325 Collections.sort(result); 326 return result; 327 } 328 329 @Override 330 public String getAbbreviation(String name) { 331 return "xxx"; 332 } 333 334 @Override 335 public boolean isDatatype(String typeSimple) { 336 // TODO Auto-generated method stub 337 return false; 338 } 339 340 @Override 341 public boolean isResource(String t) { 342 StructureDefinition sd; 343 try { 344 sd = fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+t); 345 } catch (Exception e) { 346 return false; 347 } 348 if (sd == null) 349 return false; 350 if (sd.hasBaseType()) 351 return false; 352 return sd.getKind() == StructureDefinitionKind.RESOURCE; 353 } 354 355 @Override 356 public boolean hasLinkFor(String typeSimple) { 357 return false; 358 } 359 360 @Override 361 public String getLinkFor(String typeSimple) { 362 return null; 363 } 364 365 @Override 366 public BindingResolution resolveBinding(ElementDefinitionBindingComponent binding) { 367 return null; 368 } 369 370 @Override 371 public String getLinkForProfile(StructureDefinition profile, String url) { 372 return null; 373 } 374 375 public Questionnaire getQuestionnaire() { 376 return questionnaire; 377 } 378 379 public void setQuestionnaire(Questionnaire questionnaire) { 380 this.questionnaire = questionnaire; 381 } 382 383 @Override 384 public Set<String> typeTails() { 385 return new HashSet<String>(Arrays.asList("Integer","UnsignedInt","PositiveInt","Decimal","DateTime","Date","Time","Instant","String","Uri","Oid","Uuid","Id","Boolean","Code","Markdown","Base64Binary","Coding","CodeableConcept","Attachment","Identifier","Quantity","SampledData","Range","Period","Ratio","HumanName","Address","ContactPoint","Timing","Reference","Annotation","Signature","Meta")); 386 } 387 388 @Override 389 public List<StructureDefinition> allStructures() { 390 List<StructureDefinition> result = new ArrayList<StructureDefinition>(); 391 result.addAll(structures.values()); 392 return result; 393 } 394 395 @Override 396 public String oid2Uri(String oid) { 397 String uri = OIDUtils.getUriForOid(oid); 398 if (uri != null) 399 return uri; 400 for (NamingSystem ns : systems) { 401 if (hasOid(ns, oid)) { 402 uri = getUri(ns); 403 if (uri != null) 404 return null; 405 } 406 } 407 return null; 408 } 409 410 private String getUri(NamingSystem ns) { 411 for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) { 412 if (id.getType() == NamingSystemIdentifierType.URI) 413 return id.getValue(); 414 } 415 return null; 416 } 417 418 private boolean hasOid(NamingSystem ns, String oid) { 419 for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) { 420 if (id.getType() == NamingSystemIdentifierType.OID && id.getValue().equals(oid)) 421 return true; 422 } 423 return false; 424 } 425 426 427 428 429 public void loadFromFolder(String folder) throws FileNotFoundException, Exception { 430 for (String n : new File(folder).list()) { 431 if (n.endsWith(".json")) 432 loadFromFile(Utilities.path(folder, n), new JsonParser()); 433 else if (n.endsWith(".xml")) 434 loadFromFile(Utilities.path(folder, n), new XmlParser()); 435 } 436 } 437 438 private void loadFromFile(String filename, IParser p) throws FileNotFoundException, Exception { 439 Resource r; 440 try { 441 r = p.parse(new FileInputStream(filename)); 442 if (r.getResourceType() == ResourceType.Bundle) { 443 for (BundleEntryComponent e : ((Bundle) r).getEntry()) { 444 seeResource(null, e.getResource()); 445 } 446 } else { 447 seeResource(null, r); 448 } 449 } catch (Exception e) { 450 return; 451 } 452 } 453 454}