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 module: hapi-fhir-server-mdm
- name: hapi_fhir_server_openapi - name: hapi_fhir_server_openapi
module: 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 - name: hapi_fhir_spring_boot_autoconfigure
module: hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure module: hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure
- name: hapi_fhir_spring_boot_sample_server_jersey - name: hapi_fhir_spring_boot_sample_server_jersey

View File

@ -38,12 +38,19 @@
<artifactId>hapi-fhir-sql-migrate</artifactId> <artifactId>hapi-fhir-sql-migrate</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-test-utilities</artifactId> <artifactId>hapi-fhir-test-utilities</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <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>
<dependency> <dependency>
<groupId>com.github.ben-manes.caffeine</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>caffeine</artifactId> <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>
<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.StringParam;
import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.UriParam; 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 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.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.CodeSystem; 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. // TermReadSvcImpl calls these methods as a part of its "isCodeSystemSupported" calls.
// We should modify CachingValidationSupport to cache the results of "isXXXSupported" // We should modify CachingValidationSupport to cache the results of "isXXXSupported"
// at which point we could do away with this cache // 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 * Constructor
@ -123,7 +123,9 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport
* version is always pointed by the ForcedId for the no-versioned CS * version is always pointed by the ForcedId for the no-versioned CS
*/ */
private Optional<IBaseResource> getCodeSystemCurrentVersion(UriType theUrl) { 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); return myTermReadSvc.readCodeSystemByForcedId(LOINC_LOW);
} }
@ -145,7 +147,9 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport
*/ */
private Optional<IBaseResource> getValueSetCurrentVersion(UriType theUrl) { private Optional<IBaseResource> getValueSetCurrentVersion(UriType theUrl) {
Optional<String> vsIdOpt = TermReadSvcUtil.getValueSetId(theUrl.getValueAsString()); 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); IFhirResourceDao<? extends IBaseResource> valueSetResourceDao = myDaoRegistry.getResourceDao(myValueSetType);
IBaseResource valueSet = valueSetResourceDao.read(new IdDt("ValueSet", vsIdOpt.get())); 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) { private <T extends IBaseResource> IBaseResource doFetchResource(@Nullable Class<T> theClass, String theUri) {
if (theClass == null) { if (theClass == null) {
Supplier<IBaseResource>[] fetchers = new Supplier[]{() -> doFetchResource(ValueSet.class, theUri), () -> doFetchResource(CodeSystem.class, theUri), () -> doFetchResource(StructureDefinition.class, theUri)}; Supplier<IBaseResource>[] fetchers = new Supplier[]{
return Arrays.stream(fetchers).map(t -> t.get()).filter(t -> t != myNoMatch).findFirst().orElse(myNoMatch); () -> 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); 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.interceptor.model.TransactionWriteOperationsDetails;
import ca.uhn.fhir.jpa.util.MemoryCacheService; import ca.uhn.fhir.jpa.util.MemoryCacheService;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import com.github.benmanes.caffeine.cache.Cache; import ca.uhn.fhir.sl.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine; import ca.uhn.fhir.sl.cache.CacheFactory;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -61,10 +61,7 @@ public class TransactionConcurrencySemaphoreInterceptor {
*/ */
public TransactionConcurrencySemaphoreInterceptor(MemoryCacheService theMemoryCacheService) { public TransactionConcurrencySemaphoreInterceptor(MemoryCacheService theMemoryCacheService) {
myMemoryCacheService = theMemoryCacheService; myMemoryCacheService = theMemoryCacheService;
mySemaphoreCache = Caffeine mySemaphoreCache = CacheFactory.build(TimeUnit.MINUTES.toMillis(1));
.newBuilder()
.expireAfterAccess(1, TimeUnit.MINUTES)
.build();
} }
/** /**

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.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; 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.apache.commons.lang3.Validate;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -78,14 +78,8 @@ public class PartitionLookupSvcImpl implements IPartitionLookupSvc {
@Override @Override
@PostConstruct @PostConstruct
public void start() { public void start() {
myNameToPartitionCache = Caffeine myNameToPartitionCache = CacheFactory.build(TimeUnit.MINUTES.toMillis(1), new NameToPartitionCacheLoader());
.newBuilder() myIdToPartitionCache = CacheFactory.build(TimeUnit.MINUTES.toMillis(1), new IdToPartitionCacheLoader());
.expireAfterWrite(1, TimeUnit.MINUTES)
.build(new NameToPartitionCacheLoader());
myIdToPartitionCache = Caffeine
.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.build(new IdToPartitionCacheLoader());
myTxTemplate = new TransactionTemplate(myTxManager); 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.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; 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.CoverageIgnore;
import ca.uhn.fhir.util.FhirVersionIndependentConcept; import ca.uhn.fhir.util.FhirVersionIndependentConcept;
import ca.uhn.fhir.util.HapiExtensions; 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.UrlUtil;
import ca.uhn.fhir.util.ValidateUtil; import ca.uhn.fhir.util.ValidateUtil;
import ca.uhn.hapi.converters.canonical.VersionCanonicalizer; 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.annotations.VisibleForTesting;
import com.google.common.base.Stopwatch; import com.google.common.base.Stopwatch;
import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ArrayListMultimap;
@ -193,25 +193,22 @@ import static org.apache.commons.lang3.StringUtils.startsWithIgnoreCase;
public class TermReadSvcImpl implements ITermReadSvc { public class TermReadSvcImpl implements ITermReadSvc {
public static final int DEFAULT_FETCH_SIZE = 250; 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 int SINGLE_FETCH_SIZE = 1;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TermReadSvcImpl.class); 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 ValueSetExpansionOptions DEFAULT_EXPANSION_OPTIONS = new ValueSetExpansionOptions();
private static final TermCodeSystemVersionDetails NO_CURRENT_VERSION = new TermCodeSystemVersionDetails(-1L, null); 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_PROPERTIES = "myProperties";
private static final String IDX_PROP_KEY = IDX_PROPERTIES + ".myKey"; 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_VALUE_STRING = IDX_PROPERTIES + ".myValueString";
private static final String IDX_PROP_DISPLAY_STRING = IDX_PROPERTIES + ".myDisplayString"; private static final String IDX_PROP_DISPLAY_STRING = IDX_PROPERTIES + ".myDisplayString";
private static final int SECONDS_IN_MINUTE = 60;
public static final int DEFAULT_MASS_INDEXER_OBJECT_LOADING_THREADS = 2; private static final int INDEXED_ROOTS_LOGGING_COUNT = 50_000;
// doesn't seem to be much gain by using more threads than this value private static Runnable myInvokeOnNextCallForUnitTest;
public static final int MAX_MASS_INDEXER_OBJECT_LOADING_THREADS = 6; private static boolean ourForceDisableHibernateSearchForUnitTest;
private final Cache<String, TermCodeSystemVersionDetails> myCodeSystemCurrentVersionCache = CacheFactory.build(TimeUnit.MINUTES.toMillis(1));
private boolean myPreExpandingValueSets = false;
private final Cache<String, TermCodeSystemVersionDetails> myCodeSystemCurrentVersionCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build();
@Autowired @Autowired
protected DaoRegistry myDaoRegistry; protected DaoRegistry myDaoRegistry;
@Autowired @Autowired
@ -232,6 +229,7 @@ public class TermReadSvcImpl implements ITermReadSvc {
protected FhirContext myContext; protected FhirContext myContext;
@PersistenceContext(type = PersistenceContextType.TRANSACTION) @PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager; protected EntityManager myEntityManager;
private boolean myPreExpandingValueSets = false;
@Autowired @Autowired
private ITermCodeSystemVersionDao myCodeSystemVersionDao; private ITermCodeSystemVersionDao myCodeSystemVersionDao;
@Autowired @Autowired
@ -255,19 +253,17 @@ public class TermReadSvcImpl implements ITermReadSvc {
private ITermDeferredStorageSvc myDeferredStorageSvc; private ITermDeferredStorageSvc myDeferredStorageSvc;
@Autowired @Autowired
private IIdHelperService myIdHelperService; private IIdHelperService myIdHelperService;
@Autowired @Autowired
private ApplicationContext myApplicationContext; private ApplicationContext myApplicationContext;
private volatile IValidationSupport myJpaValidationSupport; private volatile IValidationSupport myJpaValidationSupport;
private volatile IValidationSupport myValidationSupport; private volatile IValidationSupport myValidationSupport;
//We need this bean so we can tell which mode hibernate search is running in. //We need this bean so we can tell which mode hibernate search is running in.
@Autowired @Autowired
private HibernatePropertiesProvider myHibernatePropertiesProvider; private HibernatePropertiesProvider myHibernatePropertiesProvider;
@Autowired @Autowired
private CachingValidationSupport myCachingValidationSupport; private CachingValidationSupport myCachingValidationSupport;
@Autowired
private VersionCanonicalizer myVersionCanonicalizer;
@Override @Override
public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) {
@ -368,7 +364,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
myCodeSystemCurrentVersionCache.invalidateAll(); myCodeSystemCurrentVersionCache.invalidateAll();
} }
public void deleteValueSetForResource(ResourceTable theResourceTable) { public void deleteValueSetForResource(ResourceTable theResourceTable) {
// Get existing entity so it can be deleted. // Get existing entity so it can be deleted.
Optional<TermValueSet> optionalExistingTermValueSetById = myTermValueSetDao.findByResourcePid(theResourceTable.getId()); Optional<TermValueSet> optionalExistingTermValueSetById = myTermValueSetDao.findByResourcePid(theResourceTable.getId());
@ -394,7 +389,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
deleteValueSetForResource(theResourceTable); deleteValueSetForResource(theResourceTable);
} }
@Override @Override
@Transactional @Transactional
public List<FhirVersionIndependentConcept> expandValueSetIntoConceptList(@Nullable ValueSetExpansionOptions theExpansionOptions, @Nonnull String theValueSetCanonicalUrl) { 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)); accumulator.addParameter().setName("count").setValue(new IntegerType(count));
} }
myTxTemplate.executeWithoutResult(tx-> { myTxTemplate.executeWithoutResult(tx -> {
expandValueSetIntoAccumulator(theValueSetToExpand, theExpansionOptions, accumulator, theFilter, true); expandValueSetIntoAccumulator(theValueSetToExpand, theExpansionOptions, accumulator, theFilter, true);
}); });
@ -590,7 +584,8 @@ public class TermReadSvcImpl implements ITermReadSvc {
//-- this is quick solution, may need to revisit //-- this is quick solution, may need to revisit
if (!applyFilter(display, filterDisplayValue)) { if (!applyFilter(display, filterDisplayValue)) {
continue;} continue;
}
Long conceptPid = conceptView.getConceptPid(); Long conceptPid = conceptView.getConceptPid();
if (!pidToConcept.containsKey(conceptPid)) { if (!pidToConcept.containsKey(conceptPid)) {
@ -602,7 +597,7 @@ public class TermReadSvcImpl implements ITermReadSvc {
if (conceptView.getDesignationPid() != null) { if (conceptView.getDesignationPid() != null) {
TermConceptDesignation designation = new TermConceptDesignation(); TermConceptDesignation designation = new TermConceptDesignation();
if(isValueSetDisplayLanguageMatch(theExpansionOptions, conceptView.getDesignationLang() )) { if (isValueSetDisplayLanguageMatch(theExpansionOptions, conceptView.getDesignationLang())) {
designation.setUseSystem(conceptView.getDesignationUseSystem()); designation.setUseSystem(conceptView.getDesignationUseSystem());
designation.setUseCode(conceptView.getDesignationUseCode()); designation.setUseCode(conceptView.getDesignationUseCode());
designation.setUseDisplay(conceptView.getDesignationUseDisplay()); designation.setUseDisplay(conceptView.getDesignationUseDisplay());
@ -660,19 +655,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
logConceptsExpanded("Finished expanding concepts. ", theTermValueSet, conceptsExpanded); 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) { private void logConceptsExpanded(String theLogDescriptionPrefix, TermValueSet theTermValueSet, int theConceptsExpanded) {
if (theConceptsExpanded > 0) { if (theConceptsExpanded > 0) {
ourLog.debug("{}Have expanded {} concepts in ValueSet[{}]", theLogDescriptionPrefix, theConceptsExpanded, theTermValueSet.getUrl()); 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 * Note: Not transactional because specific calls within this method
* get executed in a transaction * get executed in a transaction
*/ */
@SuppressWarnings("ConstantConditions")
private void doExpandValueSet(ValueSetExpansionOptions theExpansionOptions, ValueSet theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator, @Nonnull ExpansionFilter theExpansionFilter) { private void doExpandValueSet(ValueSetExpansionOptions theExpansionOptions, ValueSet theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator, @Nonnull ExpansionFilter theExpansionFilter) {
Set<String> addedCodes = new HashSet<>(); Set<String> addedCodes = new HashSet<>();
@ -751,16 +732,16 @@ public class TermReadSvcImpl implements ITermReadSvc {
ourLog.debug("Handling includes"); ourLog.debug("Handling includes");
for (ValueSet.ConceptSetComponent include : theValueSetToExpand.getCompose().getInclude()) { for (ValueSet.ConceptSetComponent include : theValueSetToExpand.getCompose().getInclude()) {
myTxTemplate.executeWithoutResult(tx -> myTxTemplate.executeWithoutResult(tx ->
expandValueSetHandleIncludeOrExclude(theExpansionOptions, theValueSetCodeAccumulator, addedCodes, expandValueSetHandleIncludeOrExclude(theExpansionOptions, theValueSetCodeAccumulator, addedCodes,
include, true, theExpansionFilter) ); include, true, theExpansionFilter));
} }
// Handle excludes // Handle excludes
ourLog.debug("Handling excludes"); ourLog.debug("Handling excludes");
for (ValueSet.ConceptSetComponent exclude : theValueSetToExpand.getCompose().getExclude()) { for (ValueSet.ConceptSetComponent exclude : theValueSetToExpand.getCompose().getExclude()) {
myTxTemplate.executeWithoutResult(tx -> myTxTemplate.executeWithoutResult(tx ->
expandValueSetHandleIncludeOrExclude(theExpansionOptions, theValueSetCodeAccumulator, addedCodes, expandValueSetHandleIncludeOrExclude(theExpansionOptions, theValueSetCodeAccumulator, addedCodes,
exclude, false, ExpansionFilter.NO_FILTER) ); exclude, false, ExpansionFilter.NO_FILTER));
} }
if (theValueSetCodeAccumulator instanceof ValueSetConceptAccumulator) { if (theValueSetCodeAccumulator instanceof ValueSetConceptAccumulator) {
@ -770,7 +751,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
ourLog.debug("Done working with {} in {}ms", valueSetInfo, sw.getMillis()); ourLog.debug("Done working with {} in {}ms", valueSetInfo, sw.getMillis());
} }
private String getValueSetInfo(ValueSet theValueSet) { private String getValueSetInfo(ValueSet theValueSet) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
boolean isIdentified = false; boolean isIdentified = false;
@ -799,11 +779,11 @@ public class TermReadSvcImpl implements ITermReadSvc {
* Returns true if there are potentially more results to process. * Returns true if there are potentially more results to process.
*/ */
private void expandValueSetHandleIncludeOrExclude(@Nullable ValueSetExpansionOptions theExpansionOptions, private void expandValueSetHandleIncludeOrExclude(@Nullable ValueSetExpansionOptions theExpansionOptions,
IValueSetConceptAccumulator theValueSetCodeAccumulator, IValueSetConceptAccumulator theValueSetCodeAccumulator,
Set<String> theAddedCodes, Set<String> theAddedCodes,
ValueSet.ConceptSetComponent theIncludeOrExclude, ValueSet.ConceptSetComponent theIncludeOrExclude,
boolean theAdd, boolean theAdd,
@Nonnull ExpansionFilter theExpansionFilter) { @Nonnull ExpansionFilter theExpansionFilter) {
String system = theIncludeOrExclude.getSystem(); String system = theIncludeOrExclude.getSystem();
boolean hasSystem = isNotBlank(system); boolean hasSystem = isNotBlank(system);
@ -841,7 +821,7 @@ public class TermReadSvcImpl implements ITermReadSvc {
org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent includeOrExclude = ValueSet40_50.convertConceptSetComponent(theIncludeOrExclude); org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent includeOrExclude = ValueSet40_50.convertConceptSetComponent(theIncludeOrExclude);
new InMemoryTerminologyServerValidationSupport(myContext).expandValueSetIncludeOrExclude(new ValidationSupportContext(provideValidationSupport()), consumer, includeOrExclude); new InMemoryTerminologyServerValidationSupport(myContext).expandValueSetIncludeOrExclude(new ValidationSupportContext(provideValidationSupport()), consumer, includeOrExclude);
} catch (InMemoryTerminologyServerValidationSupport.ExpansionCouldNotBeCompletedInternallyException e) { } 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; return;
} }
throw new InternalErrorException(Msg.code(888) + e); throw new InternalErrorException(Msg.code(888) + e);
@ -881,14 +861,14 @@ public class TermReadSvcImpl implements ITermReadSvc {
} }
private void expandValueSetHandleIncludeOrExcludeUsingDatabase( private void expandValueSetHandleIncludeOrExcludeUsingDatabase(
ValueSetExpansionOptions theExpansionOptions, ValueSetExpansionOptions theExpansionOptions,
IValueSetConceptAccumulator theValueSetCodeAccumulator, IValueSetConceptAccumulator theValueSetCodeAccumulator,
Set<String> theAddedCodes, Set<String> theAddedCodes,
ValueSet.ConceptSetComponent theIncludeOrExclude, ValueSet.ConceptSetComponent theIncludeOrExclude,
boolean theAdd, boolean theAdd,
@Nonnull ExpansionFilter theExpansionFilter, @Nonnull ExpansionFilter theExpansionFilter,
String theSystem, String theSystem,
TermCodeSystem theCs) { TermCodeSystem theCs) {
StopWatch fullOperationSw = new StopWatch(); StopWatch fullOperationSw = new StopWatch();
@ -913,17 +893,19 @@ public class TermReadSvcImpl implements ITermReadSvc {
int count = 0; int count = 0;
Optional<Integer> chunkSizeOpt = getScrollChunkSize(theAdd, theValueSetCodeAccumulator); Optional<Integer> chunkSizeOpt = getScrollChunkSize(theAdd, theValueSetCodeAccumulator);
if (chunkSizeOpt.isEmpty()) { return; } if (chunkSizeOpt.isEmpty()) {
return;
}
int chunkSize = chunkSizeOpt.get(); int chunkSize = chunkSizeOpt.get();
SearchProperties searchProps = buildSearchScroll(termCodeSystemVersion, theExpansionFilter, theSystem, SearchProperties searchProps = buildSearchScroll(termCodeSystemVersion, theExpansionFilter, theSystem,
theIncludeOrExclude, chunkSize, includeOrExcludeVersion); theIncludeOrExclude, chunkSize, includeOrExcludeVersion);
int accumulatedBatchesSoFar = 0; 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); 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; int countForBatch = 0;
List<Long> pids = chunk.hits() List<Long> pids = chunk.hits()
@ -970,7 +952,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
} }
} }
private List<TermConcept> sortTermConcepts(SearchProperties searchProps, List<TermConcept> termConcepts) { private List<TermConcept> sortTermConcepts(SearchProperties searchProps, List<TermConcept> termConcepts) {
List<String> codes = searchProps.getIncludeOrExcludeCodes(); List<String> codes = searchProps.getIncludeOrExcludeCodes();
if (codes.size() > 1) { if (codes.size() > 1) {
@ -988,7 +969,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
return termConcepts; return termConcepts;
} }
private Optional<Integer> getScrollChunkSize(boolean theAdd, IValueSetConceptAccumulator theValueSetCodeAccumulator) { private Optional<Integer> getScrollChunkSize(boolean theAdd, IValueSetConceptAccumulator theValueSetCodeAccumulator) {
int maxResultsPerBatch = SearchBuilder.getMaximumPageSize(); int maxResultsPerBatch = SearchBuilder.getMaximumPageSize();
@ -1002,18 +982,14 @@ public class TermReadSvcImpl implements ITermReadSvc {
maxResultsPerBatch = Math.min(maxResultsPerBatch, accumulatorCapacityRemaining + 1); 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, private SearchProperties buildSearchScroll(TermCodeSystemVersion theTermCodeSystemVersion,
ExpansionFilter theExpansionFilter, ExpansionFilter theExpansionFilter,
String theSystem, String theSystem,
ValueSet.ConceptSetComponent theIncludeOrExclude, ValueSet.ConceptSetComponent theIncludeOrExclude,
Integer theScrollChunkSize, String theIncludeOrExcludeVersion) { Integer theScrollChunkSize, String theIncludeOrExcludeVersion) {
SearchSession searchSession = Search.session(myEntityManager); SearchSession searchSession = Search.session(myEntityManager);
//Manually building a predicate since we need to throw it around. //Manually building a predicate since we need to throw it around.
SearchPredicateFactory predicate = searchSession.scope(TermConcept.class).predicate(); SearchPredicateFactory predicate = searchSession.scope(TermConcept.class).predicate();
@ -1067,11 +1043,10 @@ public class TermReadSvcImpl implements ITermReadSvc {
.where(f -> finishedQuery) .where(f -> finishedQuery)
.toQuery(); .toQuery();
returnProps.setSearchScroll( termConceptsQuery.scroll(theScrollChunkSize) ); returnProps.setSearchScroll(termConceptsQuery.scroll(theScrollChunkSize));
return returnProps; return returnProps;
} }
private ValueSet.ConceptReferenceComponent getMatchedConceptIncludedInValueSet(ValueSet.ConceptSetComponent theIncludeOrExclude, TermConcept concept) { private ValueSet.ConceptReferenceComponent getMatchedConceptIncludedInValueSet(ValueSet.ConceptSetComponent theIncludeOrExclude, TermConcept concept) {
return theIncludeOrExclude return theIncludeOrExclude
.getConcept() .getConcept()
@ -1084,13 +1059,15 @@ public class TermReadSvcImpl implements ITermReadSvc {
* Helper method which builds a predicate for the expansion * Helper method which builds a predicate for the expansion
*/ */
private Optional<PredicateFinalStep> buildExpansionPredicate(List<String> theCodes, SearchPredicateFactory thePredicate) { 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()) { if (theCodes.size() < BooleanQuery.getMaxClauseCount()) {
return Optional.of(thePredicate.simpleQueryString() 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 // Number of codes is larger than maxClauseCount, so we split the query in several clauses
// partition codes in lists of BooleanQuery.getMaxClauseCount() size // partition codes in lists of BooleanQuery.getMaxClauseCount() size
@ -1106,7 +1083,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
return Optional.of(step); return Optional.of(step);
} }
private String buildCodeSystemUrlAndVersion(String theSystem, String theIncludeOrExcludeVersion) { private String buildCodeSystemUrlAndVersion(String theSystem, String theIncludeOrExcludeVersion) {
String codeSystemUrlAndVersion; String codeSystemUrlAndVersion;
if (theIncludeOrExcludeVersion != null) { if (theIncludeOrExcludeVersion != null) {
@ -1177,14 +1153,13 @@ public class TermReadSvcImpl implements ITermReadSvc {
} }
private void handleFilterPropertyDefault(SearchPredicateFactory theF, private void handleFilterPropertyDefault(SearchPredicateFactory theF,
BooleanPredicateClausesStep<?> theB, ValueSet.ConceptSetFilterComponent theFilter) { BooleanPredicateClausesStep<?> theB, ValueSet.ConceptSetFilterComponent theFilter) {
String value = theFilter.getValue(); String value = theFilter.getValue();
Term term = new Term(CONCEPT_PROPERTY_PREFIX_NAME + theFilter.getProperty(), value); Term term = new Term(CONCEPT_PROPERTY_PREFIX_NAME + theFilter.getProperty(), value);
theB.must(theF.match().field(term.field()).matching(term.text())); theB.must(theF.match().field(term.field()).matching(term.text()));
} }
private void handleFilterRegex(SearchPredicateFactory theF, BooleanPredicateClausesStep<?> theB, ValueSet.ConceptSetFilterComponent theFilter) { private void handleFilterRegex(SearchPredicateFactory theF, BooleanPredicateClausesStep<?> theB, ValueSet.ConceptSetFilterComponent theFilter) {
/* /*
* We treat the regex filter as a match on the regex * We treat the regex filter as a match on the regex
@ -1206,12 +1181,11 @@ public class TermReadSvcImpl implements ITermReadSvc {
theB.must(theF.regexp() theB.must(theF.regexp()
.field(CONCEPT_PROPERTY_PREFIX_NAME + theFilter.getProperty()) .field(CONCEPT_PROPERTY_PREFIX_NAME + theFilter.getProperty())
.matching(value) ); .matching(value));
} }
private void handleFilterLoincCopyright(SearchPredicateFactory theF, BooleanPredicateClausesStep<?> theB, private void handleFilterLoincCopyright(SearchPredicateFactory theF, BooleanPredicateClausesStep<?> theB,
ValueSet.ConceptSetFilterComponent theFilter) { ValueSet.ConceptSetFilterComponent theFilter) {
if (theFilter.getOp() == ValueSet.FilterOperator.EQUAL) { if (theFilter.getOp() == ValueSet.FilterOperator.EQUAL) {
@ -1238,7 +1212,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
theB.mustNot(theF.exists().field(CONCEPT_PROPERTY_PREFIX_NAME + "EXTERNAL_COPYRIGHT_NOTICE")); theB.mustNot(theF.exists().field(CONCEPT_PROPERTY_PREFIX_NAME + "EXTERNAL_COPYRIGHT_NOTICE"));
} }
private void addFilterLoincCopyright3rdParty(SearchPredicateFactory theF, BooleanPredicateClausesStep<?> theB) { private void addFilterLoincCopyright3rdParty(SearchPredicateFactory theF, BooleanPredicateClausesStep<?> theB) {
theB.must(theF.exists().field(CONCEPT_PROPERTY_PREFIX_NAME + "EXTERNAL_COPYRIGHT_NOTICE")); 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()))))); b.must(f.bool(innerB -> terms.forEach(term -> innerB.should(f.match().field(term.field()).matching(term.text())))));
} }
@SuppressWarnings("EnumSwitchStatementWhichMissesCases") @SuppressWarnings("EnumSwitchStatementWhichMissesCases")
private void handleFilterLoincParentChild(SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) { private void handleFilterLoincParentChild(SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) {
switch (theFilter.getOp()) { switch (theFilter.getOp()) {
@ -1370,7 +1342,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
return new Term(CONCEPT_PROPERTY_PREFIX_NAME + theProperty, theValue); return new Term(CONCEPT_PROPERTY_PREFIX_NAME + theProperty, theValue);
} }
private List<Term> getAncestorTerms(String theSystem, String theProperty, String theValue) { private List<Term> getAncestorTerms(String theSystem, String theProperty, String theValue) {
List<Term> retVal = new ArrayList<>(); List<Term> retVal = new ArrayList<>();
@ -1383,7 +1354,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
return retVal; return retVal;
} }
@SuppressWarnings("EnumSwitchStatementWhichMissesCases") @SuppressWarnings("EnumSwitchStatementWhichMissesCases")
private void handleFilterLoincDescendant(String theSystem, SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) { private void handleFilterLoincDescendant(String theSystem, SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) {
switch (theFilter.getOp()) { switch (theFilter.getOp()) {
@ -1398,15 +1368,14 @@ public class TermReadSvcImpl implements ITermReadSvc {
} }
} }
private void addLoincFilterDescendantEqual(String theSystem, SearchPredicateFactory f, private void addLoincFilterDescendantEqual(String theSystem, SearchPredicateFactory f,
BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) { BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) {
List<Long> parentPids = getCodeParentPids(theSystem, theFilter.getProperty(), theFilter.getValue()); List<Long> parentPids = getCodeParentPids(theSystem, theFilter.getProperty(), theFilter.getValue());
if (parentPids.isEmpty()) { if (parentPids.isEmpty()) {
// Can't return empty must, because it wil match according to other predicates. // 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) // 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; return;
} }
@ -1423,7 +1392,7 @@ public class TermReadSvcImpl implements ITermReadSvc {
* representing the codes in theFilter.getValue() * representing the codes in theFilter.getValue()
*/ */
private void addLoincFilterDescendantIn(String theSystem, SearchPredicateFactory f, private void addLoincFilterDescendantIn(String theSystem, SearchPredicateFactory f,
BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) { BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) {
String[] values = theFilter.getValue().split(","); String[] values = theFilter.getValue().split(",");
if (values.length == 0) { if (values.length == 0) {
@ -1437,7 +1406,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
))); )));
} }
/** /**
* Returns the list of parentId(s) of the TermConcept representing theValue as a code * 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(" "); String[] parentPids = code.getParentPidsAsString().split(" ");
List<Long> retVal = Arrays.stream(parentPids) List<Long> retVal = Arrays.stream(parentPids)
.filter( pid -> !StringUtils.equals(pid, "NONE") ) .filter(pid -> !StringUtils.equals(pid, "NONE"))
.map(Long::parseLong) .map(Long::parseLong)
.collect(Collectors.toList()); .collect(Collectors.toList());
logFilteringValueOnProperty(theValue, theProperty); logFilteringValueOnProperty(theValue, theProperty);
return retVal; return retVal;
} }
/** /**
* Returns the list of parentId(s) of the TermConcept representing theValue as a code * 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() List<Long> retVal = termConcepts.stream()
.flatMap(tc -> Arrays.stream(tc.getParentPidsAsString().split(" "))) .flatMap(tc -> Arrays.stream(tc.getParentPidsAsString().split(" ")))
.filter( pid -> !StringUtils.equals(pid, "NONE") ) .filter(pid -> !StringUtils.equals(pid, "NONE"))
.map(Long::parseLong) .map(Long::parseLong)
.collect(Collectors.toList()); .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: [" + return "Invalid filter criteria - More TermConcepts were found than indicated codes. Queried codes: [" +
join(",", theValues + "]; Obtained TermConcept IDs, codes: [" + join(",", theValues + "]; Obtained TermConcept IDs, codes: [" +
theTermConcepts.stream().map(tc -> tc.getId() + ", " + tc.getCode()) theTermConcepts.stream().map(tc -> tc.getId() + ", " + tc.getCode())
.collect(joining("; "))+ "]"); .collect(joining("; ")) + "]");
} }
// case: less TermConcept(s) retrieved than codes queried // case: less TermConcept(s) retrieved than codes queried
Set<String> matchedCodes = theTermConcepts.stream().map(TermConcept::getCode).collect(toSet()); Set<String> matchedCodes = theTermConcepts.stream().map(TermConcept::getCode).collect(toSet());
List<String> notMatchedValues = theValues.stream() 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: [" + return "Invalid filter criteria - No TermConcept(s) were found for the requested codes: [" +
join(",", notMatchedValues + "]"); join(",", notMatchedValues + "]");
} }
private void logFilteringValueOnProperty(String theValue, String theProperty) { private void logFilteringValueOnProperty(String theValue, String theProperty) {
ourLog.debug(" * Filtering with value={} on property {}", theValue, theProperty); ourLog.debug(" * Filtering with value={} on property {}", theValue, theProperty);
} }
@ -1560,7 +1526,7 @@ public class TermReadSvcImpl implements ITermReadSvc {
Collection<TermConceptDesignation> designations = next Collection<TermConceptDesignation> designations = next
.getDesignation() .getDesignation()
.stream() .stream()
.map(t->new TermConceptDesignation() .map(t -> new TermConceptDesignation()
.setValue(t.getValue()) .setValue(t.getValue())
.setLanguage(t.getLanguage()) .setLanguage(t.getLanguage())
.setUseCode(t.getUse().getCode()) .setUseCode(t.getUse().getCode())
@ -1765,7 +1731,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
} }
} }
@Override @Override
public Optional<TermConcept> findCode(String theCodeSystem, String theCode) { public Optional<TermConcept> findCode(String theCodeSystem, String theCode) {
/* /*
@ -1790,12 +1755,13 @@ public class TermReadSvcImpl implements ITermReadSvc {
@Transactional(propagation = Propagation.MANDATORY) @Transactional(propagation = Propagation.MANDATORY)
public List<TermConcept> findCodes(String theCodeSystem, List<String> theCodeList) { public List<TermConcept> findCodes(String theCodeSystem, List<String> theCodeList) {
TermCodeSystemVersionDetails csv = getCurrentCodeSystemVersion(theCodeSystem); TermCodeSystemVersionDetails csv = getCurrentCodeSystemVersion(theCodeSystem);
if (csv == null) { return Collections.emptyList(); } if (csv == null) {
return Collections.emptyList();
}
return myConceptDao.findByCodeSystemAndCodeList(csv.myPid, theCodeList); return myConceptDao.findByCodeSystemAndCodeList(csv.myPid, theCodeList);
} }
@Nullable @Nullable
private TermCodeSystemVersionDetails getCurrentCodeSystemVersion(String theCodeSystemIdentifier) { private TermCodeSystemVersionDetails getCurrentCodeSystemVersion(String theCodeSystemIdentifier) {
String version = getVersionFromIdentifier(theCodeSystemIdentifier); String version = getVersionFromIdentifier(theCodeSystemIdentifier);
@ -1821,19 +1787,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
return retVal; 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) { private String getVersionFromIdentifier(String theUri) {
String retVal = null; String retVal = null;
if (StringUtils.isNotEmpty((theUri))) { if (StringUtils.isNotEmpty((theUri))) {
@ -2021,14 +1974,13 @@ public class TermReadSvcImpl implements ITermReadSvc {
myCachingValidationSupport.invalidateCaches(); myCachingValidationSupport.invalidateCaches();
} }
private synchronized void setPreExpandingValueSets(boolean thePreExpandingValueSets) {
myPreExpandingValueSets = thePreExpandingValueSets;
}
private synchronized boolean isPreExpandingValueSets() { private synchronized boolean isPreExpandingValueSets() {
return myPreExpandingValueSets; return myPreExpandingValueSets;
} }
private synchronized void setPreExpandingValueSets(boolean thePreExpandingValueSets) {
myPreExpandingValueSets = thePreExpandingValueSets;
}
private boolean isNotSafeToPreExpandValueSets() { private boolean isNotSafeToPreExpandValueSets() {
return myDeferredStorageSvc != null && !myDeferredStorageSvc.isStorageQueueEmpty(true); return myDeferredStorageSvc != null && !myDeferredStorageSvc.isStorageQueueEmpty(true);
@ -2430,7 +2382,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
return false; return false;
} }
@Nonnull @Nonnull
private FhirVersionIndependentConcept toConcept(IPrimitiveType<String> theCodeType, IPrimitiveType<String> theCodeSystemIdentifierType, IBaseCoding theCodingType) { private FhirVersionIndependentConcept toConcept(IPrimitiveType<String> theCodeType, IPrimitiveType<String> theCodeSystemIdentifierType, IBaseCoding theCodingType) {
String code = theCodeType != null ? theCodeType.getValueAsString() : null; String code = theCodeType != null ? theCodeType.getValueAsString() : null;
@ -2446,7 +2397,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
return new FhirVersionIndependentConcept(system, code, null, systemVersion); return new FhirVersionIndependentConcept(system, code, null, systemVersion);
} }
/** /**
* When the search is for unversioned loinc system it uses the forcedId to obtain the current * 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. * 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)); 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 @Override
public Optional<IBaseResource> readCodeSystemByForcedId(String theForcedId) { public Optional<IBaseResource> readCodeSystemByForcedId(String theForcedId) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -2498,11 +2438,6 @@ public class TermReadSvcImpl implements ITermReadSvc {
return Optional.of(cs); return Optional.of(cs);
} }
private static final int SECONDS_IN_MINUTE = 60;
private static final int INDEXED_ROOTS_LOGGING_COUNT = 50_000;
@Transactional @Transactional
@Override @Override
public ReindexTerminologyResult reindexTerminology() throws InterruptedException { public ReindexTerminologyResult reindexTerminology() throws InterruptedException {
@ -2523,14 +2458,14 @@ public class TermReadSvcImpl implements ITermReadSvc {
try { try {
SearchSession searchSession = getSearchSession(); SearchSession searchSession = getSearchSession();
searchSession searchSession
.massIndexer( TermConcept.class ) .massIndexer(TermConcept.class)
.dropAndCreateSchemaOnStart( true ) .dropAndCreateSchemaOnStart(true)
.purgeAllOnStart( false ) .purgeAllOnStart(false)
.batchSizeToLoadObjects( 100 ) .batchSizeToLoadObjects(100)
.cacheMode( CacheMode.IGNORE ) .cacheMode(CacheMode.IGNORE)
.threadsToLoadObjects( 6 ) .threadsToLoadObjects(6)
.transactionTimeout( 60 * SECONDS_IN_MINUTE ) .transactionTimeout(60 * SECONDS_IN_MINUTE)
.monitor( new PojoMassIndexingLoggingMonitor(INDEXED_ROOTS_LOGGING_COUNT) ) .monitor(new PojoMassIndexingLoggingMonitor(INDEXED_ROOTS_LOGGING_COUNT))
.startAndWait(); .startAndWait();
} finally { } finally {
myDeferredStorageSvc.setProcessDeferred(true); myDeferredStorageSvc.setProcessDeferred(true);
@ -2539,13 +2474,11 @@ public class TermReadSvcImpl implements ITermReadSvc {
return ReindexTerminologyResult.SUCCESS; return ReindexTerminologyResult.SUCCESS;
} }
@VisibleForTesting @VisibleForTesting
boolean isBatchTerminologyTasksRunning() { boolean isBatchTerminologyTasksRunning() {
return isNotSafeToPreExpandValueSets() || isPreExpandingValueSets(); return isNotSafeToPreExpandValueSets() || isPreExpandingValueSets();
} }
@VisibleForTesting @VisibleForTesting
int calculateObjectLoadingThreadNumber() { int calculateObjectLoadingThreadNumber() {
IConnectionPoolInfoProvider connectionPoolInfoProvider = IConnectionPoolInfoProvider connectionPoolInfoProvider =
@ -2563,12 +2496,136 @@ public class TermReadSvcImpl implements ITermReadSvc {
return objectThreads; return objectThreads;
} }
@VisibleForTesting @VisibleForTesting
SearchSession getSearchSession() { 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 @VisibleForTesting
public static void setForceDisableHibernateSearchForUnitTest(boolean theForceDisableHibernateSearchForUnitTest) { public static void setForceDisableHibernateSearchForUnitTest(boolean theForceDisableHibernateSearchForUnitTest) {
@ -2669,101 +2726,5 @@ public class TermReadSvcImpl implements ITermReadSvc {
return theReqLang.equalsIgnoreCase(theStoredLang); 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,11 +160,17 @@
</exclusions> </exclusions>
</dependency> </dependency>
<!-- test --> <!-- test -->
<dependency> <dependency>
<groupId>org.testcontainers</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>junit-jupiter</artifactId> <artifactId>hapi-fhir-caching-testing</artifactId>
<scope>test</scope> <version>${project.version}</version>
</dependency> <scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId> <artifactId>mockito-core</artifactId>

View File

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

View File

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

View File

@ -116,8 +116,15 @@
<!-- Caffeine --> <!-- Caffeine -->
<dependency> <dependency>
<groupId>com.github.ben-manes.caffeine</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>caffeine</artifactId> <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> </dependency>
<!-- test dependencies --> <!-- test dependencies -->

View File

@ -132,6 +132,12 @@
</dependency> </dependency>
<!-- Testing --> <!-- Testing -->
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>ch.qos.logback</groupId> <groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
@ -154,6 +160,7 @@
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
<pluginManagement> <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.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; 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 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.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException; import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBase;
@ -93,10 +93,7 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
myFhirPathEngine = new FHIRPathEngine(worker); myFhirPathEngine = new FHIRPathEngine(worker);
myFhirPathEngine.setHostServices(new SearchParamExtractorR4HostServices()); myFhirPathEngine.setHostServices(new SearchParamExtractorR4HostServices());
myParsedFhirPathCache = Caffeine myParsedFhirPathCache = CacheFactory.build(TimeUnit.MINUTES.toMillis(10));
.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
} }

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.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; 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 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.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException; import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBase;
@ -93,10 +93,7 @@ public class SearchParamExtractorR4B extends BaseSearchParamExtractor implements
myFhirPathEngine = new FHIRPathEngine(worker); myFhirPathEngine = new FHIRPathEngine(worker);
myFhirPathEngine.setHostServices(new SearchParamExtractorR4BHostServices()); myFhirPathEngine.setHostServices(new SearchParamExtractorR4BHostServices());
myParsedFhirPathCache = Caffeine myParsedFhirPathCache = CacheFactory.build(TimeUnit.MINUTES.toMillis(10));
.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
} }

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

View File

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

View File

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

View File

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

View File

@ -21,6 +21,12 @@
<version>${project.version}</version> <version>${project.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <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.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc; 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.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet;

View File

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

View File

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

View File

@ -51,6 +51,12 @@
<version>${project.version}</version> <version>${project.version}</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.hl7.fhir.testcases</groupId> <groupId>org.hl7.fhir.testcases</groupId>
@ -178,6 +184,7 @@
<groupId>org.testcontainers</groupId> <groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId> <artifactId>junit-jupiter</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.jayway.jsonpath</groupId> <groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path-assert</artifactId> <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.api.config.DaoConfig;
import ca.uhn.fhir.jpa.model.entity.TagDefinition; import ca.uhn.fhir.jpa.model.entity.TagDefinition;
import ca.uhn.fhir.jpa.model.entity.TagTypeEnum; import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
import com.github.benmanes.caffeine.cache.Cache; import ca.uhn.fhir.sl.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine; import ca.uhn.fhir.sl.cache.CacheFactory;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
@ -77,7 +77,7 @@ class MemoryCacheServiceTest {
} }
void withCacheOfSize(int theMaxSize) { 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) { void fillCacheWithRange(int theStart, int theEnd) {

View File

@ -107,8 +107,14 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.github.ben-manes.caffeine</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>caffeine</artifactId> <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> <scope>test</scope>
</dependency> </dependency>

View File

@ -25,6 +25,13 @@
<artifactId>org.hl7.fhir.utilities</artifactId> <artifactId>org.hl7.fhir.utilities</artifactId>
<version>${fhir_core_version}</version> <version>${fhir_core_version}</version>
</dependency> </dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<!-- Server --> <!-- Server -->
<dependency> <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> <description>Tooling for migrating SQL schemas.</description>
<dependencies> <dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId> <artifactId>spring-jdbc</artifactId>
@ -85,6 +81,8 @@
<artifactId>sqlbuilder</artifactId> <artifactId>sqlbuilder</artifactId>
</dependency> </dependency>
<!-- test -->
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-test-utilities</artifactId> <artifactId>hapi-fhir-test-utilities</artifactId>

View File

@ -59,6 +59,11 @@
<artifactId>hapi-fhir-structures-hl7org-dstu2</artifactId> <artifactId>hapi-fhir-structures-hl7org-dstu2</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </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 --> <!-- TODO KHS remove jpa stuff from here -->
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <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.config.DaoConfig;
import ca.uhn.fhir.jpa.api.model.TranslationQuery; import ca.uhn.fhir.jpa.api.model.TranslationQuery;
import ca.uhn.fhir.jpa.model.entity.TagTypeEnum; 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.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder; 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.beans.factory.annotation.Autowired;
import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.transaction.support.TransactionSynchronizationManager;
@ -35,6 +36,7 @@ import javax.annotation.Nonnull;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function; import java.util.function.Function;
import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.MINUTES;
@ -87,10 +89,7 @@ public class MemoryCacheService {
break; break;
} }
Cache<Object, Object> nextCache = Caffeine.newBuilder() Cache<Object, Object> nextCache = CacheFactory.build(SECONDS.toMillis(timeoutSeconds), maximumSize);
.expireAfterWrite(timeoutSeconds, SECONDS)
.maximumSize(maximumSize)
.build();
myCaches.put(next, nextCache); myCaches.put(next, nextCache);
} }

View File

@ -126,6 +126,11 @@
<artifactId>org.hl7.fhir.dstu3</artifactId> <artifactId>org.hl7.fhir.dstu3</artifactId>
<version>${fhir_core_version}</version> <version>${fhir_core_version}</version>
</dependency> </dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.fhir</groupId> <groupId>org.fhir</groupId>
<artifactId>ucum</artifactId> <artifactId>ucum</artifactId>
@ -197,15 +202,14 @@
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<!-- Used by the validator --> <!-- Testing -->
<dependency> <dependency>
<groupId>com.github.ben-manes.caffeine</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>caffeine</artifactId> <artifactId>hapi-fhir-caching-testing</artifactId>
<optional>true</optional> <version>${project.version}</version>
<scope>test</scope>
</dependency> </dependency>
<!-- Testing -->
<dependency> <dependency>
<groupId>org.xmlunit</groupId> <groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId> <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.context.support.ValidationSupportContext;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.util.CoverageIgnore; 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.Validate;
import org.apache.commons.lang3.time.DateUtils; 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.context.IWorkerContext;
import org.hl7.fhir.dstu3.formats.IParser; import org.hl7.fhir.dstu3.formats.IParser;
import org.hl7.fhir.dstu3.formats.ParserType; import org.hl7.fhir.dstu3.formats.ParserType;
@ -43,7 +43,6 @@ import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit;
import static org.apache.commons.lang3.StringUtils.isNotBlank; 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)) { 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)); 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 // Set a default locale
setValidationMessageLanguage(getLocale()); setValidationMessageLanguage(getLocale());

View File

@ -40,6 +40,11 @@
<artifactId>org.hl7.fhir.r4</artifactId> <artifactId>org.hl7.fhir.r4</artifactId>
<version>${fhir_core_version}</version> <version>${fhir_core_version}</version>
</dependency> </dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.fhir</groupId> <groupId>org.fhir</groupId>
<artifactId>ucum</artifactId> <artifactId>ucum</artifactId>
@ -76,12 +81,6 @@
<optional>true</optional> <optional>true</optional>
</dependency> </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 Test dependencies on other optional parts of HAPI
@ -174,6 +173,13 @@
</dependency> </dependency>
<!-- Testing --> <!-- Testing -->
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.xmlunit</groupId> <groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId> <artifactId>xmlunit-core</artifactId>
@ -259,6 +265,7 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- UNIT TEST DEPENDENCIES --> <!-- UNIT TEST DEPENDENCIES -->
<dependency> <dependency>
<groupId>net.sf.json-lib</groupId> <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.context.support.ValidationSupportContext;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.util.CoverageIgnore; 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.Validate;
import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
import org.fhir.ucum.UcumService; 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.FHIRException;
import org.hl7.fhir.exceptions.TerminologyServiceException; import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.r4.context.IWorkerContext; 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)); 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 // Set a default locale
setValidationMessageLanguage(getLocale()); setValidationMessageLanguage(getLocale());

View File

@ -16,12 +16,13 @@
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>com.squareup.okhttp3</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>okhttp</artifactId> <artifactId>hapi-fhir-base</artifactId>
<version>${project.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-base</artifactId> <artifactId>hapi-fhir-caching-api</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
@ -76,13 +77,6 @@
<optional>true</optional> <optional>true</optional>
</dependency> </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 Test dependencies on other optional parts of HAPI
--> -->

View File

@ -1,15 +1,15 @@
package org.hl7.fhir.r4b.hapi.ctx; 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.FhirContext;
import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext; 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.rest.api.Constants;
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.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.Validate;
import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
import org.fhir.ucum.UcumService; import org.fhir.ucum.UcumService;
@ -49,7 +49,6 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit;
import static org.apache.commons.lang3.StringUtils.isNotBlank; 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)); 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 // Set a default locale
setValidationMessageLanguage(getLocale()); setValidationMessageLanguage(getLocale());

View File

@ -35,6 +35,11 @@
<artifactId>org.hl7.fhir.r5</artifactId> <artifactId>org.hl7.fhir.r5</artifactId>
<version>${fhir_core_version}</version> <version>${fhir_core_version}</version>
</dependency> </dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.fhir</groupId> <groupId>org.fhir</groupId>
<artifactId>ucum</artifactId> <artifactId>ucum</artifactId>
@ -76,13 +81,6 @@
<optional>true</optional> <optional>true</optional>
</dependency> </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 Test dependencies on other optional parts of HAPI
--> -->
@ -154,6 +152,12 @@
</dependency> </dependency>
<!-- Testing --> <!-- Testing -->
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.xmlunit</groupId> <groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId> <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.i18n.Msg;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.util.CoverageIgnore; 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.Validate;
import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
import org.fhir.ucum.UcumService; 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.FHIRException;
import org.hl7.fhir.exceptions.TerminologyServiceException; import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.r5.context.IWorkerContext; 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)); 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 // Set a default locale
setValidationMessageLanguage(getLocale()); setValidationMessageLanguage(getLocale());

View File

@ -26,6 +26,17 @@
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </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> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.validation</artifactId> <artifactId>org.hl7.fhir.validation</artifactId>
@ -75,15 +86,11 @@
<version>${fhir_core_version}</version> <version>${fhir_core_version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.fhir</groupId> <groupId>org.fhir</groupId>
<artifactId>ucum</artifactId> <artifactId>ucum</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.google.code.findbugs</groupId> <groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId> <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.TranslateConceptResults;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions; 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.concurrent.BasicThreadFactory;
import org.apache.commons.lang3.time.DateUtils; 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.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -63,31 +63,11 @@ public class CachingValidationSupport extends BaseValidationSupportWrapper imple
*/ */
public CachingValidationSupport(IValidationSupport theWrap, CacheTimeouts theCacheTimeouts) { public CachingValidationSupport(IValidationSupport theWrap, CacheTimeouts theCacheTimeouts) {
super(theWrap.getFhirContext(), theWrap); super(theWrap.getFhirContext(), theWrap);
myExpandValueSetCache = Caffeine myExpandValueSetCache = CacheFactory.build(theCacheTimeouts.getExpandValueSetMillis(), 100);
.newBuilder() myValidateCodeCache = CacheFactory.build(theCacheTimeouts.getValidateCodeMillis(), 5000);
.expireAfterWrite(theCacheTimeouts.getExpandValueSetMillis(), TimeUnit.MILLISECONDS) myLookupCodeCache = CacheFactory.build(theCacheTimeouts.getLookupCodeMillis(), 5000);
.maximumSize(100) myTranslateCodeCache = CacheFactory.build(theCacheTimeouts.getTranslateCodeMillis(), 5000);
.build(); myCache = CacheFactory.build(theCacheTimeouts.getMiscMillis(), 5000);
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();
myNonExpiringCache = Collections.synchronizedMap(new HashMap<>()); myNonExpiringCache = Collections.synchronizedMap(new HashMap<>());
LinkedBlockingQueue<Runnable> executorQueue = new LinkedBlockingQueue<>(1000); 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.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; 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.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
import org.fhir.ucum.UcumService; 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.advisors.impl.BaseAdvisor_10_50;
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50; import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50;
import org.hl7.fhir.exceptions.FHIRException; 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)); timeoutMillis = Long.parseLong(System.getProperty(Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS));
} }
myFetchResourceCache = Caffeine.newBuilder() myFetchResourceCache = CacheFactory.build(timeoutMillis, 10000, key -> {
.expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS)
.maximumSize(10000)
.build(key -> {
String fetchResourceName = key.getResourceName(); String fetchResourceName = key.getResourceName();
if (myValidationSupportContext.getRootValidationSupport().getFhirContext().getVersion().getVersion() == FhirVersionEnum.DSTU2) { if (myValidationSupportContext.getRootValidationSupport().getFhirContext().getVersion().getVersion() == FhirVersionEnum.DSTU2) {

20
pom.xml
View File

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