001package org.hl7.fhir.r4.terminologies;
002
003/*-
004 * #%L
005 * org.hl7.fhir.r4
006 * %%
007 * Copyright (C) 2014 - 2019 Health Level 7
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 * 
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023
024/*
025Copyright (c) 2011+, HL7, Inc
026All rights reserved.
027
028Redistribution and use in source and binary forms, with or without modification, 
029are permitted provided that the following conditions are met:
030
031 * Redistributions of source code must retain the above copyright notice, this 
032   list of conditions and the following disclaimer.
033 * Redistributions in binary form must reproduce the above copyright notice, 
034   this list of conditions and the following disclaimer in the documentation 
035   and/or other materials provided with the distribution.
036 * Neither the name of HL7 nor the names of its contributors may be used to 
037   endorse or promote products derived from this software without specific 
038   prior written permission.
039
040THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
041ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
042WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
043IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
044INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
045NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
046PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
047WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
048ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
049POSSIBILITY OF SUCH DAMAGE.
050
051*/
052
053import java.io.File;
054import java.io.FileInputStream;
055import java.io.FileOutputStream;
056import java.io.IOException;
057import java.util.HashMap;
058import java.util.Map;
059
060import org.apache.commons.io.IOUtils;
061import org.hl7.fhir.exceptions.FHIRFormatError;
062import org.hl7.fhir.r4.context.IWorkerContext;
063import org.hl7.fhir.r4.formats.IParser.OutputStyle;
064import org.hl7.fhir.r4.formats.JsonParser;
065import org.hl7.fhir.r4.model.MetadataResource;
066import org.hl7.fhir.r4.model.OperationOutcome;
067import org.hl7.fhir.r4.model.Parameters;
068import org.hl7.fhir.r4.model.Resource;
069import org.hl7.fhir.r4.model.ValueSet;
070import org.hl7.fhir.r4.terminologies.ValueSetExpander.TerminologyServiceErrorClass;
071import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
072import org.hl7.fhir.r4.utils.ToolingExtensions;
073import org.hl7.fhir.utilities.Utilities;
074import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
075
076public class ValueSetExpansionCache implements ValueSetExpanderFactory {
077
078  public class CacheAwareExpander implements ValueSetExpander {
079
080          @Override
081          public ValueSetExpansionOutcome expand(ValueSet source, Parameters expParams) throws ETooCostly, IOException {
082            String cacheKey = makeCacheKey(source, expParams);
083                if (expansions.containsKey(cacheKey))
084                        return expansions.get(cacheKey);
085                ValueSetExpander vse = new ValueSetExpanderSimple(context);
086                ValueSetExpansionOutcome vso = vse.expand(source, expParams);
087                if (vso.getError() != null) {
088                  // well, we'll see if the designated server can expand it, and if it can, we'll cache it locally
089                        vso = context.expandVS(source, false, expParams == null || !expParams.getParameterBool("excludeNested"));
090                        if (cacheFolder != null) {
091                          FileOutputStream s = new FileOutputStream(Utilities.path(cacheFolder, makeFileName(source.getUrl())));
092                          context.newXmlParser().setOutputStyle(OutputStyle.PRETTY).compose(s, vso.getValueset());
093                          s.close();
094                        }
095                }
096                if (vso.getValueset() != null)
097                  expansions.put(cacheKey, vso);
098                return vso;
099          }
100
101    private String makeCacheKey(ValueSet source, Parameters expParams) throws IOException {
102      if (expParams == null)
103        return source.getUrl();
104      JsonParser p = new JsonParser();
105      String r = p.composeString(expParams);
106      return source.getUrl() + " " + r.hashCode(); 
107    }
108
109  }
110
111  private static final String VS_ID_EXT = "http://tools/cache";
112
113  private final Map<String, ValueSetExpansionOutcome> expansions = new HashMap<String, ValueSetExpansionOutcome>();
114  private final Map<String, MetadataResource> canonicals = new HashMap<String, MetadataResource>();
115  private final IWorkerContext context;
116  private final String cacheFolder;
117
118  private Object lock;
119        
120        public ValueSetExpansionCache(IWorkerContext context, Object lock) {
121    super();
122    cacheFolder = null;
123    this.lock = lock;
124    this.context = context;
125  }
126  
127        public ValueSetExpansionCache(IWorkerContext context, String cacheFolder, Object lock) throws FHIRFormatError, IOException {
128    super();
129    this.context = context;
130    this.cacheFolder = cacheFolder;
131    this.lock = lock;
132    if (this.cacheFolder != null)
133      loadCache();
134  }
135  
136  private String makeFileName(String url) {
137    return url.replace("$", "").replace(":", "").replace("|", ".").replace("//", "/").replace("/", "_")+".xml";
138  }
139  
140  private void loadCache() throws FHIRFormatError, IOException {
141    File[] files = new File(cacheFolder).listFiles();
142    for (File f : files) {
143      if (f.getName().endsWith(".xml")) {
144        final FileInputStream is = new FileInputStream(f);
145        try {      
146          Resource r = context.newXmlParser().setOutputStyle(OutputStyle.PRETTY).parse(is);
147          if (r instanceof OperationOutcome) {
148            OperationOutcome oo = (OperationOutcome) r;
149            expansions.put(ToolingExtensions.getExtension(oo,VS_ID_EXT).getValue().toString(),
150                new ValueSetExpansionOutcome(new XhtmlComposer(XhtmlComposer.XML, false).composePlainText(oo.getText().getDiv()), TerminologyServiceErrorClass.UNKNOWN));
151          } else if (r instanceof ValueSet) {
152            ValueSet vs = (ValueSet) r;
153            if (vs.hasExpansion())
154              expansions.put(vs.getUrl(), new ValueSetExpansionOutcome(vs));
155            else {
156              canonicals.put(vs.getUrl(), vs);
157              if (vs.hasVersion())
158                canonicals.put(vs.getUrl()+"|"+vs.getVersion(), vs);
159            }
160          } else if (r instanceof MetadataResource) {
161            MetadataResource md = (MetadataResource) r;
162            canonicals.put(md.getUrl(), md);
163            if (md.hasVersion())
164              canonicals.put(md.getUrl()+"|"+md.getVersion(), md);
165          }
166        } finally {
167          IOUtils.closeQuietly(is);
168        }
169      }
170    }
171  }
172
173  @Override
174        public ValueSetExpander getExpander() {
175                return new CacheAwareExpander();
176                // return new ValueSetExpander(valuesets, codesystems);
177        }
178
179  public MetadataResource getStoredResource(String canonicalUri) {
180    synchronized (lock) {
181    return canonicals.get(canonicalUri);
182    }
183  }
184
185  public void storeResource(MetadataResource md) throws IOException {
186    synchronized (lock) {
187      canonicals.put(md.getUrl(), md);
188      if (md.hasVersion())
189        canonicals.put(md.getUrl()+"|"+md.getVersion(), md);    
190    }
191    if (cacheFolder != null) {
192      FileOutputStream s = new FileOutputStream(Utilities.path(cacheFolder, makeFileName(md.getUrl()+"|"+md.getVersion())));
193      context.newXmlParser().setOutputStyle(OutputStyle.PRETTY).compose(s, md);
194      s.close();
195    }
196  }
197
198
199}