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.CSFileInputStream;
064import org.hl7.fhir.utilities.Utilities;
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}