001package org.hl7.fhir.r5.context;
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.File;
035import java.io.FileNotFoundException;
036import java.io.FileOutputStream;
037import java.io.IOException;
038import java.io.OutputStreamWriter;
039import java.util.*;
040
041import lombok.AccessLevel;
042import lombok.Getter;
043import lombok.Setter;
044import lombok.experimental.Accessors;
045import org.apache.commons.lang3.StringUtils;
046import org.hl7.fhir.exceptions.FHIRException;
047import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult;
048import org.hl7.fhir.r5.formats.IParser.OutputStyle;
049import org.hl7.fhir.r5.formats.JsonParser;
050import org.hl7.fhir.r5.model.*;
051import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
052import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
053import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent;
054import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
055import org.hl7.fhir.r5.terminologies.ValueSetExpander.TerminologyServiceErrorClass;
056import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
057import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
058import org.hl7.fhir.utilities.TextFile;
059import org.hl7.fhir.utilities.Utilities;
060import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
061import org.hl7.fhir.utilities.validation.ValidationOptions;
062
063import com.google.gson.JsonElement;
064import com.google.gson.JsonObject;
065import com.google.gson.JsonPrimitive;
066
067/**
068 * This implements a two level cache. 
069 *  - a temporary cache for remembering previous local operations
070 *  - a persistent cache for remembering tx server operations
071 *  
072 * the cache is a series of pairs: a map, and a list. the map is the loaded cache, the list is the persistent cache, carefully maintained in order for version control consistency
073 * 
074 * @author graha
075 *
076 */
077public class TerminologyCache {
078  public static final boolean TRANSIENT = false;
079  public static final boolean PERMANENT = true;
080  private static final String NAME_FOR_NO_SYSTEM = "all-systems";
081  private static final String ENTRY_MARKER = "-------------------------------------------------------------------------------------";
082  private static final String BREAK = "####";
083
084  private static final String CACHE_FILE_EXTENSION = ".cache";
085
086  private static final String CAPABILITY_STATEMENT_TITLE = ".capabilityStatement";
087  private static final String TERMINOLOGY_CAPABILITIES_TITLE = ".terminologyCapabilities";
088
089  @Getter
090  private int requestCount;
091  @Getter
092  private int hitCount;
093  @Getter
094  private int networkCount;
095
096  public class CacheToken {
097    @Getter
098    private String name;
099    private String key;
100    @Getter
101    private String request;
102    @Accessors(fluent = true)
103    @Getter
104    private boolean hasVersion;
105
106    public void setName(String n) {
107      if (name == null)
108        name = n;
109      else if (!n.equals(name))
110        name = NAME_FOR_NO_SYSTEM;
111    }
112  }
113
114  private class CacheEntry {
115    private String request;
116    private boolean persistent;
117    private ValidationResult v;
118    private ValueSetExpansionOutcome e;
119  }
120  
121  private class NamedCache {
122    private String name; 
123    private List<CacheEntry> list = new ArrayList<CacheEntry>(); // persistent entries
124    private Map<String, CacheEntry> map = new HashMap<String, CacheEntry>();
125  }
126  
127
128  private Object lock;
129  private String folder;
130
131  private CapabilityStatement capabilityStatementCache = null;
132
133  public boolean hasCapabilityStatement() {
134    return capabilityStatementCache != null;
135  }
136
137  public CapabilityStatement getCapabilityStatement() {
138    return capabilityStatementCache;
139  }
140
141  public void cacheCapabilityStatement(CapabilityStatement capabilityStatement) {
142    if (noCaching) {
143      return;
144    }
145    this.capabilityStatementCache = capabilityStatement;
146    save(capabilityStatementCache, CAPABILITY_STATEMENT_TITLE);
147  }
148
149  private TerminologyCapabilities terminologyCapabilitiesCache = null;
150
151  public boolean hasTerminologyCapabilities() {
152    return terminologyCapabilitiesCache != null;
153  }
154
155  public TerminologyCapabilities getTerminologyCapabilities() {
156    return terminologyCapabilitiesCache;
157  }
158
159  public void cacheTerminologyCapabilities(TerminologyCapabilities terminologyCapabilities) {
160    if (noCaching) {
161      return;
162    }
163    this.terminologyCapabilitiesCache = terminologyCapabilities;
164    save(terminologyCapabilitiesCache, TERMINOLOGY_CAPABILITIES_TITLE);
165  }
166
167  private Map<String, NamedCache> caches = new HashMap<String, NamedCache>();
168  @Getter @Setter
169  private static boolean noCaching;
170  
171  @Getter @Setter
172  private static boolean cacheErrors;
173
174  // use lock from the context
175  public TerminologyCache(Object lock, String folder) throws FileNotFoundException, IOException, FHIRException {
176    super();
177    this.lock = lock;
178    this.folder = folder;
179    requestCount = 0;
180    hitCount = 0;
181    networkCount = 0;
182
183    if (folder != null) {
184      load();
185  }
186  }
187  
188  public void clear() {
189    caches.clear();
190  }
191
192  public CacheToken generateValidationToken(ValidationOptions options, Coding code, ValueSet vs) {
193    CacheToken ct = new CacheToken();
194    if (code.hasSystem()) {
195      ct.name = getNameForSystem(code.getSystem());
196      ct.hasVersion = code.hasVersion();
197    }
198    else
199      ct.name = NAME_FOR_NO_SYSTEM;
200    nameCacheToken(vs, ct);
201    JsonParser json = new JsonParser();
202    json.setOutputStyle(OutputStyle.PRETTY);
203    if (vs != null && vs.hasUrl() && vs.hasVersion()) {
204      try {
205        ct.request = "{\"code\" : "+json.composeString(code, "codeableConcept")+", \"url\": \""+Utilities.escapeJson(vs.getUrl())
206            +"\", \"version\": \""+Utilities.escapeJson(vs.getVersion())+"\""+(options == null ? "" : ", "+options.toJson())+"}\r\n";      
207      } catch (IOException e) {
208        throw new Error(e);
209      }
210    } else {
211      ValueSet vsc = getVSEssense(vs);
212      try {
213        ct.request = "{\"code\" : "+json.composeString(code, "code")+", \"valueSet\" :"+(vsc == null ? "null" : extracted(json, vsc))+(options == null ? "" : ", "+options.toJson())+"}";
214      } catch (IOException e) {
215        throw new Error(e);
216      }
217    }
218    ct.key = String.valueOf(hashJson(ct.request));
219    return ct;
220  }
221
222  public String extracted(JsonParser json, ValueSet vsc) throws IOException {
223    String s = null;
224    if (vsc.getExpansion().getContains().size() > 1000 || vsc.getCompose().getIncludeFirstRep().getConcept().size() > 1000) {      
225      s =  vsc.getUrl();
226    } else {
227      s = json.composeString(vsc);
228    }
229    return s;
230  }
231
232  public CacheToken generateValidationToken(ValidationOptions options, CodeableConcept code, ValueSet vs) {
233    CacheToken ct = new CacheToken();
234    for (Coding c : code.getCoding()) {
235      if (c.hasSystem()) {
236        ct.setName(getNameForSystem(c.getSystem()));
237        ct.hasVersion = c.hasVersion();
238      }
239    }
240    nameCacheToken(vs, ct);
241    JsonParser json = new JsonParser();
242    json.setOutputStyle(OutputStyle.PRETTY);
243    if (vs != null && vs.hasUrl() && vs.hasVersion()) {
244      try {
245       ct.request = "{\"code\" : "+json.composeString(code, "codeableConcept")+", \"url\": \""+Utilities.escapeJson(vs.getUrl())+
246           "\", \"version\": \""+Utilities.escapeJson(vs.getVersion())+"\""+(options == null ? "" : ", "+options.toJson())+"+}\r\n";      
247      } catch (IOException e) {
248        throw new Error(e);
249      }
250    } else {
251      ValueSet vsc = getVSEssense(vs);
252      try {
253        ct.request = "{\"code\" : "+json.composeString(code, "codeableConcept")+", \"valueSet\" :"+extracted(json, vsc)+(options == null ? "" : ", "+options.toJson())+"}";
254      } catch (IOException e) {
255        throw new Error(e);
256      }
257    }
258    ct.key = String.valueOf(hashJson(ct.request));
259    return ct;
260  }
261  
262  public ValueSet getVSEssense(ValueSet vs) {
263    if (vs == null)
264      return null;
265    ValueSet vsc = new ValueSet();
266    vsc.setCompose(vs.getCompose());
267    if (vs.hasExpansion()) {
268      vsc.getExpansion().getParameter().addAll(vs.getExpansion().getParameter());
269      vsc.getExpansion().getContains().addAll(vs.getExpansion().getContains());
270    }
271    return vsc;
272  }
273
274  public CacheToken generateExpandToken(ValueSet vs, boolean hierarchical) {
275    CacheToken ct = new CacheToken();
276    nameCacheToken(vs, ct);
277    if (vs.hasUrl() && vs.hasVersion()) {
278      ct.request = "{\"hierarchical\" : "+(hierarchical ? "true" : "false")+", \"url\": \""+Utilities.escapeJson(vs.getUrl())+"\", \"version\": \""+Utilities.escapeJson(vs.getVersion())+"\"}\r\n";      
279    } else {
280      ValueSet vsc = getVSEssense(vs);
281      JsonParser json = new JsonParser();
282      json.setOutputStyle(OutputStyle.PRETTY);
283      try {
284      ct.request = "{\"hierarchical\" : "+(hierarchical ? "true" : "false")+", \"valueSet\" :"+extracted(json, vsc)+"}\r\n";
285      } catch (IOException e) {
286        throw new Error(e);
287      }
288    }
289    ct.key = String.valueOf(hashJson(ct.request));
290    return ct;
291  }
292
293  public void nameCacheToken(ValueSet vs, CacheToken ct) {
294    if (vs != null) {
295      for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
296        if (inc.hasSystem()) {
297          ct.setName(getNameForSystem(inc.getSystem()));
298          ct.hasVersion = inc.hasVersion();
299        }
300      }
301      for (ConceptSetComponent inc : vs.getCompose().getExclude()) {
302        if (inc.hasSystem()) {
303          ct.setName(getNameForSystem(inc.getSystem()));
304          ct.hasVersion = inc.hasVersion();
305        }
306      }
307      for (ValueSetExpansionContainsComponent inc : vs.getExpansion().getContains()) {
308        if (inc.hasSystem()) {
309          ct.setName(getNameForSystem(inc.getSystem()));
310          ct.hasVersion = inc.hasVersion();
311        }
312      }
313    }
314  }
315
316  private String getNameForSystem(String system) {
317    if (system.equals("http://snomed.info/sct"))
318      return "snomed";
319    if (system.equals("http://www.nlm.nih.gov/research/umls/rxnorm"))
320      return "rxnorm";
321    if (system.equals("http://loinc.org"))
322      return "loinc";
323    if (system.equals("http://unitsofmeasure.org"))
324      return "ucum";
325    if (system.startsWith("http://hl7.org/fhir/sid/"))
326      return system.substring(24).replace("/", "");
327    if (system.startsWith("urn:iso:std:iso:"))
328      return "iso"+system.substring(16).replace(":", "");
329    if (system.startsWith("http://terminology.hl7.org/CodeSystem/"))
330      return system.substring(38).replace("/", "");
331    if (system.startsWith("http://hl7.org/fhir/"))
332      return system.substring(20).replace("/", "");
333    if (system.equals("urn:ietf:bcp:47"))
334      return "lang";
335    if (system.equals("urn:ietf:bcp:13"))
336      return "mimetypes";
337    if (system.equals("urn:iso:std:iso:11073:10101"))
338      return "11073";
339    if (system.equals("http://dicom.nema.org/resources/ontology/DCM"))
340      return "dicom";
341    return system.replace("/", "_").replace(":", "_").replace("?", "X").replace("#", "X");
342  }
343
344  public NamedCache getNamedCache(CacheToken cacheToken) {
345
346    final String cacheName = cacheToken.name == null ? "null" : cacheToken.name;
347
348    NamedCache nc = caches.get(cacheName);
349
350    if (nc == null) {
351      nc = new NamedCache();
352      nc.name = cacheName;
353      caches.put(nc.name, nc);
354    }
355    return nc;
356  }
357  
358  public ValueSetExpansionOutcome getExpansion(CacheToken cacheToken) {
359    synchronized (lock) {
360      NamedCache nc = getNamedCache(cacheToken);
361      CacheEntry e = nc.map.get(cacheToken.key);
362      if (e == null)
363        return null;
364      else
365        return e.e;
366    }
367  }
368
369  public void cacheExpansion(CacheToken cacheToken, ValueSetExpansionOutcome res, boolean persistent) {
370    synchronized (lock) {      
371      NamedCache nc = getNamedCache(cacheToken);
372      CacheEntry e = new CacheEntry();
373      e.request = cacheToken.request;
374      e.persistent = persistent;
375      e.e = res;
376      store(cacheToken, persistent, nc, e);
377    }    
378  }
379
380  public void store(CacheToken cacheToken, boolean persistent, NamedCache nc, CacheEntry e) {
381    if (noCaching) {
382      return;
383    }
384
385    if ( !cacheErrors &&
386      ( e.v!= null
387        && e.v.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED
388        && !cacheToken.hasVersion)) {
389      return;
390    }
391
392    boolean n = nc.map.containsKey(cacheToken.key);
393    nc.map.put(cacheToken.key, e);
394    if (persistent) {
395      if (n) {
396        for (int i = nc.list.size()- 1; i>= 0; i--) {
397          if (nc.list.get(i).request.equals(e.request)) {
398            nc.list.remove(i);
399          }
400        }
401      }
402      nc.list.add(e);
403      save(nc);  
404    }
405  }
406
407  public ValidationResult getValidation(CacheToken cacheToken) {
408    if (cacheToken.key == null) {
409      return null;
410    }
411    synchronized (lock) {
412      requestCount++;
413      NamedCache nc = getNamedCache(cacheToken);
414      CacheEntry e = nc.map.get(cacheToken.key);
415      if (e == null) {
416        networkCount++;
417        return null;
418      } else {
419        hitCount++;
420        return e.v;
421    }
422  }
423  }
424
425  public void cacheValidation(CacheToken cacheToken, ValidationResult res, boolean persistent) {
426    if (cacheToken.key != null) {
427      synchronized (lock) {      
428        NamedCache nc = getNamedCache(cacheToken);
429        CacheEntry e = new CacheEntry();
430        e.request = cacheToken.request;
431        e.persistent = persistent;
432        e.v = res;
433        store(cacheToken, persistent, nc, e);
434      }    
435    }
436  }
437
438  
439  // persistence
440  
441  public void save() {
442    
443  }
444  
445  private <K extends Resource> void save(K resource, String title) {
446    if (folder == null)
447      return;
448
449    try {
450      OutputStreamWriter sw = new OutputStreamWriter(new FileOutputStream(Utilities.path(folder, title + CACHE_FILE_EXTENSION)), "UTF-8");
451
452      JsonParser json = new JsonParser();
453      json.setOutputStyle(OutputStyle.PRETTY);
454
455      sw.write(json.composeString(resource).trim());
456      sw.close();
457    } catch (Exception e) {
458      System.out.println("error saving capability statement "+e.getMessage());
459    }
460  }
461
462  private void save(NamedCache nc) {
463    if (folder == null)
464      return;
465    
466    try {
467      OutputStreamWriter sw = new OutputStreamWriter(new FileOutputStream(Utilities.path(folder, nc.name+CACHE_FILE_EXTENSION)), "UTF-8");
468      sw.write(ENTRY_MARKER+"\r\n");
469      JsonParser json = new JsonParser();
470      json.setOutputStyle(OutputStyle.PRETTY);
471      for (CacheEntry ce : nc.list) {
472        sw.write(ce.request.trim());
473        sw.write(BREAK+"\r\n");
474        if (ce.e != null) {
475          sw.write("e: {\r\n");
476          if (ce.e.getValueset() != null)
477            sw.write("  \"valueSet\" : "+json.composeString(ce.e.getValueset()).trim()+",\r\n");
478          sw.write("  \"error\" : \""+Utilities.escapeJson(ce.e.getError()).trim()+"\"\r\n}\r\n");
479        } else {
480          sw.write("v: {\r\n");
481          boolean first = true;
482          if (ce.v.getDisplay() != null) {            
483            if (first) first = false; else sw.write(",\r\n");
484            sw.write("  \"display\" : \""+Utilities.escapeJson(ce.v.getDisplay()).trim()+"\"");
485          }
486          if (ce.v.getCode() != null) {
487            if (first) first = false; else sw.write(",\r\n");
488            sw.write("  \"code\" : \""+Utilities.escapeJson(ce.v.getCode()).trim()+"\"");
489          }
490          if (ce.v.getSystem() != null) {
491            if (first) first = false; else sw.write(",\r\n");
492            sw.write("  \"system\" : \""+Utilities.escapeJson(ce.v.getSystem()).trim()+"\"");
493          }
494          if (ce.v.getSeverity() != null) {
495            if (first) first = false; else sw.write(",\r\n");
496            sw.write("  \"severity\" : "+"\""+ce.v.getSeverity().toCode().trim()+"\""+"");
497          }
498          if (ce.v.getMessage() != null) {
499            if (first) first = false; else sw.write(",\r\n");
500            sw.write("  \"error\" : \""+Utilities.escapeJson(ce.v.getMessage()).trim()+"\"");
501          }
502          if (ce.v.getErrorClass() != null) {
503            if (first) first = false; else sw.write(",\r\n");
504            sw.write("  \"class\" : \""+Utilities.escapeJson(ce.v.getErrorClass().toString())+"\"");
505          }
506          if (ce.v.getDefinition() != null) {
507            if (first) first = false; else sw.write(",\r\n");
508            sw.write("  \"definition\" : \""+Utilities.escapeJson(ce.v.getDefinition()).trim()+"\"");
509          }
510          sw.write("\r\n}\r\n");
511        }
512        sw.write(ENTRY_MARKER+"\r\n");
513      }      
514      sw.close();
515    } catch (Exception e) {
516      System.out.println("error saving "+nc.name+": "+e.getMessage());
517    }
518  }
519
520  private boolean isCapabilityCache(String fn) {
521    if (fn == null) {
522      return false;
523    }
524    return fn.startsWith(CAPABILITY_STATEMENT_TITLE) || fn.startsWith(TERMINOLOGY_CAPABILITIES_TITLE);
525  }
526
527  private void loadCapabilityCache(String fn) {
528        try {
529          String src = TextFile.fileToString(Utilities.path(folder, fn));
530
531      JsonObject o = (JsonObject) new com.google.gson.JsonParser().parse(src);
532      Resource resource = new JsonParser().parse(o);
533
534      if (fn.startsWith(CAPABILITY_STATEMENT_TITLE)) {
535        this.capabilityStatementCache = (CapabilityStatement) resource;
536      } else if (fn.startsWith(TERMINOLOGY_CAPABILITIES_TITLE)) {
537        this.terminologyCapabilitiesCache = (TerminologyCapabilities) resource;
538      }
539    } catch (Exception e) {
540      throw new FHIRException("Error loading " + fn + ": " + e.getMessage(), e);
541    }
542  }
543
544
545
546  private CacheEntry getCacheEntry(String request, String resultString) throws IOException {
547              CacheEntry ce = new CacheEntry();
548              ce.persistent = true;
549    ce.request = request;
550    boolean e = resultString.charAt(0) == 'e';
551    resultString = resultString.substring(3);
552    JsonObject o = (JsonObject) new com.google.gson.JsonParser().parse(resultString);
553              String error = loadJS(o.get("error"));
554              if (e) {
555                if (o.has("valueSet"))
556                  ce.e = new ValueSetExpansionOutcome((ValueSet) new JsonParser().parse(o.getAsJsonObject("valueSet")), error, TerminologyServiceErrorClass.UNKNOWN);
557                else
558                  ce.e = new ValueSetExpansionOutcome(error, TerminologyServiceErrorClass.UNKNOWN);
559              } else {
560                String t = loadJS(o.get("severity"));
561                IssueSeverity severity = t == null ? null :  IssueSeverity.fromCode(t);
562                String display = loadJS(o.get("display"));
563                String code = loadJS(o.get("code"));
564                String system = loadJS(o.get("system"));
565                String definition = loadJS(o.get("definition"));
566                t = loadJS(o.get("class"));
567                TerminologyServiceErrorClass errorClass = t == null ? null : TerminologyServiceErrorClass.valueOf(t) ;
568                ce.v = new ValidationResult(severity, error, system, new ConceptDefinitionComponent().setDisplay(display).setDefinition(definition).setCode(code)).setErrorClass(errorClass);
569              }
570    return ce;
571  }
572
573  private void loadNamedCache(String fn) {
574    int c = 0;
575    try {
576      String src = TextFile.fileToString(Utilities.path(folder, fn));
577      String title = fn.substring(0, fn.lastIndexOf("."));
578
579      NamedCache nc = new NamedCache();
580      nc.name = title;
581
582      if (src.startsWith("?"))
583        src = src.substring(1);
584      int i = src.indexOf(ENTRY_MARKER);
585      while (i > -1) {
586        c++;
587        String s = src.substring(0, i);
588        src = src.substring(i + ENTRY_MARKER.length() + 1);
589        i = src.indexOf(ENTRY_MARKER);
590        if (!Utilities.noString(s)) {
591          int j = s.indexOf(BREAK);
592          String request = s.substring(0, j);
593          String p = s.substring(j + BREAK.length() + 1).trim();
594
595          CacheEntry cacheEntry = getCacheEntry(request, p);
596
597          nc.map.put(String.valueOf(hashJson(cacheEntry.request)), cacheEntry);
598          nc.list.add(cacheEntry);
599            }
600        caches.put(nc.name, nc);
601          }        
602        } catch (Exception e) {
603          System.out.println("Error loading "+fn+": "+e.getMessage()+" entry "+c+" - ignoring it");
604          e.printStackTrace();
605        }
606      }
607
608  private void load() throws FHIRException {
609    for (String fn : new File(folder).list()) {
610      if (fn.endsWith(CACHE_FILE_EXTENSION) && !fn.equals("validation" + CACHE_FILE_EXTENSION)) {
611        try {
612          if (isCapabilityCache(fn)) {
613            loadCapabilityCache(fn);
614          } else {
615            loadNamedCache(fn);
616          }
617        } catch (FHIRException e) {
618          throw e;
619        }
620      }
621    }
622  }
623  
624  private String loadJS(JsonElement e) {
625    if (e == null)
626      return null;
627    if (!(e instanceof JsonPrimitive))
628      return null;
629    String s = e.getAsString();
630    if ("".equals(s))
631      return null;
632    return s;
633  }
634
635  protected String hashJson(String s) {
636    s = StringUtils.remove(s, ' ');
637    s = StringUtils.remove(s, '\n');
638    s = StringUtils.remove(s, '\r');
639    return String.valueOf(s.hashCode());
640  }
641
642  // management
643  
644  public TerminologyCache copy() {
645    // TODO Auto-generated method stub
646    return null;
647  }
648  
649  public String summary(ValueSet vs) {
650    if (vs == null)
651      return "null";
652    
653    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
654    for (ConceptSetComponent cc : vs.getCompose().getInclude())
655      b.append("Include "+getIncSummary(cc));
656    for (ConceptSetComponent cc : vs.getCompose().getExclude())
657      b.append("Exclude "+getIncSummary(cc));
658    return b.toString();
659  }
660
661  private String getIncSummary(ConceptSetComponent cc) {
662    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
663    for (UriType vs : cc.getValueSet())
664      b.append(vs.asStringValue());
665    String vsd = b.length() > 0 ? " where the codes are in the value sets ("+b.toString()+")" : "";
666    String system = cc.getSystem();
667    if (cc.hasConcept())
668      return Integer.toString(cc.getConcept().size())+" codes from "+system+vsd;
669    if (cc.hasFilter()) {
670      String s = "";
671      for (ConceptSetFilterComponent f : cc.getFilter()) {
672        if (!Utilities.noString(s))
673          s = s + " & ";
674        s = s + f.getProperty()+" "+(f.hasOp() ? f.getOp().toCode() : "?")+" "+f.getValue();
675      }
676      return "from "+system+" where "+s+vsd;
677    }
678    return "All codes from "+system+vsd;
679  }
680
681  public String summary(Coding code) {
682    return code.getSystem()+"#"+code.getCode()+": \""+code.getDisplay()+"\"";
683  }
684
685  public String summary(CodeableConcept code) {
686    StringBuilder b = new StringBuilder();
687    b.append("{");
688    boolean first = true;
689    for (Coding c : code.getCoding()) {
690      if (first) first = false; else b.append(",");
691      b.append(summary(c));
692    }
693    b.append("}: \"");
694    b.append(code.getText());
695    b.append("\"");
696    return b.toString();
697  }
698
699  public void removeCS(String url) {
700    synchronized (lock) {
701      String name = getNameForSystem(url);
702      if (caches.containsKey(name)) {
703        caches.remove(name);
704      }
705    }   
706  }
707
708  public String getFolder() {
709    return folder;
710  }
711 
712  
713}