001package org.hl7.fhir.common.hapi.validation.support; 002 003import ca.uhn.fhir.context.BaseRuntimeChildDefinition; 004import ca.uhn.fhir.context.support.ConceptValidationOptions; 005import ca.uhn.fhir.context.support.IValidationSupport; 006import ca.uhn.fhir.context.support.ValidationSupportContext; 007import com.github.benmanes.caffeine.cache.Cache; 008import com.github.benmanes.caffeine.cache.Caffeine; 009import org.checkerframework.checker.nullness.qual.Nullable; 010import org.hl7.fhir.instance.model.api.IBase; 011import org.hl7.fhir.instance.model.api.IBaseResource; 012import org.hl7.fhir.instance.model.api.IPrimitiveType; 013import org.slf4j.Logger; 014import org.slf4j.LoggerFactory; 015 016import javax.annotation.Nonnull; 017import java.util.List; 018import java.util.Optional; 019import java.util.concurrent.TimeUnit; 020import java.util.function.Function; 021 022import static org.apache.commons.lang3.StringUtils.defaultIfBlank; 023import static org.apache.commons.lang3.StringUtils.defaultString; 024import static org.apache.commons.lang3.StringUtils.isNotBlank; 025 026@SuppressWarnings("unchecked") 027public class CachingValidationSupport extends BaseValidationSupportWrapper implements IValidationSupport { 028 029 private static final Logger ourLog = LoggerFactory.getLogger(CachingValidationSupport.class); 030 private final Cache<String, Object> myCache; 031 private final Cache<String, Object> myValidateCodeCache; 032 private final Cache<String, Object> myLookupCodeCache; 033 034 035 public CachingValidationSupport(IValidationSupport theWrap) { 036 super(theWrap.getFhirContext(), theWrap); 037 myValidateCodeCache = Caffeine 038 .newBuilder() 039 .expireAfterWrite(10, TimeUnit.MINUTES) 040 .maximumSize(5000) 041 .build(); 042 myLookupCodeCache = Caffeine 043 .newBuilder() 044 .expireAfterWrite(10, TimeUnit.MINUTES) 045 .maximumSize(5000) 046 .build(); 047 myCache = Caffeine 048 .newBuilder() 049 .expireAfterWrite(10, TimeUnit.MINUTES) 050 .maximumSize(5000) 051 .build(); 052 } 053 054 @Override 055 public List<IBaseResource> fetchAllConformanceResources() { 056 String key = "fetchAllConformanceResources"; 057 return loadFromCache(myCache, key, t -> super.fetchAllConformanceResources()); 058 } 059 060 @Override 061 public <T extends IBaseResource> List<T> fetchAllStructureDefinitions() { 062 String key = "fetchAllStructureDefinitions"; 063 return loadFromCache(myCache, key, t -> super.fetchAllStructureDefinitions()); 064 } 065 066 @Override 067 public <T extends IBaseResource> T fetchResource(Class<T> theClass, String theUri) { 068 return loadFromCache(myCache, "fetchResource " + theClass.getName() + " " + theUri, 069 t -> super.fetchResource(theClass, theUri)); 070 } 071 072 @Override 073 public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { 074 String key = "isCodeSystemSupported " + theSystem; 075 Boolean retVal = loadFromCache(myCache, key, t -> super.isCodeSystemSupported(theValidationSupportContext, theSystem)); 076 assert retVal != null; 077 return retVal; 078 } 079 080 @Override 081 public CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { 082 String key = "validateCode " + theCodeSystem + " " + theCode + " " + defaultIfBlank(theValueSetUrl, "NO_VS"); 083 return loadFromCache(myValidateCodeCache, key, t -> super.validateCode(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, theValueSetUrl)); 084 } 085 086 @Override 087 public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) { 088 String key = "lookupCode " + theSystem + " " + theCode; 089 return loadFromCache(myLookupCodeCache, key, t -> super.lookupCode(theValidationSupportContext, theSystem, theCode)); 090 } 091 092 @SuppressWarnings("OptionalAssignedToNull") 093 @Nullable 094 private <T> T loadFromCache(Cache theCache, String theKey, Function<String, T> theLoader) { 095 ourLog.trace("Fetching from cache: {}", theKey); 096 097 Function<String, Optional<T>> loaderWrapper = key -> Optional.ofNullable(theLoader.apply(theKey)); 098 Optional<T> result = (Optional<T>) theCache.get(theKey, loaderWrapper); 099 assert result != null; 100 101 return result.orElse(null); 102 103 } 104 105 @Override 106 public IValidationSupport.CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theValidationOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { 107 108 BaseRuntimeChildDefinition urlChild = myCtx.getResourceDefinition(theValueSet).getChildByName("url"); 109 Optional<String> valueSetUrl = urlChild.getAccessor().getValues(theValueSet).stream().map(t -> ((IPrimitiveType<?>) t).getValueAsString()).filter(t->isNotBlank(t)).findFirst(); 110 if (valueSetUrl.isPresent()) { 111 String key = "validateCodeInValueSet " + theValidationOptions.toString() + " " + defaultString(theCodeSystem, "(null)") + " " + defaultString(theCode, "(null)") + " " + defaultString(theDisplay, "(null)") + " " + valueSetUrl.get(); 112 return loadFromCache(myValidateCodeCache, key, t-> super.validateCodeInValueSet(theValidationSupportContext, theValidationOptions, theCodeSystem, theCode, theDisplay, theValueSet)); 113 } 114 115 return super.validateCodeInValueSet(theValidationSupportContext, theValidationOptions, theCodeSystem, theCode, theDisplay, theValueSet); 116 } 117 118 @Override 119 public void invalidateCaches() { 120 myLookupCodeCache.invalidateAll(); 121 myCache.invalidateAll(); 122 myValidateCodeCache.invalidateAll(); 123 } 124}