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}