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}