Cache service loaders (#4196)

* Extracting a Cache interface and building service loaders for Caffeine and Guava

* Fix estimated size interface

* Use LoadingCache when needed.

* Removing Caffeine from dependency lists.

* Adding hapi-fhir-cache-caffeine as a test dependency

* Putting caching solutions in a single module.

* Fixing the spacing

* Standardizing the use of TimeUnits

* Making a new module to simplify the switch of the cache library in tests.

* Making sure the Guava design matches the behavior of Caffeine.

* Making sure the Cache structure also does not throw InvalidCacheLoading exception to match the LoadingCache.

* Renaming module names for the caching group.

* Better error handing that informs devs what to do.

* Improving documentation

* Typo

* Matching error message design with Caffeine.

* Matching the behavior of Caffeine with Guava

* Final adjustments for the test dependencies on the cache modules.

* Fixing relative pom path.

* Adding caffeine as a testing requirement for the new modules.

* Add changelog and set JPA server to use caffeine cache

* POM fixes

* Build fix

* Buid fix

* Fixes

* Address review comment

* One more cache

* Move changelog to next release

* Update pom versions

* Build fix

* Build fixes

* Build fix

* Try to get build working

* Experiment with failing build

* Rever change

* Fix POM version

* Build fix

* Build fix

* Add Msg.code to new exceptions

Co-authored-by: Vitor Pamplona <vitor@vitorpamplona.com>
This commit is contained in:
James Agnew 2022-11-09 14:47:23 -05:00 committed by GitHub
parent f50d350f1f
commit 9a45576793
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 1152 additions and 409 deletions

View File

@ -80,6 +80,16 @@ stages:
module: hapi-fhir-server-mdm
- name: hapi_fhir_server_openapi
module: hapi-fhir-server-openapi
- name: hapi_fhir_serviceloaders
module: hapi-fhir-serviceloaders
- name: hapi_fhir_caching_api
module: hapi-fhir-serviceloaders/hapi-fhir-caching-api
- name: hapi_fhir_caching_caffeine
module: hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine
- name: hapi_fhir_caching_guava
module: hapi-fhir-serviceloaders/hapi-fhir-caching-guava
- name: hapi_fhir_caching_testing
module: hapi-fhir-serviceloaders/hapi-fhir-caching-testing
- name: hapi_fhir_spring_boot_autoconfigure
module: hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure
- name: hapi_fhir_spring_boot_sample_server_jersey

View File

@ -44,6 +44,13 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>

View File

@ -0,0 +1,6 @@
---
type: add
issue: 4196
title: "In-memory caching provided by Caffeine has been refactored into using
s ServiceLoader pattern in order to improve Android compatibility. Thanks to
Vitor Pamplona for the pull request!"

View File

@ -366,8 +366,15 @@
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-caffeine</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>

View File

@ -35,9 +35,9 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.UriParam;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.apache.commons.lang3.Validate;
import ca.uhn.fhir.sl.cache.Cache;
import ca.uhn.fhir.sl.cache.CacheFactory;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.CodeSystem;
@ -91,7 +91,7 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport
// TermReadSvcImpl calls these methods as a part of its "isCodeSystemSupported" calls.
// We should modify CachingValidationSupport to cache the results of "isXXXSupported"
// at which point we could do away with this cache
private Cache<String, IBaseResource> myLoadCache = Caffeine.newBuilder().maximumSize(1000).expireAfterWrite(1, TimeUnit.MINUTES).build();
private Cache<String, IBaseResource> myLoadCache = CacheFactory.build(TimeUnit.MINUTES.toMillis(1), 1000);
/**
* Constructor
@ -123,7 +123,9 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport
* version is always pointed by the ForcedId for the no-versioned CS
*/
private Optional<IBaseResource> getCodeSystemCurrentVersion(UriType theUrl) {
if (!theUrl.getValueAsString().contains(LOINC_LOW)) return Optional.empty();
if (!theUrl.getValueAsString().contains(LOINC_LOW)) {
return Optional.empty();
}
return myTermReadSvc.readCodeSystemByForcedId(LOINC_LOW);
}
@ -145,7 +147,9 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport
*/
private Optional<IBaseResource> getValueSetCurrentVersion(UriType theUrl) {
Optional<String> vsIdOpt = TermReadSvcUtil.getValueSetId(theUrl.getValueAsString());
if (!vsIdOpt.isPresent()) return Optional.empty();
if (!vsIdOpt.isPresent()) {
return Optional.empty();
}
IFhirResourceDao<? extends IBaseResource> valueSetResourceDao = myDaoRegistry.getResourceDao(myValueSetType);
IBaseResource valueSet = valueSetResourceDao.read(new IdDt("ValueSet", vsIdOpt.get()));
@ -188,8 +192,17 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport
private <T extends IBaseResource> IBaseResource doFetchResource(@Nullable Class<T> theClass, String theUri) {
if (theClass == null) {
Supplier<IBaseResource>[] fetchers = new Supplier[]{() -> doFetchResource(ValueSet.class, theUri), () -> doFetchResource(CodeSystem.class, theUri), () -> doFetchResource(StructureDefinition.class, theUri)};
return Arrays.stream(fetchers).map(t -> t.get()).filter(t -> t != myNoMatch).findFirst().orElse(myNoMatch);
Supplier<IBaseResource>[] fetchers = new Supplier[]{
() -> doFetchResource(ValueSet.class, theUri),
() -> doFetchResource(CodeSystem.class, theUri),
() -> doFetchResource(StructureDefinition.class, theUri)
};
return Arrays
.stream(fetchers)
.map(t -> t.get())
.filter(t -> t != myNoMatch)
.findFirst()
.orElse(myNoMatch);
}
IdType id = new IdType(theUri);

View File

@ -27,8 +27,8 @@ import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.interceptor.model.TransactionWriteOperationsDetails;
import ca.uhn.fhir.jpa.util.MemoryCacheService;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import ca.uhn.fhir.sl.cache.Cache;
import ca.uhn.fhir.sl.cache.CacheFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -61,10 +61,7 @@ public class TransactionConcurrencySemaphoreInterceptor {
*/
public TransactionConcurrencySemaphoreInterceptor(MemoryCacheService theMemoryCacheService) {
myMemoryCacheService = theMemoryCacheService;
mySemaphoreCache = Caffeine
.newBuilder()
.expireAfterAccess(1, TimeUnit.MINUTES)
.build();
mySemaphoreCache = CacheFactory.build(TimeUnit.MINUTES.toMillis(1));
}
/**

View File

@ -29,12 +29,12 @@ import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import org.apache.commons.lang3.Validate;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import ca.uhn.fhir.sl.cache.CacheFactory;
import ca.uhn.fhir.sl.cache.CacheLoader;
import ca.uhn.fhir.sl.cache.LoadingCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@ -78,14 +78,8 @@ public class PartitionLookupSvcImpl implements IPartitionLookupSvc {
@Override
@PostConstruct
public void start() {
myNameToPartitionCache = Caffeine
.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.build(new NameToPartitionCacheLoader());
myIdToPartitionCache = Caffeine
.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.build(new IdToPartitionCacheLoader());
myNameToPartitionCache = CacheFactory.build(TimeUnit.MINUTES.toMillis(1), new NameToPartitionCacheLoader());
myIdToPartitionCache = CacheFactory.build(TimeUnit.MINUTES.toMillis(1), new IdToPartitionCacheLoader());
myTxTemplate = new TransactionTemplate(myTxManager);
}

View File

@ -78,6 +78,8 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.sl.cache.Cache;
import ca.uhn.fhir.sl.cache.CacheFactory;
import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.FhirVersionIndependentConcept;
import ca.uhn.fhir.util.HapiExtensions;
@ -85,8 +87,6 @@ import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.util.ValidateUtil;
import ca.uhn.hapi.converters.canonical.VersionCanonicalizer;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Stopwatch;
import com.google.common.collect.ArrayListMultimap;
@ -193,25 +193,22 @@ import static org.apache.commons.lang3.StringUtils.startsWithIgnoreCase;
public class TermReadSvcImpl implements ITermReadSvc {
public static final int DEFAULT_FETCH_SIZE = 250;
public static final int DEFAULT_MASS_INDEXER_OBJECT_LOADING_THREADS = 2;
// doesn't seem to be much gain by using more threads than this value
public static final int MAX_MASS_INDEXER_OBJECT_LOADING_THREADS = 6;
private static final int SINGLE_FETCH_SIZE = 1;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TermReadSvcImpl.class);
private static final ValueSetExpansionOptions DEFAULT_EXPANSION_OPTIONS = new ValueSetExpansionOptions();
private static final TermCodeSystemVersionDetails NO_CURRENT_VERSION = new TermCodeSystemVersionDetails(-1L, null);
private static Runnable myInvokeOnNextCallForUnitTest;
private static boolean ourForceDisableHibernateSearchForUnitTest;
private static final String IDX_PROPERTIES = "myProperties";
private static final String IDX_PROP_KEY = IDX_PROPERTIES + ".myKey";
private static final String IDX_PROP_VALUE_STRING = IDX_PROPERTIES + ".myValueString";
private static final String IDX_PROP_DISPLAY_STRING = IDX_PROPERTIES + ".myDisplayString";
public static final int DEFAULT_MASS_INDEXER_OBJECT_LOADING_THREADS = 2;
// doesn't seem to be much gain by using more threads than this value
public static final int MAX_MASS_INDEXER_OBJECT_LOADING_THREADS = 6;
private boolean myPreExpandingValueSets = false;
private final Cache<String, TermCodeSystemVersionDetails> myCodeSystemCurrentVersionCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build();
private static final int SECONDS_IN_MINUTE = 60;
private static final int INDEXED_ROOTS_LOGGING_COUNT = 50_000;
private static Runnable myInvokeOnNextCallForUnitTest;
private static boolean ourForceDisableHibernateSearchForUnitTest;
private final Cache<String, TermCodeSystemVersionDetails> myCodeSystemCurrentVersionCache = CacheFactory.build(TimeUnit.MINUTES.toMillis(1));
@Autowired
protected DaoRegistry myDaoRegistry;
@Autowired
@ -232,6 +229,7 @@ public class TermReadSvcImpl implements ITermReadSvc {
protected FhirContext myContext;
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager;
private boolean myPreExpandingValueSets = false;
@Autowired
private ITermCodeSystemVersionDao myCodeSystemVersionDao;
@Autowired
@ -255,19 +253,17 @@ public class TermReadSvcImpl implements ITermReadSvc {
private ITermDeferredStorageSvc myDeferredStorageSvc;
@Autowired
private IIdHelperService myIdHelperService;
@Autowired
private ApplicationContext myApplicationContext;
private volatile IValidationSupport myJpaValidationSupport;
private volatile IValidationSupport myValidationSupport;
//We need this bean so we can tell which mode hibernate search is running in.
@Autowired
private HibernatePropertiesProvider myHibernatePropertiesProvider;
@Autowired
private CachingValidationSupport myCachingValidationSupport;
@Autowired
private VersionCanonicalizer myVersionCanonicalizer;
@Override
public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) {
@ -368,7 +364,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
myCodeSystemCurrentVersionCache.invalidateAll();
}
public void deleteValueSetForResource(ResourceTable theResourceTable) {
// Get existing entity so it can be deleted.
Optional<TermValueSet> optionalExistingTermValueSetById = myTermValueSetDao.findByResourcePid(theResourceTable.getId());
@ -394,7 +389,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
deleteValueSetForResource(theResourceTable);
}
@Override
@Transactional
public List<FhirVersionIndependentConcept> expandValueSetIntoConceptList(@Nullable ValueSetExpansionOptions theExpansionOptions, @Nonnull String theValueSetCanonicalUrl) {
@ -447,7 +441,7 @@ public class TermReadSvcImpl implements ITermReadSvc {
accumulator.addParameter().setName("count").setValue(new IntegerType(count));
}
myTxTemplate.executeWithoutResult(tx-> {
myTxTemplate.executeWithoutResult(tx -> {
expandValueSetIntoAccumulator(theValueSetToExpand, theExpansionOptions, accumulator, theFilter, true);
});
@ -590,7 +584,8 @@ public class TermReadSvcImpl implements ITermReadSvc {
//-- this is quick solution, may need to revisit
if (!applyFilter(display, filterDisplayValue)) {
continue;}
continue;
}
Long conceptPid = conceptView.getConceptPid();
if (!pidToConcept.containsKey(conceptPid)) {
@ -602,7 +597,7 @@ public class TermReadSvcImpl implements ITermReadSvc {
if (conceptView.getDesignationPid() != null) {
TermConceptDesignation designation = new TermConceptDesignation();
if(isValueSetDisplayLanguageMatch(theExpansionOptions, conceptView.getDesignationLang() )) {
if (isValueSetDisplayLanguageMatch(theExpansionOptions, conceptView.getDesignationLang())) {
designation.setUseSystem(conceptView.getDesignationUseSystem());
designation.setUseCode(conceptView.getDesignationUseCode());
designation.setUseDisplay(conceptView.getDesignationUseDisplay());
@ -660,19 +655,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
logConceptsExpanded("Finished expanding concepts. ", theTermValueSet, conceptsExpanded);
}
static boolean isValueSetDisplayLanguageMatch(ValueSetExpansionOptions theExpansionOptions, String theStoredLang){
if( theExpansionOptions == null) {
return true;
}
if(theExpansionOptions.getTheDisplayLanguage() == null || theStoredLang == null) {
return true;
}
return theExpansionOptions.getTheDisplayLanguage().equalsIgnoreCase(theStoredLang);
}
private void logConceptsExpanded(String theLogDescriptionPrefix, TermValueSet theTermValueSet, int theConceptsExpanded) {
if (theConceptsExpanded > 0) {
ourLog.debug("{}Have expanded {} concepts in ValueSet[{}]", theLogDescriptionPrefix, theConceptsExpanded, theTermValueSet.getUrl());
@ -730,7 +712,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
* Note: Not transactional because specific calls within this method
* get executed in a transaction
*/
@SuppressWarnings("ConstantConditions")
private void doExpandValueSet(ValueSetExpansionOptions theExpansionOptions, ValueSet theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator, @Nonnull ExpansionFilter theExpansionFilter) {
Set<String> addedCodes = new HashSet<>();
@ -752,7 +733,7 @@ public class TermReadSvcImpl implements ITermReadSvc {
for (ValueSet.ConceptSetComponent include : theValueSetToExpand.getCompose().getInclude()) {
myTxTemplate.executeWithoutResult(tx ->
expandValueSetHandleIncludeOrExclude(theExpansionOptions, theValueSetCodeAccumulator, addedCodes,
include, true, theExpansionFilter) );
include, true, theExpansionFilter));
}
// Handle excludes
@ -760,7 +741,7 @@ public class TermReadSvcImpl implements ITermReadSvc {
for (ValueSet.ConceptSetComponent exclude : theValueSetToExpand.getCompose().getExclude()) {
myTxTemplate.executeWithoutResult(tx ->
expandValueSetHandleIncludeOrExclude(theExpansionOptions, theValueSetCodeAccumulator, addedCodes,
exclude, false, ExpansionFilter.NO_FILTER) );
exclude, false, ExpansionFilter.NO_FILTER));
}
if (theValueSetCodeAccumulator instanceof ValueSetConceptAccumulator) {
@ -770,7 +751,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
ourLog.debug("Done working with {} in {}ms", valueSetInfo, sw.getMillis());
}
private String getValueSetInfo(ValueSet theValueSet) {
StringBuilder sb = new StringBuilder();
boolean isIdentified = false;
@ -841,7 +821,7 @@ public class TermReadSvcImpl implements ITermReadSvc {
org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent includeOrExclude = ValueSet40_50.convertConceptSetComponent(theIncludeOrExclude);
new InMemoryTerminologyServerValidationSupport(myContext).expandValueSetIncludeOrExclude(new ValidationSupportContext(provideValidationSupport()), consumer, includeOrExclude);
} catch (InMemoryTerminologyServerValidationSupport.ExpansionCouldNotBeCompletedInternallyException e) {
if (!theExpansionOptions.isFailOnMissingCodeSystem() && e.getFailureType() == InMemoryTerminologyServerValidationSupport.FailureType.UNKNOWN_CODE_SYSTEM) {
if (theExpansionOptions != null && !theExpansionOptions.isFailOnMissingCodeSystem() && e.getFailureType() == InMemoryTerminologyServerValidationSupport.FailureType.UNKNOWN_CODE_SYSTEM) {
return;
}
throw new InternalErrorException(Msg.code(888) + e);
@ -913,17 +893,19 @@ public class TermReadSvcImpl implements ITermReadSvc {
int count = 0;
Optional<Integer> chunkSizeOpt = getScrollChunkSize(theAdd, theValueSetCodeAccumulator);
if (chunkSizeOpt.isEmpty()) { return; }
if (chunkSizeOpt.isEmpty()) {
return;
}
int chunkSize = chunkSizeOpt.get();
SearchProperties searchProps = buildSearchScroll(termCodeSystemVersion, theExpansionFilter, theSystem,
theIncludeOrExclude, chunkSize, includeOrExcludeVersion);
int accumulatedBatchesSoFar = 0;
try ( SearchScroll<EntityReference> scroll = searchProps.getSearchScroll() ) {
try (SearchScroll<EntityReference> scroll = searchProps.getSearchScroll()) {
ourLog.debug("Beginning batch expansion for {} with max results per batch: {}", (theAdd ? "inclusion" : "exclusion"), chunkSize);
for ( SearchScrollResult<EntityReference> chunk = scroll.next(); chunk.hasHits(); chunk = scroll.next() ) {
for (SearchScrollResult<EntityReference> chunk = scroll.next(); chunk.hasHits(); chunk = scroll.next()) {
int countForBatch = 0;
List<Long> pids = chunk.hits()
@ -970,7 +952,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
}
}
private List<TermConcept> sortTermConcepts(SearchProperties searchProps, List<TermConcept> termConcepts) {
List<String> codes = searchProps.getIncludeOrExcludeCodes();
if (codes.size() > 1) {
@ -988,7 +969,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
return termConcepts;
}
private Optional<Integer> getScrollChunkSize(boolean theAdd, IValueSetConceptAccumulator theValueSetCodeAccumulator) {
int maxResultsPerBatch = SearchBuilder.getMaximumPageSize();
@ -1002,13 +982,9 @@ public class TermReadSvcImpl implements ITermReadSvc {
maxResultsPerBatch = Math.min(maxResultsPerBatch, accumulatorCapacityRemaining + 1);
}
}
return maxResultsPerBatch > 0 ? Optional.of(maxResultsPerBatch): Optional.empty();
return maxResultsPerBatch > 0 ? Optional.of(maxResultsPerBatch) : Optional.empty();
}
private SearchProperties buildSearchScroll(TermCodeSystemVersion theTermCodeSystemVersion,
ExpansionFilter theExpansionFilter,
String theSystem,
@ -1067,11 +1043,10 @@ public class TermReadSvcImpl implements ITermReadSvc {
.where(f -> finishedQuery)
.toQuery();
returnProps.setSearchScroll( termConceptsQuery.scroll(theScrollChunkSize) );
returnProps.setSearchScroll(termConceptsQuery.scroll(theScrollChunkSize));
return returnProps;
}
private ValueSet.ConceptReferenceComponent getMatchedConceptIncludedInValueSet(ValueSet.ConceptSetComponent theIncludeOrExclude, TermConcept concept) {
return theIncludeOrExclude
.getConcept()
@ -1084,11 +1059,13 @@ public class TermReadSvcImpl implements ITermReadSvc {
* Helper method which builds a predicate for the expansion
*/
private Optional<PredicateFinalStep> buildExpansionPredicate(List<String> theCodes, SearchPredicateFactory thePredicate) {
if (CollectionUtils.isEmpty(theCodes)) { return Optional.empty(); }
if (CollectionUtils.isEmpty(theCodes)) {
return Optional.empty();
}
if (theCodes.size() < BooleanQuery.getMaxClauseCount()) {
return Optional.of(thePredicate.simpleQueryString()
.field( "myCode" ).matching( String.join(" | ", theCodes)) );
.field("myCode").matching(String.join(" | ", theCodes)));
}
// Number of codes is larger than maxClauseCount, so we split the query in several clauses
@ -1106,7 +1083,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
return Optional.of(step);
}
private String buildCodeSystemUrlAndVersion(String theSystem, String theIncludeOrExcludeVersion) {
String codeSystemUrlAndVersion;
if (theIncludeOrExcludeVersion != null) {
@ -1184,7 +1160,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
theB.must(theF.match().field(term.field()).matching(term.text()));
}
private void handleFilterRegex(SearchPredicateFactory theF, BooleanPredicateClausesStep<?> theB, ValueSet.ConceptSetFilterComponent theFilter) {
/*
* We treat the regex filter as a match on the regex
@ -1206,10 +1181,9 @@ public class TermReadSvcImpl implements ITermReadSvc {
theB.must(theF.regexp()
.field(CONCEPT_PROPERTY_PREFIX_NAME + theFilter.getProperty())
.matching(value) );
.matching(value));
}
private void handleFilterLoincCopyright(SearchPredicateFactory theF, BooleanPredicateClausesStep<?> theB,
ValueSet.ConceptSetFilterComponent theFilter) {
@ -1238,7 +1212,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
theB.mustNot(theF.exists().field(CONCEPT_PROPERTY_PREFIX_NAME + "EXTERNAL_COPYRIGHT_NOTICE"));
}
private void addFilterLoincCopyright3rdParty(SearchPredicateFactory theF, BooleanPredicateClausesStep<?> theB) {
theB.must(theF.exists().field(CONCEPT_PROPERTY_PREFIX_NAME + "EXTERNAL_COPYRIGHT_NOTICE"));
}
@ -1276,7 +1249,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
b.must(f.bool(innerB -> terms.forEach(term -> innerB.should(f.match().field(term.field()).matching(term.text())))));
}
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
private void handleFilterLoincParentChild(SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) {
switch (theFilter.getOp()) {
@ -1370,7 +1342,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
return new Term(CONCEPT_PROPERTY_PREFIX_NAME + theProperty, theValue);
}
private List<Term> getAncestorTerms(String theSystem, String theProperty, String theValue) {
List<Term> retVal = new ArrayList<>();
@ -1383,7 +1354,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
return retVal;
}
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
private void handleFilterLoincDescendant(String theSystem, SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) {
switch (theFilter.getOp()) {
@ -1398,7 +1368,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
}
}
private void addLoincFilterDescendantEqual(String theSystem, SearchPredicateFactory f,
BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) {
@ -1406,7 +1375,7 @@ public class TermReadSvcImpl implements ITermReadSvc {
if (parentPids.isEmpty()) {
// Can't return empty must, because it wil match according to other predicates.
// Some day there will be a 'matchNone' predicate (https://discourse.hibernate.org/t/fail-fast-predicate/6062)
b.mustNot( f.matchAll() );
b.mustNot(f.matchAll());
return;
}
@ -1437,7 +1406,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
)));
}
/**
* Returns the list of parentId(s) of the TermConcept representing theValue as a code
*/
@ -1448,14 +1416,13 @@ public class TermReadSvcImpl implements ITermReadSvc {
String[] parentPids = code.getParentPidsAsString().split(" ");
List<Long> retVal = Arrays.stream(parentPids)
.filter( pid -> !StringUtils.equals(pid, "NONE") )
.filter(pid -> !StringUtils.equals(pid, "NONE"))
.map(Long::parseLong)
.collect(Collectors.toList());
logFilteringValueOnProperty(theValue, theProperty);
return retVal;
}
/**
* Returns the list of parentId(s) of the TermConcept representing theValue as a code
*/
@ -1470,7 +1437,7 @@ public class TermReadSvcImpl implements ITermReadSvc {
List<Long> retVal = termConcepts.stream()
.flatMap(tc -> Arrays.stream(tc.getParentPidsAsString().split(" ")))
.filter( pid -> !StringUtils.equals(pid, "NONE") )
.filter(pid -> !StringUtils.equals(pid, "NONE"))
.map(Long::parseLong)
.collect(Collectors.toList());
@ -1488,19 +1455,18 @@ public class TermReadSvcImpl implements ITermReadSvc {
return "Invalid filter criteria - More TermConcepts were found than indicated codes. Queried codes: [" +
join(",", theValues + "]; Obtained TermConcept IDs, codes: [" +
theTermConcepts.stream().map(tc -> tc.getId() + ", " + tc.getCode())
.collect(joining("; "))+ "]");
.collect(joining("; ")) + "]");
}
// case: less TermConcept(s) retrieved than codes queried
Set<String> matchedCodes = theTermConcepts.stream().map(TermConcept::getCode).collect(toSet());
List<String> notMatchedValues = theValues.stream()
.filter(v -> ! matchedCodes.contains (v)) .collect(toList());
.filter(v -> !matchedCodes.contains(v)).collect(toList());
return "Invalid filter criteria - No TermConcept(s) were found for the requested codes: [" +
join(",", notMatchedValues + "]");
}
private void logFilteringValueOnProperty(String theValue, String theProperty) {
ourLog.debug(" * Filtering with value={} on property {}", theValue, theProperty);
}
@ -1560,7 +1526,7 @@ public class TermReadSvcImpl implements ITermReadSvc {
Collection<TermConceptDesignation> designations = next
.getDesignation()
.stream()
.map(t->new TermConceptDesignation()
.map(t -> new TermConceptDesignation()
.setValue(t.getValue())
.setLanguage(t.getLanguage())
.setUseCode(t.getUse().getCode())
@ -1765,7 +1731,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
}
}
@Override
public Optional<TermConcept> findCode(String theCodeSystem, String theCode) {
/*
@ -1790,12 +1755,13 @@ public class TermReadSvcImpl implements ITermReadSvc {
@Transactional(propagation = Propagation.MANDATORY)
public List<TermConcept> findCodes(String theCodeSystem, List<String> theCodeList) {
TermCodeSystemVersionDetails csv = getCurrentCodeSystemVersion(theCodeSystem);
if (csv == null) { return Collections.emptyList(); }
if (csv == null) {
return Collections.emptyList();
}
return myConceptDao.findByCodeSystemAndCodeList(csv.myPid, theCodeList);
}
@Nullable
private TermCodeSystemVersionDetails getCurrentCodeSystemVersion(String theCodeSystemIdentifier) {
String version = getVersionFromIdentifier(theCodeSystemIdentifier);
@ -1821,19 +1787,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
return retVal;
}
private static class TermCodeSystemVersionDetails {
private final long myPid;
private final String myCodeSystemVersionId;
public TermCodeSystemVersionDetails(long thePid, String theCodeSystemVersionId) {
myPid = thePid;
myCodeSystemVersionId = theCodeSystemVersionId;
}
}
private String getVersionFromIdentifier(String theUri) {
String retVal = null;
if (StringUtils.isNotEmpty((theUri))) {
@ -2021,14 +1974,13 @@ public class TermReadSvcImpl implements ITermReadSvc {
myCachingValidationSupport.invalidateCaches();
}
private synchronized void setPreExpandingValueSets(boolean thePreExpandingValueSets) {
myPreExpandingValueSets = thePreExpandingValueSets;
}
private synchronized boolean isPreExpandingValueSets() {
return myPreExpandingValueSets;
}
private synchronized void setPreExpandingValueSets(boolean thePreExpandingValueSets) {
myPreExpandingValueSets = thePreExpandingValueSets;
}
private boolean isNotSafeToPreExpandValueSets() {
return myDeferredStorageSvc != null && !myDeferredStorageSvc.isStorageQueueEmpty(true);
@ -2430,7 +2382,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
return false;
}
@Nonnull
private FhirVersionIndependentConcept toConcept(IPrimitiveType<String> theCodeType, IPrimitiveType<String> theCodeSystemIdentifierType, IBaseCoding theCodingType) {
String code = theCodeType != null ? theCodeType.getValueAsString() : null;
@ -2446,7 +2397,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
return new FhirVersionIndependentConcept(system, code, null, systemVersion);
}
/**
* When the search is for unversioned loinc system it uses the forcedId to obtain the current
* version, as it is not necessarily the last one anymore.
@ -2471,16 +2421,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
return Optional.of(termValueSetList.get(0));
}
@Nonnull
private static String createMessageAppendForDisplayMismatch(String theCodeSystemUrl, String theDisplay, String theExpectedDisplay) {
return " - Concept Display \"" + theDisplay + "\" does not match expected \"" + theExpectedDisplay + "\" for CodeSystem: " + theCodeSystemUrl;
}
@Nonnull
private static String createMessageAppendForCodeNotFoundInCodeSystem(String theCodeSystemUrl) {
return " - Code is not found in CodeSystem: " + theCodeSystemUrl;
}
@Override
public Optional<IBaseResource> readCodeSystemByForcedId(String theForcedId) {
@SuppressWarnings("unchecked")
@ -2498,11 +2438,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
return Optional.of(cs);
}
private static final int SECONDS_IN_MINUTE = 60;
private static final int INDEXED_ROOTS_LOGGING_COUNT = 50_000;
@Transactional
@Override
public ReindexTerminologyResult reindexTerminology() throws InterruptedException {
@ -2523,14 +2458,14 @@ public class TermReadSvcImpl implements ITermReadSvc {
try {
SearchSession searchSession = getSearchSession();
searchSession
.massIndexer( TermConcept.class )
.dropAndCreateSchemaOnStart( true )
.purgeAllOnStart( false )
.batchSizeToLoadObjects( 100 )
.cacheMode( CacheMode.IGNORE )
.threadsToLoadObjects( 6 )
.transactionTimeout( 60 * SECONDS_IN_MINUTE )
.monitor( new PojoMassIndexingLoggingMonitor(INDEXED_ROOTS_LOGGING_COUNT) )
.massIndexer(TermConcept.class)
.dropAndCreateSchemaOnStart(true)
.purgeAllOnStart(false)
.batchSizeToLoadObjects(100)
.cacheMode(CacheMode.IGNORE)
.threadsToLoadObjects(6)
.transactionTimeout(60 * SECONDS_IN_MINUTE)
.monitor(new PojoMassIndexingLoggingMonitor(INDEXED_ROOTS_LOGGING_COUNT))
.startAndWait();
} finally {
myDeferredStorageSvc.setProcessDeferred(true);
@ -2539,13 +2474,11 @@ public class TermReadSvcImpl implements ITermReadSvc {
return ReindexTerminologyResult.SUCCESS;
}
@VisibleForTesting
boolean isBatchTerminologyTasksRunning() {
return isNotSafeToPreExpandValueSets() || isPreExpandingValueSets();
}
@VisibleForTesting
int calculateObjectLoadingThreadNumber() {
IConnectionPoolInfoProvider connectionPoolInfoProvider =
@ -2563,12 +2496,136 @@ public class TermReadSvcImpl implements ITermReadSvc {
return objectThreads;
}
@VisibleForTesting
SearchSession getSearchSession() {
return Search.session( myEntityManager );
return Search.session(myEntityManager);
}
@Override
public ValueSetExpansionOutcome expandValueSet(ValidationSupportContext theValidationSupportContext, ValueSetExpansionOptions theExpansionOptions, @Nonnull IBaseResource theValueSetToExpand) {
ValueSet canonicalInput = myVersionCanonicalizer.valueSetToCanonical(theValueSetToExpand);
org.hl7.fhir.r4.model.ValueSet expandedR4 = expandValueSet(theExpansionOptions, canonicalInput);
return new ValueSetExpansionOutcome(myVersionCanonicalizer.valueSetFromCanonical(expandedR4));
}
@Override
public IBaseResource expandValueSet(ValueSetExpansionOptions theExpansionOptions, IBaseResource theInput) {
org.hl7.fhir.r4.model.ValueSet valueSetToExpand = myVersionCanonicalizer.valueSetToCanonical(theInput);
org.hl7.fhir.r4.model.ValueSet valueSetR4 = expandValueSet(theExpansionOptions, valueSetToExpand);
return myVersionCanonicalizer.valueSetFromCanonical(valueSetR4);
}
@Override
public void expandValueSet(ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) {
org.hl7.fhir.r4.model.ValueSet valueSetToExpand = myVersionCanonicalizer.valueSetToCanonical(theValueSetToExpand);
expandValueSet(theExpansionOptions, valueSetToExpand, theValueSetCodeAccumulator);
}
private org.hl7.fhir.r4.model.ValueSet getValueSetFromResourceTable(ResourceTable theResourceTable) {
Class<? extends IBaseResource> type = getFhirContext().getResourceDefinition("ValueSet").getImplementingClass();
IBaseResource valueSet = myDaoRegistry.getResourceDao("ValueSet").toResource(type, theResourceTable, null, false);
return myVersionCanonicalizer.valueSetToCanonical(valueSet);
}
@Override
public CodeValidationResult validateCodeIsInPreExpandedValueSet(ConceptValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) {
ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet, "ValueSet must not be null");
org.hl7.fhir.r4.model.ValueSet valueSetR4 = myVersionCanonicalizer.valueSetToCanonical(theValueSet);
org.hl7.fhir.r4.model.Coding codingR4 = myVersionCanonicalizer.codingToCanonical((IBaseCoding) theCoding);
org.hl7.fhir.r4.model.CodeableConcept codeableConcept = myVersionCanonicalizer.codeableConceptToCanonical(theCodeableConcept);
return validateCodeIsInPreExpandedValueSet(theOptions, valueSetR4, theSystem, theCode, theDisplay, codingR4, codeableConcept);
}
@Override
public boolean isValueSetPreExpandedForCodeValidation(IBaseResource theValueSet) {
ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet, "ValueSet must not be null");
org.hl7.fhir.r4.model.ValueSet valueSetR4 = myVersionCanonicalizer.valueSetToCanonical(theValueSet);
return isValueSetPreExpandedForCodeValidation(valueSetR4);
}
@Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theDisplayLanguage) {
return lookupCode(theSystem, theCode, theDisplayLanguage);
}
private static class TermCodeSystemVersionDetails {
private final long myPid;
private final String myCodeSystemVersionId;
public TermCodeSystemVersionDetails(long thePid, String theCodeSystemVersionId) {
myPid = thePid;
myCodeSystemVersionId = theCodeSystemVersionId;
}
}
public static class Job implements HapiJob {
@Autowired
private ITermReadSvc myTerminologySvc;
@Override
public void execute(JobExecutionContext theContext) {
myTerminologySvc.preExpandDeferredValueSetsToTerminologyTables();
}
}
/**
* Properties returned from method buildSearchScroll
*/
private static final class SearchProperties {
private SearchScroll<EntityReference> mySearchScroll;
private Optional<PredicateFinalStep> myExpansionStepOpt;
private List<String> myIncludeOrExcludeCodes;
public SearchScroll<EntityReference> getSearchScroll() {
return mySearchScroll;
}
public void setSearchScroll(SearchScroll<EntityReference> theSearchScroll) {
mySearchScroll = theSearchScroll;
}
public Optional<PredicateFinalStep> getExpansionStepOpt() {
return myExpansionStepOpt;
}
public void setExpansionStepOpt(Optional<PredicateFinalStep> theExpansionStepOpt) {
myExpansionStepOpt = theExpansionStepOpt;
}
public List<String> getIncludeOrExcludeCodes() {
return myIncludeOrExcludeCodes;
}
public void setIncludeOrExcludeCodes(List<String> theIncludeOrExcludeCodes) {
myIncludeOrExcludeCodes = theIncludeOrExcludeCodes;
}
}
static boolean isValueSetDisplayLanguageMatch(ValueSetExpansionOptions theExpansionOptions, String theStoredLang) {
if (theExpansionOptions == null) {
return true;
}
if (theExpansionOptions.getTheDisplayLanguage() == null || theStoredLang == null) {
return true;
}
return theExpansionOptions.getTheDisplayLanguage().equalsIgnoreCase(theStoredLang);
}
@Nonnull
private static String createMessageAppendForDisplayMismatch(String theCodeSystemUrl, String theDisplay, String theExpectedDisplay) {
return " - Concept Display \"" + theDisplay + "\" does not match expected \"" + theExpectedDisplay + "\" for CodeSystem: " + theCodeSystemUrl;
}
@Nonnull
private static String createMessageAppendForCodeNotFoundInCodeSystem(String theCodeSystemUrl) {
return " - Code is not found in CodeSystem: " + theCodeSystemUrl;
}
@VisibleForTesting
public static void setForceDisableHibernateSearchForUnitTest(boolean theForceDisableHibernateSearchForUnitTest) {
@ -2669,101 +2726,5 @@ public class TermReadSvcImpl implements ITermReadSvc {
return theReqLang.equalsIgnoreCase(theStoredLang);
}
public static class Job implements HapiJob {
@Autowired
private ITermReadSvc myTerminologySvc;
@Override
public void execute(JobExecutionContext theContext) {
myTerminologySvc.preExpandDeferredValueSetsToTerminologyTables();
}
}
/**
* Properties returned from method buildSearchScroll
*/
private static final class SearchProperties {
private SearchScroll<EntityReference> mySearchScroll;
private Optional<PredicateFinalStep> myExpansionStepOpt;
private List<String> myIncludeOrExcludeCodes;
public SearchScroll<EntityReference> getSearchScroll() {
return mySearchScroll;
}
public void setSearchScroll(SearchScroll<EntityReference> theSearchScroll) {
mySearchScroll = theSearchScroll;
}
public Optional<PredicateFinalStep> getExpansionStepOpt() {
return myExpansionStepOpt;
}
public void setExpansionStepOpt(Optional<PredicateFinalStep> theExpansionStepOpt) {
myExpansionStepOpt = theExpansionStepOpt;
}
public List<String> getIncludeOrExcludeCodes() {
return myIncludeOrExcludeCodes;
}
public void setIncludeOrExcludeCodes(List<String> theIncludeOrExcludeCodes) {
myIncludeOrExcludeCodes = theIncludeOrExcludeCodes;
}
}
@Override
public ValueSetExpansionOutcome expandValueSet(ValidationSupportContext theValidationSupportContext, ValueSetExpansionOptions theExpansionOptions, @Nonnull IBaseResource theValueSetToExpand) {
ValueSet canonicalInput = myVersionCanonicalizer.valueSetToCanonical(theValueSetToExpand);
org.hl7.fhir.r4.model.ValueSet expandedR4 = expandValueSet(theExpansionOptions, canonicalInput);
return new ValueSetExpansionOutcome(myVersionCanonicalizer.valueSetFromCanonical(expandedR4));
}
@Override
public IBaseResource expandValueSet(ValueSetExpansionOptions theExpansionOptions, IBaseResource theInput) {
org.hl7.fhir.r4.model.ValueSet valueSetToExpand = myVersionCanonicalizer.valueSetToCanonical(theInput);
org.hl7.fhir.r4.model.ValueSet valueSetR4 = expandValueSet(theExpansionOptions, valueSetToExpand);
return myVersionCanonicalizer.valueSetFromCanonical(valueSetR4);
}
@Override
public void expandValueSet(ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) {
org.hl7.fhir.r4.model.ValueSet valueSetToExpand = myVersionCanonicalizer.valueSetToCanonical(theValueSetToExpand);
expandValueSet(theExpansionOptions, valueSetToExpand, theValueSetCodeAccumulator);
}
private org.hl7.fhir.r4.model.ValueSet getValueSetFromResourceTable(ResourceTable theResourceTable) {
Class<? extends IBaseResource> type = getFhirContext().getResourceDefinition("ValueSet").getImplementingClass();
IBaseResource valueSet = myDaoRegistry.getResourceDao("ValueSet").toResource(type, theResourceTable, null, false);
return myVersionCanonicalizer.valueSetToCanonical(valueSet);
}
@Override
public CodeValidationResult validateCodeIsInPreExpandedValueSet(ConceptValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) {
ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet, "ValueSet must not be null");
org.hl7.fhir.r4.model.ValueSet valueSetR4 = myVersionCanonicalizer.valueSetToCanonical(theValueSet);
org.hl7.fhir.r4.model.Coding codingR4 = myVersionCanonicalizer.codingToCanonical((IBaseCoding) theCoding);
org.hl7.fhir.r4.model.CodeableConcept codeableConcept = myVersionCanonicalizer.codeableConceptToCanonical(theCodeableConcept);
return validateCodeIsInPreExpandedValueSet(theOptions, valueSetR4, theSystem, theCode, theDisplay, codingR4, codeableConcept);
}
@Autowired
private VersionCanonicalizer myVersionCanonicalizer;
@Override
public boolean isValueSetPreExpandedForCodeValidation(IBaseResource theValueSet) {
ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet, "ValueSet must not be null");
org.hl7.fhir.r4.model.ValueSet valueSetR4 = myVersionCanonicalizer.valueSetToCanonical(theValueSet);
return isValueSetPreExpandedForCodeValidation(valueSetR4);
}
@Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theDisplayLanguage) {
return lookupCode(theSystem, theCode, theDisplayLanguage);
}
}

View File

@ -160,6 +160,12 @@
</exclusions>
</dependency>
<!-- test -->
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>

View File

@ -48,6 +48,12 @@
<groupId>org.hl7.fhir.testcases</groupId>
<artifactId>fhir-test-cases</artifactId>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>

View File

@ -42,6 +42,12 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>

View File

@ -116,8 +116,15 @@
<!-- Caffeine -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<!-- test dependencies -->

View File

@ -132,6 +132,12 @@
</dependency>
<!-- Testing -->
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
@ -154,6 +160,7 @@
</dependency>
</dependencies>
<build>
<pluginManagement>

View File

@ -24,9 +24,9 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.annotations.VisibleForTesting;
import ca.uhn.fhir.sl.cache.Cache;
import ca.uhn.fhir.sl.cache.CacheFactory;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.instance.model.api.IBase;
@ -93,10 +93,7 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
myFhirPathEngine = new FHIRPathEngine(worker);
myFhirPathEngine.setHostServices(new SearchParamExtractorR4HostServices());
myParsedFhirPathCache = Caffeine
.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
myParsedFhirPathCache = CacheFactory.build(TimeUnit.MINUTES.toMillis(10));
}

View File

@ -24,9 +24,9 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.annotations.VisibleForTesting;
import ca.uhn.fhir.sl.cache.Cache;
import ca.uhn.fhir.sl.cache.CacheFactory;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.instance.model.api.IBase;
@ -93,10 +93,7 @@ public class SearchParamExtractorR4B extends BaseSearchParamExtractor implements
myFhirPathEngine = new FHIRPathEngine(worker);
myFhirPathEngine.setHostServices(new SearchParamExtractorR4BHostServices());
myParsedFhirPathCache = Caffeine
.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
myParsedFhirPathCache = CacheFactory.build(TimeUnit.MINUTES.toMillis(10));
}

View File

@ -24,8 +24,8 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import ca.uhn.fhir.sl.cache.Cache;
import ca.uhn.fhir.sl.cache.CacheFactory;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.instance.model.api.IBase;
@ -81,10 +81,7 @@ public class SearchParamExtractorR5 extends BaseSearchParamExtractor implements
myFhirPathEngine = new FHIRPathEngine(worker);
myFhirPathEngine.setHostServices(new SearchParamExtractorR5HostServices());
myParsedFhirPathCache = Caffeine
.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
myParsedFhirPathCache = CacheFactory.build(TimeUnit.MINUTES.toMillis(10));
}
@Override

View File

@ -72,6 +72,12 @@
</dependency>
<!-- test dependencies -->
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>

View File

@ -21,6 +21,12 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -21,6 +21,12 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -21,6 +21,12 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -25,7 +25,7 @@ import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import com.github.benmanes.caffeine.cache.Cache;
import ca.uhn.fhir.sl.cache.Cache;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.ValueSet;

View File

@ -21,6 +21,13 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -21,6 +21,12 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -51,6 +51,12 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hl7.fhir.testcases</groupId>
@ -178,6 +184,7 @@
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path-assert</artifactId>

View File

@ -3,8 +3,8 @@ package ca.uhn.fhir.jpa.util;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.model.entity.TagDefinition;
import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import ca.uhn.fhir.sl.cache.Cache;
import ca.uhn.fhir.sl.cache.CacheFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
@ -77,7 +77,7 @@ class MemoryCacheServiceTest {
}
void withCacheOfSize(int theMaxSize) {
myCache = Caffeine.newBuilder().expireAfterWrite(60, TimeUnit.MINUTES).maximumSize(theMaxSize).build();
myCache = CacheFactory.build(TimeUnit.MINUTES.toMillis(60), theMaxSize);
}
void fillCacheWithRange(int theStart, int theEnd) {

View File

@ -107,8 +107,14 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>

View File

@ -25,6 +25,13 @@
<artifactId>org.hl7.fhir.utilities</artifactId>
<version>${fhir_core_version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<!-- Server -->
<dependency>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.3.0-PRE4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>hapi-fhir-caching-api</artifactId>
<packaging>jar</packaging>
<name>HAPI FHIR - ServiceLoaders - Caching API</name>
<dependencies>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-base</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
</plugins>
</build>
</project>

View File

@ -0,0 +1,34 @@
package ca.uhn.fhir.sl.cache;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
/**
* This interface is a blend between
* <a href="https://github.com/ben-manes/caffeine">Caffeine's Cache</a> and
* <a href="https://github.com/google/guava/wiki/CachesExplained"></a>Guava's Cache</a>.
*
* Please check their documentation for information in the methods below.
*/
public interface Cache<K, V> {
V getIfPresent(K key);
V get(K key, Function<? super K, ? extends V> mappingFunction);
Map<K, V> getAllPresent(Iterable<? extends K> keys);
void put(K key, V value);
void putAll(Map<? extends K, ? extends V> map);
void invalidate(K key);
void invalidateAll(Iterable<? extends K> keys);
void invalidateAll();
long estimatedSize();
void cleanUp();
}

View File

@ -0,0 +1,47 @@
package ca.uhn.fhir.sl.cache;
import ca.uhn.fhir.i18n.Msg;
import java.util.Iterator;
import java.util.ServiceLoader;
@SuppressWarnings("unchecked")
public class CacheFactory {
static ServiceLoader<CacheProvider> loader = ServiceLoader.load(CacheProvider.class);
public static Iterator<CacheProvider> providers(boolean refresh) {
if (refresh) {
loader.reload();
}
return loader.iterator();
}
public static <K, V> Cache<K, V> build(long timeoutMillis) {
if (providers(false).hasNext()) {
return providers(false).next().create(timeoutMillis);
}
throw new RuntimeException(Msg.code(2200) + "No Cache Service Providers found. Choose between hapi-fhir-caching-caffeine (Default) and hapi-fhir-caching-guava (Android)");
}
public static <K, V> LoadingCache<K, V> build(long timeoutMillis, CacheLoader<K, V> cacheLoader) {
if (providers(false).hasNext()) {
return providers(false).next().create(timeoutMillis, cacheLoader);
}
throw new RuntimeException(Msg.code(2201) + "No Cache Service Providers found. Choose between hapi-fhir-caching-caffeine (Default) and hapi-fhir-caching-guava (Android)");
}
public static <K, V> Cache<K, V> build(long timeoutMillis, long maximumSize) {
if (providers(false).hasNext()) {
return providers(false).next().create(timeoutMillis, maximumSize);
}
throw new RuntimeException(Msg.code(2202) + "No Cache Service Providers found. Choose between hapi-fhir-caching-caffeine (Default) and hapi-fhir-caching-guava (Android)");
}
public static <K, V> LoadingCache<K, V> build(long timeoutMillis, long maximumSize, CacheLoader<K, V> cacheLoader) {
if (providers(false).hasNext()) {
return providers(false).next().create(timeoutMillis, maximumSize, cacheLoader);
}
throw new RuntimeException(Msg.code(2203) + "No Cache Service Providers found. Choose between hapi-fhir-caching-caffeine (Default) and hapi-fhir-caching-guava (Android)");
}
}

View File

@ -0,0 +1,5 @@
package ca.uhn.fhir.sl.cache;
public interface CacheLoader<K, V> {
V load(K var1) throws Exception;
}

View File

@ -0,0 +1,11 @@
package ca.uhn.fhir.sl.cache;
public interface CacheProvider<K,V> {
Cache create(long timeoutMillis);
Cache create(long timeoutMillis, long maximumSize);
LoadingCache create(long timeoutMillis, CacheLoader<K,V> cacheLoader);
LoadingCache create(long timeoutMillis, long maximumSize, CacheLoader<K,V> cacheLoader);
}

View File

@ -0,0 +1,19 @@
package ca.uhn.fhir.sl.cache;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
/**
* This interface is a blend between
* <a href="https://github.com/ben-manes/caffeine">Caffeine's LoadingCache</a> and
* <a href="https://github.com/google/guava/wiki/CachesExplained"></a>Guava's LoadingCache</a>.
*
* Please check their documentation for information in the methods below.
*/
public interface LoadingCache<K, V> extends Cache<K, V> {
V get(K key);
Map<K, V> getAll(Iterable<? extends K> keys);
void refresh(K key);
}

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.3.0-PRE4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>hapi-fhir-caching-caffeine</artifactId>
<packaging>jar</packaging>
<name>HAPI FHIR - ServiceLoaders - Caching Caffeine</name>
<dependencies>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-api</artifactId>
<version>6.3.0-PRE4-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
</plugins>
</build>
</project>

View File

@ -0,0 +1,59 @@
package ca.uhn.fhir.sl.cache.caffeine;
import java.util.Map;
import java.util.function.Function;
public class CacheDelegator<K, V> implements ca.uhn.fhir.sl.cache.Cache<K, V> {
com.github.benmanes.caffeine.cache.Cache<K, V> cache;
public CacheDelegator(com.github.benmanes.caffeine.cache.Cache<K, V> impl) {
this.cache = impl;
}
@Override
public V getIfPresent(K key) {
return cache.getIfPresent(key);
}
@Override
public V get(K key, Function<? super K, ? extends V> mappingFunction) {
return cache.get(key, mappingFunction);
}
@Override
public Map<K, V> getAllPresent(Iterable<? extends K> keys) { return cache.getAllPresent(keys); }
@Override
public void put(K key, V value) {
cache.put(key, value);
}
@Override
public void putAll(Map<? extends K, ? extends V> map) {
cache.putAll(map);
}
@Override
public void invalidate(K key) { cache.invalidate(key); }
@Override
public void invalidateAll(Iterable<? extends K> keys) {
cache.invalidateAll(keys);
}
@Override
public void invalidateAll() {
cache.invalidateAll();
}
@Override
public long estimatedSize() {
return cache.estimatedSize();
}
@Override
public void cleanUp(){
cache.cleanUp();
}
}

View File

@ -0,0 +1,46 @@
package ca.uhn.fhir.sl.cache.caffeine;
import java.util.concurrent.TimeUnit;
import com.github.benmanes.caffeine.cache.Caffeine;
import ca.uhn.fhir.sl.cache.Cache;
import ca.uhn.fhir.sl.cache.CacheLoader;
import ca.uhn.fhir.sl.cache.LoadingCache;
public class CacheProvider<K,V> implements ca.uhn.fhir.sl.cache.CacheProvider<K,V> {
public Cache<K,V> create(long timeoutMillis) {
return new CacheDelegator<K,V>(
Caffeine.newBuilder()
.expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS)
.build()
);
}
public LoadingCache<K,V> create(long timeoutMillis, CacheLoader<K,V> loading) {
return new LoadingCacheDelegator<K,V>(
Caffeine.newBuilder()
.expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS)
.build(loading::load)
);
}
public Cache<K,V> create(long timeoutMillis, long maximumSize) {
return new CacheDelegator<K,V>(
Caffeine.newBuilder()
.expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS)
.maximumSize(maximumSize)
.build()
);
}
public LoadingCache<K,V> create(long timeoutMillis, long maximumSize, CacheLoader<K,V> loading) {
return new LoadingCacheDelegator<K,V>(
Caffeine.newBuilder()
.expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS)
.maximumSize(maximumSize)
.build(loading::load)
);
}
}

View File

@ -0,0 +1,29 @@
package ca.uhn.fhir.sl.cache.caffeine;
import java.util.Map;
import ca.uhn.fhir.sl.cache.LoadingCache;
public class LoadingCacheDelegator<K, V> extends CacheDelegator<K, V> implements LoadingCache<K,V> {
public LoadingCacheDelegator(com.github.benmanes.caffeine.cache.LoadingCache<K, V> impl) {
super(impl);
}
public com.github.benmanes.caffeine.cache.LoadingCache<K, V> getCache() {
return (com.github.benmanes.caffeine.cache.LoadingCache<K, V>) cache;
}
@Override
public V get(K key) {
return getCache().get(key);
}
@Override
public Map<K, V> getAll(Iterable<? extends K> keys) {
return getCache().getAll(keys);
}
@Override
public void refresh(K key) { getCache().refresh(key); }
}

View File

@ -0,0 +1 @@
ca.uhn.fhir.sl.cache.caffeine.CacheProvider

View File

@ -0,0 +1,17 @@
package ca.uhn.fhir.sl.cache.caffeine;
import static org.junit.jupiter.api.Assertions.assertNull;
import ca.uhn.fhir.sl.cache.CacheFactory;
import ca.uhn.fhir.sl.cache.LoadingCache;
import org.junit.jupiter.api.Test;
public class CacheLoaderTest {
@Test
void loaderReturnsNullTest() {
LoadingCache<String, String> cache = CacheFactory.build(1000, key -> {
return null;
});
assertNull(cache.get("1"));
}
}

View File

@ -0,0 +1,13 @@
package ca.uhn.fhir.sl.cache.caffeine;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import ca.uhn.fhir.sl.cache.CacheFactory;
import org.junit.jupiter.api.Test;
public class ServiceLoaderTest {
@Test
void loaderIsAvailable() {
assertNotNull(CacheFactory.build(1000));
}
}

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.3.0-PRE4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>hapi-fhir-caching-guava</artifactId>
<packaging>jar</packaging>
<name>HAPI FHIR - ServiceLoaders - Caching Guava</name>
<dependencies>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
</plugins>
</build>
</project>

View File

@ -0,0 +1,78 @@
package ca.uhn.fhir.sl.cache.guava;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
import ca.uhn.fhir.i18n.Msg;
import com.google.common.cache.CacheLoader;
import com.google.common.util.concurrent.UncheckedExecutionException;
public class CacheDelegator<K, V> implements ca.uhn.fhir.sl.cache.Cache<K, V> {
com.google.common.cache.Cache<K, V> cache;
public CacheDelegator(com.google.common.cache.Cache<K, V> impl) {
this.cache = impl;
}
@Override
public V getIfPresent(K key) {
return cache.getIfPresent(key);
}
@Override
public V get(K key, Function<? super K, ? extends V> mappingFunction) {
try {
return cache.get(key, () -> mappingFunction.apply(key));
} catch (ExecutionException e) {
throw new RuntimeException(Msg.code(2206) + "Failed to red from cache", e);
} catch (UncheckedExecutionException e) {
if (e.getCause() instanceof RuntimeException) {
// Unwrap exception to match Caffeine
throw (RuntimeException)e.getCause();
}
throw e;
} catch (CacheLoader.InvalidCacheLoadException e) {
// If the entry is not found or load as null, returns null instead of an exception
// This matches the behaviour of Caffeine
return null;
}
}
@Override
public Map<K, V> getAllPresent(Iterable<? extends K> keys) { return cache.getAllPresent(keys); }
@Override
public void put(K key, V value) {
cache.put(key, value);
}
@Override
public void putAll(Map<? extends K, ? extends V> map) {
cache.putAll(map);
}
@Override
public void invalidate(K key) { cache.invalidate(key); }
@Override
public void invalidateAll(Iterable<? extends K> keys) {
cache.invalidateAll(keys);
}
@Override
public void invalidateAll() {
cache.invalidateAll();
}
@Override
public long estimatedSize() {
return cache.size();
}
@Override
public void cleanUp(){
cache.cleanUp();
}
}

View File

@ -0,0 +1,56 @@
package ca.uhn.fhir.sl.cache.guava;
import java.util.concurrent.TimeUnit;
import com.google.common.cache.CacheBuilder;
import ca.uhn.fhir.sl.cache.Cache;
import ca.uhn.fhir.sl.cache.CacheLoader;
import ca.uhn.fhir.sl.cache.LoadingCache;
public class CacheProvider<K,V> implements ca.uhn.fhir.sl.cache.CacheProvider<K,V> {
public Cache<K,V> create(long timeoutMillis) {
return new CacheDelegator<K,V>(
CacheBuilder.newBuilder()
.expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS)
.build()
);
}
public LoadingCache<K,V> create(long timeoutMillis, CacheLoader<K,V> loading) {
return new LoadingCacheDelegator<K,V>(
CacheBuilder.newBuilder()
.expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS)
.build(new com.google.common.cache.CacheLoader<>() {
@Override
public V load(K k) throws Exception {
return loading.load(k);
}
})
);
}
public Cache<K,V> create(long timeoutMillis, long maximumSize) {
return new CacheDelegator<K,V>(
CacheBuilder.newBuilder()
.expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS)
.maximumSize(maximumSize)
.build()
);
}
public LoadingCache<K,V> create(long timeoutMillis, long maximumSize, CacheLoader<K,V> loading) {
return new LoadingCacheDelegator<K,V>(
CacheBuilder.newBuilder()
.expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS)
.maximumSize(maximumSize)
.build(new com.google.common.cache.CacheLoader<>() {
@Override
public V load(K k) throws Exception {
return loading.load(k);
}
})
);
}
}

View File

@ -0,0 +1,59 @@
package ca.uhn.fhir.sl.cache.guava;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import ca.uhn.fhir.i18n.Msg;
import com.google.common.cache.CacheLoader;
import com.google.common.util.concurrent.UncheckedExecutionException;
import ca.uhn.fhir.sl.cache.LoadingCache;
public class LoadingCacheDelegator<K, V> extends CacheDelegator<K, V> implements LoadingCache<K,V> {
public LoadingCacheDelegator(com.google.common.cache.LoadingCache<K, V> impl) { super(impl); }
public com.google.common.cache.LoadingCache<K, V> getCache() {
return (com.google.common.cache.LoadingCache<K, V>) cache;
}
@Override
public V get(K key) {
try {
return getCache().get(key);
} catch (UncheckedExecutionException e) {
if (e.getCause() instanceof RuntimeException) {
// Unwrap exception to match Caffeine
throw (RuntimeException)e.getCause();
}
throw e;
} catch (ExecutionException e) {
throw new RuntimeException(Msg.code(2204) + "Failed to red from cache", e);
} catch (CacheLoader.InvalidCacheLoadException e) {
// If the entry is not found or load as null, returns null instead of an exception.
// This matches the behaviour of Caffeine
return null;
}
}
@Override
public Map<K, V> getAll(Iterable<? extends K> keys) {
try {
return getCache().getAll(keys);
} catch (UncheckedExecutionException e) {
if (e.getCause() instanceof RuntimeException) {
// Unwrap exception to match Caffeine
throw (RuntimeException)e.getCause();
}
throw e;
} catch (ExecutionException e) {
throw new RuntimeException(Msg.code(2205) + "Failed to red from cache", e);
} catch (CacheLoader.InvalidCacheLoadException e) {
// If the entry is not found or load as null, returns null instead of an exception
// This matches the behaviour of Caffeine
return null;
}
}
@Override
public void refresh(K key) { getCache().refresh(key); }
}

View File

@ -0,0 +1 @@
ca.uhn.fhir.sl.cache.guava.CacheProvider

View File

@ -0,0 +1,17 @@
package ca.uhn.fhir.sl.cache.guava;
import static org.junit.jupiter.api.Assertions.assertNull;
import ca.uhn.fhir.sl.cache.CacheFactory;
import ca.uhn.fhir.sl.cache.LoadingCache;
import org.junit.jupiter.api.Test;
public class CacheLoaderTest {
@Test
void loaderReturnsNullTest() {
LoadingCache<String, String> cache = CacheFactory.build(1000, key -> {
return null;
});
assertNull(cache.get("1"));
}
}

View File

@ -0,0 +1,13 @@
package ca.uhn.fhir.sl.cache.guava;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import ca.uhn.fhir.sl.cache.CacheFactory;
import org.junit.jupiter.api.Test;
public class ServiceLoaderTest {
@Test
void loaderIsAvailable() {
assertNotNull(CacheFactory.build(1000));
}
}

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>hapi-fhir</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.3.0-PRE4-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<!-- This module exists to facilitate running tests with caffeine and guava.
By changing the dependency below, all projects switch to the preferred lib -->
<artifactId>hapi-fhir-caching-testing</artifactId>
<packaging>jar</packaging>
<name>HAPI FHIR - ServiceLoaders - Caching Testing</name>
<dependencies>
<!-- Change here (and run mvn install, mvn test -U) to run test cases with different cache library -->
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-caffeine</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
</plugins>
</build>
</project>

View File

@ -0,0 +1,24 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>hapi-fhir</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.3.0-PRE4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<packaging>pom</packaging>
<name>HAPI FHIR - ServiceLoaders</name>
<modules>
<module>hapi-fhir-caching-api</module>
<module>hapi-fhir-caching-guava</module>
<module>hapi-fhir-caching-caffeine</module>
<module>hapi-fhir-caching-testing</module>
</modules>
</project>

View File

@ -16,10 +16,6 @@
<description>Tooling for migrating SQL schemas.</description>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
@ -85,6 +81,8 @@
<artifactId>sqlbuilder</artifactId>
</dependency>
<!-- test -->
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-test-utilities</artifactId>

View File

@ -59,6 +59,11 @@
<artifactId>hapi-fhir-structures-hl7org-dstu2</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-api</artifactId>
<version>${project.version}</version>
</dependency>
<!-- TODO KHS remove jpa stuff from here -->
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>

View File

@ -23,10 +23,11 @@ package ca.uhn.fhir.jpa.util;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.model.TranslationQuery;
import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.time.DateUtils;
import ca.uhn.fhir.sl.cache.Cache;
import ca.uhn.fhir.sl.cache.CacheFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
@ -35,6 +36,7 @@ import javax.annotation.Nonnull;
import javax.annotation.PostConstruct;
import java.util.EnumMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import static java.util.concurrent.TimeUnit.MINUTES;
@ -87,10 +89,7 @@ public class MemoryCacheService {
break;
}
Cache<Object, Object> nextCache = Caffeine.newBuilder()
.expireAfterWrite(timeoutSeconds, SECONDS)
.maximumSize(maximumSize)
.build();
Cache<Object, Object> nextCache = CacheFactory.build(SECONDS.toMillis(timeoutSeconds), maximumSize);
myCaches.put(next, nextCache);
}

View File

@ -126,6 +126,11 @@
<artifactId>org.hl7.fhir.dstu3</artifactId>
<version>${fhir_core_version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.fhir</groupId>
<artifactId>ucum</artifactId>
@ -197,15 +202,14 @@
<optional>true</optional>
</dependency>
<!-- Used by the validator -->
<!-- Testing -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<optional>true</optional>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId>

View File

@ -7,10 +7,10 @@ import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.util.CoverageIgnore;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.time.DateUtils;
import ca.uhn.fhir.sl.cache.Cache;
import ca.uhn.fhir.sl.cache.CacheFactory;
import org.hl7.fhir.dstu3.context.IWorkerContext;
import org.hl7.fhir.dstu3.formats.IParser;
import org.hl7.fhir.dstu3.formats.ParserType;
@ -43,7 +43,6 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -64,7 +63,7 @@ public final class HapiWorkerContext extends I18nBase implements IWorkerContext
if (System.getProperties().containsKey(Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS)) {
timeoutMillis = Long.parseLong(System.getProperty(Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS));
}
myFetchedResourceCache = Caffeine.newBuilder().expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS).build();
myFetchedResourceCache = CacheFactory.build(timeoutMillis);
// Set a default locale
setValidationMessageLanguage(getLocale());

View File

@ -40,6 +40,11 @@
<artifactId>org.hl7.fhir.r4</artifactId>
<version>${fhir_core_version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.fhir</groupId>
<artifactId>ucum</artifactId>
@ -76,12 +81,6 @@
<optional>true</optional>
</dependency>
<!-- Used by the validator -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<optional>true</optional>
</dependency>
<!--
Test dependencies on other optional parts of HAPI
@ -174,6 +173,13 @@
</dependency>
<!-- Testing -->
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId>
@ -259,6 +265,7 @@
<scope>test</scope>
</dependency>
<!-- UNIT TEST DEPENDENCIES -->
<dependency>
<groupId>net.sf.json-lib</groupId>

View File

@ -7,11 +7,11 @@ import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.util.CoverageIgnore;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.time.DateUtils;
import org.fhir.ucum.UcumService;
import ca.uhn.fhir.sl.cache.Cache;
import ca.uhn.fhir.sl.cache.CacheFactory;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.r4.context.IWorkerContext;
@ -66,7 +66,7 @@ public final class HapiWorkerContext extends I18nBase implements IWorkerContext
timeoutMillis = Long.parseLong(System.getProperty(Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS));
}
myFetchedResourceCache = Caffeine.newBuilder().expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS).build();
myFetchedResourceCache = CacheFactory.build(timeoutMillis);
// Set a default locale
setValidationMessageLanguage(getLocale());

View File

@ -16,12 +16,13 @@
<dependencies>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-base</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-base</artifactId>
<artifactId>hapi-fhir-caching-api</artifactId>
<version>${project.version}</version>
</dependency>
@ -76,13 +77,6 @@
<optional>true</optional>
</dependency>
<!-- Used by the validator -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<optional>true</optional>
</dependency>
<!--
Test dependencies on other optional parts of HAPI
-->

View File

@ -1,15 +1,15 @@
package org.hl7.fhir.r4b.hapi.ctx;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.sl.cache.Cache;
import ca.uhn.fhir.sl.cache.CacheFactory;
import ca.uhn.fhir.util.CoverageIgnore;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.time.DateUtils;
import org.fhir.ucum.UcumService;
@ -49,7 +49,6 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -71,7 +70,7 @@ public final class HapiWorkerContext extends I18nBase implements IWorkerContext
timeoutMillis = Long.parseLong(System.getProperty(Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS));
}
myFetchedResourceCache = Caffeine.newBuilder().expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS).build();
myFetchedResourceCache = CacheFactory.build(timeoutMillis);
// Set a default locale
setValidationMessageLanguage(getLocale());

View File

@ -35,6 +35,11 @@
<artifactId>org.hl7.fhir.r5</artifactId>
<version>${fhir_core_version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.fhir</groupId>
<artifactId>ucum</artifactId>
@ -76,13 +81,6 @@
<optional>true</optional>
</dependency>
<!-- Used by the validator -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<optional>true</optional>
</dependency>
<!--
Test dependencies on other optional parts of HAPI
-->
@ -154,6 +152,12 @@
</dependency>
<!-- Testing -->
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId>

View File

@ -7,11 +7,11 @@ import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.util.CoverageIgnore;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.time.DateUtils;
import org.fhir.ucum.UcumService;
import ca.uhn.fhir.sl.cache.Cache;
import ca.uhn.fhir.sl.cache.CacheFactory;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.r5.context.IWorkerContext;
@ -71,7 +71,7 @@ public final class HapiWorkerContext extends I18nBase implements IWorkerContext
timeoutMillis = Long.parseLong(System.getProperty(Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS));
}
myFetchedResourceCache = Caffeine.newBuilder().expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS).build();
myFetchedResourceCache = CacheFactory.build(timeoutMillis);
// Set a default locale
setValidationMessageLanguage(getLocale());

View File

@ -26,6 +26,17 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-caffeine</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.validation</artifactId>
@ -80,10 +91,6 @@
<artifactId>ucum</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>

View File

@ -6,10 +6,10 @@ import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.TranslateConceptResults;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.apache.commons.lang3.time.DateUtils;
import ca.uhn.fhir.sl.cache.Cache;
import ca.uhn.fhir.sl.cache.CacheFactory;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.slf4j.Logger;
@ -63,31 +63,11 @@ public class CachingValidationSupport extends BaseValidationSupportWrapper imple
*/
public CachingValidationSupport(IValidationSupport theWrap, CacheTimeouts theCacheTimeouts) {
super(theWrap.getFhirContext(), theWrap);
myExpandValueSetCache = Caffeine
.newBuilder()
.expireAfterWrite(theCacheTimeouts.getExpandValueSetMillis(), TimeUnit.MILLISECONDS)
.maximumSize(100)
.build();
myValidateCodeCache = Caffeine
.newBuilder()
.expireAfterWrite(theCacheTimeouts.getValidateCodeMillis(), TimeUnit.MILLISECONDS)
.maximumSize(5000)
.build();
myLookupCodeCache = Caffeine
.newBuilder()
.expireAfterWrite(theCacheTimeouts.getLookupCodeMillis(), TimeUnit.MILLISECONDS)
.maximumSize(5000)
.build();
myTranslateCodeCache = Caffeine
.newBuilder()
.expireAfterWrite(theCacheTimeouts.getTranslateCodeMillis(), TimeUnit.MILLISECONDS)
.maximumSize(5000)
.build();
myCache = Caffeine
.newBuilder()
.expireAfterWrite(theCacheTimeouts.getMiscMillis(), TimeUnit.MILLISECONDS)
.maximumSize(5000)
.build();
myExpandValueSetCache = CacheFactory.build(theCacheTimeouts.getExpandValueSetMillis(), 100);
myValidateCodeCache = CacheFactory.build(theCacheTimeouts.getValidateCodeMillis(), 5000);
myLookupCodeCache = CacheFactory.build(theCacheTimeouts.getLookupCodeMillis(), 5000);
myTranslateCodeCache = CacheFactory.build(theCacheTimeouts.getTranslateCodeMillis(), 5000);
myCache = CacheFactory.build(theCacheTimeouts.getMiscMillis(), 5000);
myNonExpiringCache = Collections.synchronizedMap(new HashMap<>());
LinkedBlockingQueue<Runnable> executorQueue = new LinkedBlockingQueue<>(1000);

View File

@ -9,13 +9,14 @@ import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.time.DateUtils;
import org.fhir.ucum.UcumService;
import ca.uhn.fhir.sl.cache.Cache;
import ca.uhn.fhir.sl.cache.CacheFactory;
import ca.uhn.fhir.sl.cache.LoadingCache;
import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_10_50;
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50;
import org.hl7.fhir.exceptions.FHIRException;
@ -75,10 +76,7 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
timeoutMillis = Long.parseLong(System.getProperty(Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS));
}
myFetchResourceCache = Caffeine.newBuilder()
.expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS)
.maximumSize(10000)
.build(key -> {
myFetchResourceCache = CacheFactory.build(timeoutMillis, 10000, key -> {
String fetchResourceName = key.getResourceName();
if (myValidationSupportContext.getRootValidationSupport().getFhirContext().getVersion().getVersion() == FhirVersionEnum.DSTU2) {

18
pom.xml
View File

@ -92,6 +92,7 @@
<module>hapi-fhir-structures-r5</module>
<module>hapi-fhir-validation-resources-r5</module>
<module>hapi-fhir-jpa</module>
<module>hapi-fhir-serviceloaders</module>
<module>hapi-fhir-storage</module>
<module>hapi-fhir-storage-batch2</module>
<module>hapi-fhir-storage-batch2-jobs</module>
@ -824,6 +825,11 @@
<developer>
<id>dyoung-work</id>
</developer>
<developer>
<id>vitorpamplona</id>
<name>Vitor Pamplona</name>
<organization>PathCheck Foundation / EyeNetra Inc</organization>
</developer>
</developers>
<licenses>
@ -1300,7 +1306,7 @@
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
<version>8.0.31</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
@ -2091,6 +2097,11 @@
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.0</version>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
@ -2183,11 +2194,6 @@
<artifactId>maven-plugin-plugin</artifactId>
<version>3.6.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.2.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>