Skip to content

Commit

Permalink
Re-introduce Caching (#2393)
Browse files Browse the repository at this point in the history
* Implemented caching for cohort def list, concept set list, permissions, and data sources.
Implementation uses ehCache 3.9.
Changed editor config to tabs.
Made cache endpoints auth restricted.
  • Loading branch information
chrisknoll authored Dec 17, 2024
1 parent 67f8816 commit 70dc4e2
Show file tree
Hide file tree
Showing 23 changed files with 655 additions and 96 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
root = true

[*]
indent_style = space
indent_style = tab
indent_size = 2
end_of_line = crlf
charset = utf-8
Expand Down
13 changes: 13 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,10 @@
<logging.level.org.hibernate>info</logging.level.org.hibernate>
<logging.level.org.apache.shiro>warn</logging.level.org.apache.shiro>

<!-- Spring Cache properties -->
<spring.cache.type>jcache</spring.cache.type>

<!-- Spring Batch -->
<spring.batch.taskExecutor.corePoolSize>10</spring.batch.taskExecutor.corePoolSize>
<spring.batch.taskExecutor.maxPoolSize>20</spring.batch.taskExecutor.maxPoolSize>
<spring.batch.taskExecutor.queueCapacity>2147483647</spring.batch.taskExecutor.queueCapacity>
Expand Down Expand Up @@ -272,6 +276,7 @@
<cache.jobs.count>3</cache.jobs.count>
<!-- Achilles cache -->
<cache.achilles.usePersonCount>true</cache.achilles.usePersonCount>
<cache.webapi.enabled>true</cache.webapi.enabled>

<!-- Build info -->
<buildinfo.atlas.milestone.id>47</buildinfo.atlas.milestone.id>
Expand Down Expand Up @@ -744,6 +749,10 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
<version>1.1.1</version>
</dependency> <dependency>
<groupId>org.ohdsi.sql</groupId>
<artifactId>SqlRender</artifactId>
<version>${SqlRender.version}</version>
Expand Down Expand Up @@ -1202,6 +1211,10 @@
<version>1.1.7</version>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.9.11</version>
</dependency> <dependency>
<groupId>com.opentable.components</groupId>
<artifactId>otj-pg-embedded</artifactId>
<version>0.13.1</version>
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/org/ohdsi/webapi/CacheConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.ohdsi.webapi;

import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableCaching
public class CacheConfig {

}
18 changes: 10 additions & 8 deletions src/main/java/org/ohdsi/webapi/JerseyConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

import javax.inject.Singleton;
import javax.ws.rs.ext.RuntimeDelegate;
import org.ohdsi.webapi.cache.CacheService;

/**
*
Expand All @@ -59,31 +60,32 @@ public JerseyConfig() {
public void afterPropertiesSet() throws Exception {
packages(this.rootPackage);
register(ActivityService.class);
register(CacheService.class);
register(CcController.class);
register(CDMResultsService.class);
register(CohortAnalysisService.class);
register(CohortDefinitionService.class);
register(CohortResultsService.class);
register(CohortService.class);
register(ConceptSetService.class);
register(DDLService.class);
register(EvidenceService.class);
register(FeasibilityService.class);
register(FeatureExtractionService.class);
register(InfoService.class);
register(IRAnalysisResource.class);
register(JobService.class);
register(MultiPartFeature.class);
register(PermissionController.class);
register(PersonService.class);
register(ScriptExecutionController.class);
register(ScriptExecutionCallbackController.class);
register(SourceController.class);
register(SqlRenderService.class);
register(DDLService.class);
register(SSOController.class);
register(TherapyPathResultsService.class);
register(UserService.class);
register(VocabularyService.class);
register(ScriptExecutionController.class);
register(ScriptExecutionCallbackController.class);
register(MultiPartFeature.class);
register(FeatureExtractionService.class);
register(CcController.class);
register(SSOController.class);
register(PermissionController.class);
register(new AbstractBinder() {
@Override
protected void configure() {
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/org/ohdsi/webapi/cache/CacheInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2019 cknoll1.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.ohdsi.webapi.cache;

import java.io.Serializable;
import java.util.Optional;
import javax.cache.management.CacheStatisticsMXBean;

/**
*
* @author cknoll1
*/
public class CacheInfo implements Serializable{
public String cacheName;
public Long entries;
public CacheStatisticsMXBean cacheStatistics;
}
94 changes: 94 additions & 0 deletions src/main/java/org/ohdsi/webapi/cache/CacheService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright 2019 cknoll1.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.ohdsi.webapi.cache;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.StreamSupport;
import javax.cache.Cache;
import javax.cache.CacheManager;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.ohdsi.webapi.util.CacheHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
*
* @author cknoll1
*/
@Path("/cache")
@Component
public class CacheService {

public static class ClearCacheResult {

public List<CacheInfo> clearedCaches;

private ClearCacheResult() {
this.clearedCaches = new ArrayList<>();
}
}

private CacheManager cacheManager;

@Autowired(required = false)
public CacheService(CacheManager cacheManager) {

this.cacheManager = cacheManager;
}

public CacheService() {
}


@GET
@Path("/")
@Produces(MediaType.APPLICATION_JSON)
public List<CacheInfo> getCacheInfoList() {
List<CacheInfo> caches = new ArrayList<>();

if (cacheManager == null) return caches; //caching is disabled

for (String cacheName : cacheManager.getCacheNames()) {
Cache cache = cacheManager.getCache(cacheName);
CacheInfo info = new CacheInfo();
info.cacheName = cacheName;
info.entries = StreamSupport.stream(cache.spliterator(), false).count();
info.cacheStatistics = CacheHelper.getCacheStats(cacheManager , cacheName);
caches.add(info);
}
return caches;
}
@GET
@Path("/clear")
@Produces(MediaType.APPLICATION_JSON)
public ClearCacheResult clearAll() {
ClearCacheResult result = new ClearCacheResult();

for (String cacheName : cacheManager.getCacheNames()) {
Cache cache = cacheManager.getCache(cacheName);
CacheInfo info = new CacheInfo();
info.cacheName = cacheName;
info.entries = StreamSupport.stream(cache.spliterator(), false).count();
result.clearedCaches.add(info);
cache.clear();
}
return result;
}
}
16 changes: 16 additions & 0 deletions src/main/java/org/ohdsi/webapi/security/PermissionService.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ public class PermissionService {
@Value("#{!'${security.provider}'.equals('DisabledSecurity')}")
private boolean securityEnabled;

@Value("${security.defaultGlobalReadPermissions}")
private boolean defaultGlobalReadPermissions;

private final EntityGraph PERMISSION_ENTITY_GRAPH = EntityGraphUtils.fromAttributePaths("rolePermissions", "rolePermissions.role");

public PermissionService(
Expand Down Expand Up @@ -227,4 +230,17 @@ public void fillReadAccess(CommonEntity entity, CommonEntityDTO entityDTO) {
public boolean isSecurityEnabled() {
return this.securityEnabled;
}

// Use this key for cache (asset lists) that may be associated to a user or shared across users.
public String getAssetListCacheKey() {
if (this.isSecurityEnabled() && !defaultGlobalReadPermissions)
return permissionManager.getSubjectName();
else
return "ALL_USERS";
}

// use this cache key when the cache is associated to a user
public String getSubjectCacheKey() {
return this.isSecurityEnabled() ? permissionManager.getSubjectName() : "ALL_USERS";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,18 @@
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.cache.CacheManager;
import javax.cache.configuration.MutableConfiguration;
import javax.ws.rs.core.Response.ResponseBuilder;

import static org.ohdsi.webapi.Constants.Params.COHORT_DEFINITION_ID;
import static org.ohdsi.webapi.Constants.Params.JOB_NAME;
import static org.ohdsi.webapi.Constants.Params.SOURCE_ID;
import org.ohdsi.webapi.source.SourceService;
import static org.ohdsi.webapi.util.SecurityUtils.whitelist;
import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;

/**
* Provides REST services for working with cohort definitions.
Expand All @@ -149,6 +154,24 @@
@Component
public class CohortDefinitionService extends AbstractDaoService implements HasTags<Integer> {

//create cache
@Component
public static class CachingSetup implements JCacheManagerCustomizer {

public static final String COHORT_DEFINITION_LIST_CACHE = "cohortDefinitionList";

@Override
public void customize(CacheManager cacheManager) {
// Evict when a cohort definition is created or updated, or permissions, or tags
if (!CacheHelper.getCacheNames(cacheManager).contains(COHORT_DEFINITION_LIST_CACHE)) {
cacheManager.createCache(COHORT_DEFINITION_LIST_CACHE, new MutableConfiguration<String, List<CohortMetadataDTO>>()
.setTypes(String.class, (Class<List<CohortMetadataDTO>>) (Class<?>) List.class)
.setStoreByValue(false)
.setStatisticsEnabled(true));
}
}
}

private static final CohortExpressionQueryBuilder queryBuilder = new CohortExpressionQueryBuilder();

@Autowired
Expand Down Expand Up @@ -205,7 +228,7 @@ public class CohortDefinitionService extends AbstractDaoService implements HasTa
@Autowired
private VersionService<CohortVersion> versionService;

@Value("${security.defaultGlobalReadPermissions}")
@Value("${security.defaultGlobalReadPermissions}")
private boolean defaultGlobalReadPermissions;

private final MarkdownRender markdownPF = new MarkdownRender();
Expand Down Expand Up @@ -408,6 +431,7 @@ public GenerateSqlResult generateSql(GenerateSqlRequest request) {
@Path("/")
@Produces(MediaType.APPLICATION_JSON)
@Transactional
@Cacheable(cacheNames = CachingSetup.COHORT_DEFINITION_LIST_CACHE, key = "@permissionService.getSubjectCacheKey()")
public List<CohortMetadataDTO> getCohortDefinitionList() {
List<CohortDefinition> definitions = cohortDefinitionRepository.list();
return definitions.stream()
Expand Down Expand Up @@ -436,6 +460,7 @@ public List<CohortMetadataDTO> getCohortDefinitionList() {
@Transactional
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@CacheEvict(cacheNames = CachingSetup.COHORT_DEFINITION_LIST_CACHE, allEntries = true)
public CohortDTO createCohortDefinition(CohortDTO dto) {

Date currentTime = Calendar.getInstance().getTime();
Expand Down Expand Up @@ -538,6 +563,7 @@ public int getCountCDefWithSameName(@PathParam("id") @DefaultValue("0") final in
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Transactional
@CacheEvict(cacheNames = CachingSetup.COHORT_DEFINITION_LIST_CACHE, allEntries = true)
public CohortDTO saveCohortDefinition(@PathParam("id") final int id, CohortDTO def) {
Date currentTime = Calendar.getInstance().getTime();

Expand Down Expand Up @@ -670,6 +696,7 @@ public List<CohortGenerationInfoDTO> getInfo(@PathParam("id") final int id) {
@Produces(MediaType.APPLICATION_JSON)
@Path("/{id}/copy")
@Transactional
@CacheEvict(cacheNames = CachingSetup.COHORT_DEFINITION_LIST_CACHE, allEntries = true)
public CohortDTO copy(@PathParam("id") final int id) {
CohortDTO sourceDef = getCohortDefinition(id);
sourceDef.setId(null); // clear the ID
Expand Down Expand Up @@ -954,6 +981,7 @@ private Response printFrindly(String markdown, String format) {
@POST
@Produces(MediaType.APPLICATION_JSON)
@Path("/{id}/tag/")
@CacheEvict(cacheNames = CachingSetup.COHORT_DEFINITION_LIST_CACHE, allEntries = true)
@Transactional
public void assignTag(@PathParam("id") final Integer id, final int tagId) {
CohortDefinition entity = cohortDefinitionRepository.findOne(id);
Expand All @@ -971,6 +999,7 @@ public void assignTag(@PathParam("id") final Integer id, final int tagId) {
@DELETE
@Produces(MediaType.APPLICATION_JSON)
@Path("/{id}/tag/{tagId}")
@CacheEvict(cacheNames = CachingSetup.COHORT_DEFINITION_LIST_CACHE, allEntries = true)
@Transactional
public void unassignTag(@PathParam("id") final Integer id, @PathParam("tagId") final int tagId) {
CohortDefinition entity = cohortDefinitionRepository.findOne(id);
Expand Down Expand Up @@ -1106,6 +1135,7 @@ public void deleteVersion(@PathParam("id") final int id, @PathParam("version") f
@Produces(MediaType.APPLICATION_JSON)
@Path("/{id}/version/{version}/createAsset")
@Transactional
@CacheEvict(cacheNames = CachingSetup.COHORT_DEFINITION_LIST_CACHE, allEntries = true)
public CohortDTO copyAssetFromVersion(@PathParam("id") final int id, @PathParam("version") final int version) {
checkVersion(id, version, false);
CohortVersion cohortVersion = versionService.getById(VersionType.COHORT, id, version);
Expand Down
Loading

0 comments on commit 70dc4e2

Please sign in to comment.