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}