Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re-introduce Caching #2393

Merged
merged 10 commits into from
Dec 17, 2024
Merged
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