001package org.hl7.fhir.r4.terminologies; 002 003/* 004Copyright (c) 2011+, HL7, Inc 005All rights reserved. 006 007Redistribution and use in source and binary forms, with or without modification, 008are 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 019THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028POSSIBILITY OF SUCH DAMAGE. 029 030*/ 031 032import java.io.File; 033import java.io.FileInputStream; 034import java.io.FileOutputStream; 035import java.io.IOException; 036import java.util.HashMap; 037import java.util.Map; 038 039import org.apache.commons.io.IOUtils; 040import org.hl7.fhir.r4.context.IWorkerContext; 041import org.hl7.fhir.r4.formats.IParser.OutputStyle; 042import org.hl7.fhir.r4.model.ExpansionProfile; 043import org.hl7.fhir.r4.model.MetadataResource; 044import org.hl7.fhir.r4.model.OperationOutcome; 045import org.hl7.fhir.r4.model.Resource; 046import org.hl7.fhir.r4.model.ValueSet; 047import org.hl7.fhir.r4.terminologies.ValueSetExpander.TerminologyServiceErrorClass; 048import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome; 049import org.hl7.fhir.r4.utils.ToolingExtensions; 050import org.hl7.fhir.exceptions.FHIRFormatError; 051import org.hl7.fhir.utilities.Utilities; 052import org.hl7.fhir.utilities.xhtml.XhtmlComposer; 053 054public class ValueSetExpansionCache implements ValueSetExpanderFactory { 055 056 public class CacheAwareExpander implements ValueSetExpander { 057 058 @Override 059 public ValueSetExpansionOutcome expand(ValueSet source, ExpansionProfile profile) throws ETooCostly, IOException { 060 String cacheKey = makeCacheKey(source, profile); 061 if (expansions.containsKey(cacheKey)) 062 return expansions.get(cacheKey); 063 ValueSetExpander vse = new ValueSetExpanderSimple(context, ValueSetExpansionCache.this); 064 ValueSetExpansionOutcome vso = vse.expand(source, profile); 065 if (vso.getError() != null) { 066 // well, we'll see if the designated server can expand it, and if it can, we'll cache it locally 067 vso = context.expandVS(source, false, profile == null || !profile.getExcludeNested()); 068 if (cacheFolder != null) { 069 FileOutputStream s = new FileOutputStream(Utilities.path(cacheFolder, makeFileName(source.getUrl()))); 070 context.newXmlParser().setOutputStyle(OutputStyle.PRETTY).compose(s, vso.getValueset()); 071 s.close(); 072 } 073 } 074 if (vso.getValueset() != null) 075 expansions.put(cacheKey, vso); 076 return vso; 077 } 078 079 private String makeCacheKey(ValueSet source, ExpansionProfile profile) { 080 return profile == null ? source.getUrl() : source.getUrl() + " " + profile.getUrl()+" "+profile.getExcludeNested(); 081 } 082 083 } 084 085 private static final String VS_ID_EXT = "http://tools/cache"; 086 087 private final Map<String, ValueSetExpansionOutcome> expansions = new HashMap<String, ValueSetExpansionOutcome>(); 088 private final Map<String, MetadataResource> canonicals = new HashMap<String, MetadataResource>(); 089 private final IWorkerContext context; 090 private final String cacheFolder; 091 092 private Object lock; 093 094 public ValueSetExpansionCache(IWorkerContext context, Object lock) { 095 super(); 096 cacheFolder = null; 097 this.lock = lock; 098 this.context = context; 099 } 100 101 public ValueSetExpansionCache(IWorkerContext context, String cacheFolder, Object lock) throws FHIRFormatError, IOException { 102 super(); 103 this.context = context; 104 this.cacheFolder = cacheFolder; 105 this.lock = lock; 106 if (this.cacheFolder != null) 107 loadCache(); 108 } 109 110 private String makeFileName(String url) { 111 return url.replace("$", "").replace(":", "").replace("|", ".").replace("//", "/").replace("/", "_")+".xml"; 112 } 113 114 private void loadCache() throws FHIRFormatError, IOException { 115 File[] files = new File(cacheFolder).listFiles(); 116 for (File f : files) { 117 if (f.getName().endsWith(".xml")) { 118 final FileInputStream is = new FileInputStream(f); 119 try { 120 Resource r = context.newXmlParser().setOutputStyle(OutputStyle.PRETTY).parse(is); 121 if (r instanceof OperationOutcome) { 122 OperationOutcome oo = (OperationOutcome) r; 123 expansions.put(ToolingExtensions.getExtension(oo,VS_ID_EXT).getValue().toString(), 124 new ValueSetExpansionOutcome(new XhtmlComposer(XhtmlComposer.XML, false).composePlainText(oo.getText().getDiv()), TerminologyServiceErrorClass.UNKNOWN)); 125 } else if (r instanceof ValueSet) { 126 ValueSet vs = (ValueSet) r; 127 if (vs.hasExpansion()) 128 expansions.put(vs.getUrl(), new ValueSetExpansionOutcome(vs)); 129 else { 130 canonicals.put(vs.getUrl(), vs); 131 if (vs.hasVersion()) 132 canonicals.put(vs.getUrl()+"|"+vs.getVersion(), vs); 133 } 134 } else if (r instanceof MetadataResource) { 135 MetadataResource md = (MetadataResource) r; 136 canonicals.put(md.getUrl(), md); 137 if (md.hasVersion()) 138 canonicals.put(md.getUrl()+"|"+md.getVersion(), md); 139 } 140 } finally { 141 IOUtils.closeQuietly(is); 142 } 143 } 144 } 145 } 146 147 @Override 148 public ValueSetExpander getExpander() { 149 return new CacheAwareExpander(); 150 // return new ValueSetExpander(valuesets, codesystems); 151 } 152 153 public MetadataResource getStoredResource(String canonicalUri) { 154 synchronized (lock) { 155 return canonicals.get(canonicalUri); 156 } 157 } 158 159 public void storeResource(MetadataResource md) throws IOException { 160 synchronized (lock) { 161 canonicals.put(md.getUrl(), md); 162 if (md.hasVersion()) 163 canonicals.put(md.getUrl()+"|"+md.getVersion(), md); 164 } 165 if (cacheFolder != null) { 166 FileOutputStream s = new FileOutputStream(Utilities.path(cacheFolder, makeFileName(md.getUrl()+"|"+md.getVersion()))); 167 context.newXmlParser().setOutputStyle(OutputStyle.PRETTY).compose(s, md); 168 s.close(); 169 } 170 } 171 172 173}