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.dstu2.utils.validation.IResourceValidator;
063import org.hl7.fhir.exceptions.DefinitionException;
064import org.hl7.fhir.exceptions.FHIRException;
065import org.hl7.fhir.utilities.CSFileInputStream;
066import org.hl7.fhir.utilities.Utilities;
067import org.hl7.fhir.utilities.validation.ValidationMessage;
068import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
069
070/*
071 * This is a stand alone implementation of worker context for use inside a tool.
072 * It loads from the validation package (validation-min.xml.zip), and has a 
073 * very light cient to connect to an open unauthenticated terminology service
074 */
075
076public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerContext, ProfileKnowledgeProvider {
077
078        // all maps are to the full URI
079        private Map<String, StructureDefinition> structures = new HashMap<String, StructureDefinition>();
080
081        // -- Initializations
082        /**
083         * Load the working context from the validation pack
084         * 
085         * @param path
086         *           filename of the validation pack
087         * @return
088         * @throws IOException 
089         * @throws FileNotFoundException 
090         * @throws FHIRException 
091         * @throws Exception
092         */
093        public static SimpleWorkerContext fromPack(String path) throws FileNotFoundException, IOException, FHIRException {
094                SimpleWorkerContext res = new SimpleWorkerContext();
095                res.loadFromPack(path);
096                return res;
097        }
098
099        public static SimpleWorkerContext fromClassPath() throws IOException, FHIRException {
100                SimpleWorkerContext res = new SimpleWorkerContext();
101                res.loadFromStream(SimpleWorkerContext.class.getResourceAsStream("validation.zip"));
102                return res;
103        }
104
105        public static SimpleWorkerContext fromDefinitions(Map<String, byte[]> source) throws IOException, FHIRException {
106                SimpleWorkerContext res = new SimpleWorkerContext();
107                for (String name : source.keySet()) {
108                        if (name.endsWith(".xml")) {
109                                res.loadFromFile(new ByteArrayInputStream(source.get(name)), name);
110                        }
111                }
112                return res;
113        }
114
115        public void connectToTSServer(String url, String userAgent) throws URISyntaxException {
116          txServer = new FHIRToolingClient(url, userAgent);
117        }
118
119        private void loadFromFile(InputStream stream, String name) throws IOException, FHIRException {
120                XmlParser xml = new XmlParser();
121                Bundle f = (Bundle) xml.parse(stream);
122                for (BundleEntryComponent e : f.getEntry()) {
123
124                        if (e.getFullUrl() == null) {
125                                System.out.println("unidentified resource in " + name+" (no fullUrl)");
126                        }
127                        seeResource(e.getFullUrl(), e.getResource());
128                }
129        }
130
131        public void seeResource(String url, Resource r) throws FHIRException {
132    if (r instanceof StructureDefinition)
133      seeProfile(url, (StructureDefinition) r);
134    else if (r instanceof ValueSet)
135      seeValueSet(url, (ValueSet) r);
136    else if (r instanceof ConceptMap)
137      maps.put(((ConceptMap) r).getUrl(), (ConceptMap) r);
138        }
139        
140        private void seeValueSet(String url, ValueSet vs) throws DefinitionException {
141          if (Utilities.noString(url))
142            url = vs.getUrl();
143                if (valueSets.containsKey(vs.getUrl()))
144                        throw new DefinitionException("Duplicate Profile " + vs.getUrl());
145                valueSets.put(vs.getId(), vs);
146                valueSets.put(vs.getUrl(), vs);
147                if (!vs.getUrl().equals(url))
148                        valueSets.put(url, vs);
149                if (vs.hasCodeSystem()) {
150                        codeSystems.put(vs.getCodeSystem().getSystem().toString(), vs);
151                }
152        }
153
154        private void seeProfile(String url, StructureDefinition p) throws FHIRException {
155    if (Utilities.noString(url))
156      url = p.getUrl();
157    if (!p.hasSnapshot()) {
158      if (!p.hasBase())
159        throw new DefinitionException("Profile "+p.getName()+" ("+p.getUrl()+") has no base and no snapshot");
160      StructureDefinition sd = fetchResource(StructureDefinition.class, p.getBase());
161      if (sd == null)
162        throw new DefinitionException("Profile "+p.getName()+" ("+p.getUrl()+") base "+p.getBase()+" could not be resolved");
163      List<ValidationMessage> msgs = new ArrayList<ValidationMessage>();
164      ProfileUtilities pu = new ProfileUtilities(this, msgs, this);
165      pu.generateSnapshot(sd, p, p.getUrl(), p.getName());
166      for (ValidationMessage msg : msgs) {
167        if (msg.getLevel() == IssueSeverity.ERROR || msg.getLevel() == IssueSeverity.FATAL)
168          throw new DefinitionException("Profile "+p.getName()+" ("+p.getUrl()+"). Error generating snapshot: "+msg.getMessage());
169      }
170      if (!p.hasSnapshot())
171        throw new DefinitionException("Profile "+p.getName()+" ("+p.getUrl()+"). Error generating snapshot");
172      pu = null;
173    }
174                if (structures.containsKey(p.getUrl()))
175                        throw new DefinitionException("Duplicate structures " + p.getUrl());
176                structures.put(p.getId(), p);
177                structures.put(p.getUrl(), p);
178                if (!p.getUrl().equals(url))
179                        structures.put(url, p);
180        }
181
182        private void loadFromPack(String path) throws FileNotFoundException, IOException, FHIRException {
183                loadFromStream(new CSFileInputStream(path));
184        }
185
186        private void loadFromStream(InputStream stream) throws IOException, FHIRException {
187                ZipInputStream zip = new ZipInputStream(stream);
188                ZipEntry ze;
189                while ((ze = zip.getNextEntry()) != null) {
190                        if (ze.getName().endsWith(".xml")) {
191                                String name = ze.getName();
192                                loadFromFile(zip, name);
193                        }
194                        zip.closeEntry();
195                }
196                zip.close();
197        }
198
199
200        @Override
201        public IParser getParser(ParserType type) {
202                switch (type) {
203                case JSON: return newJsonParser();
204                case XML: 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        @Override
224        public IParser newXmlParser() {
225                return new XmlParser();
226        }
227
228        @Override
229        public <T extends Resource> boolean hasResource(Class<T> class_, String uri) {
230                try {
231                        return fetchResource(class_, uri) != null;
232                } catch (Exception e) {
233                        return false;
234                }
235        }
236
237        @Override
238        public INarrativeGenerator getNarrativeGenerator(String prefix, String basePath) {
239                return new NarrativeGenerator(prefix, basePath, this);
240        }
241
242        @Override
243        public IResourceValidator newValidator() {
244    throw new Error("not supported at this time"); 
245        }
246
247        @SuppressWarnings("unchecked")
248        @Override
249        public <T extends Resource> T fetchResource(Class<T> class_, String uri) {
250                if (class_ == StructureDefinition.class && !uri.contains("/"))
251                        uri = "http://hl7.org/fhir/StructureDefinition/"+uri;
252
253                if (uri.startsWith("http:")) {
254                        if (uri.contains("#"))
255                                uri = uri.substring(0, uri.indexOf("#"));
256                        if (class_ == StructureDefinition.class) {
257                                if (structures.containsKey(uri))
258                                        return (T) structures.get(uri);
259                                else
260                                        return null;
261                        } else if (class_ == ValueSet.class) {
262                                if (valueSets.containsKey(uri))
263                                        return (T) valueSets.get(uri);
264                                else if (codeSystems.containsKey(uri))
265                                        return (T) codeSystems.get(uri);
266                                else
267                                        return null;      
268                        }
269                }
270                if (class_ == null && uri.contains("/")) {
271                        return null;      
272                }
273
274                throw new Error("not done yet");
275        }
276
277
278
279        public int totalCount() {
280                return valueSets.size() +  maps.size() + structures.size();
281        }
282
283        public void setCache(ValueSetExpansionCache cache) {
284          this.expansionCache = cache;  
285        }
286
287  @Override
288  public List<String> getResourceNames() {
289    List<String> result = new ArrayList<String>();
290    for (StructureDefinition sd : structures.values()) {
291      if (sd.getKind() == StructureDefinitionKind.RESOURCE && !sd.hasConstrainedType())
292        result.add(sd.getName());
293    }
294    Collections.sort(result);
295    return result;
296  }
297
298  @Override
299  public String getAbbreviation(String name) {
300    return "xxx";
301  }
302
303  @Override
304  public boolean isDatatype(String typeSimple) {
305    // TODO Auto-generated method stub
306    return false;
307  }
308
309  @Override
310  public boolean isResource(String t) {
311    StructureDefinition sd;
312    try {
313      sd = fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+t);
314    } catch (Exception e) {
315      return false;
316    }
317    if (sd == null)
318      return false;
319    if (sd.hasConstrainedType())
320      return false;
321    return sd.getKind() == StructureDefinitionKind.RESOURCE;
322  }
323
324  @Override
325  public boolean hasLinkFor(String typeSimple) {
326    return false;
327  }
328
329  @Override
330  public String getLinkFor(String typeSimple) {
331    return null;
332  }
333
334  @Override
335  public BindingResolution resolveBinding(ElementDefinitionBindingComponent binding) {
336    return null;
337  }
338
339  @Override
340  public String getLinkForProfile(StructureDefinition profile, String url) {
341    return null;
342  }
343
344  @Override
345  public List<StructureDefinition> allStructures() {
346    List<StructureDefinition> res = new ArrayList<StructureDefinition>();
347    res.addAll(structures.values());
348    return res ;
349  }
350
351
352
353}